Inserting an appropriately sized frame as character at the end of a paragraph

My goal is to write a macro that will insert a Frame at the end of paragraphs “As Character” and have the frame be of the appropriate width to match the margins of the paragraph. Putting the frame in is no problem, but I can’t for the life of me figure out how to determine that width. It’s obvious that it needs to be the [page width]-[left+right page margins]-[left margin of the paragraph] -[right margin of the paragraph]. The trouble is determining the left and right margins of the paragraph. (I believe that left margin will have contributions from the paragraph-level overrides, the paragraph style, and the numbering/bullet style and level.)

My version info:
Version: 7.5.7.1 (X86_64) / LibreOffice Community
Build ID: 47eb0cf7efbacdee9b19ae25d6752381ede23126
CPU threads: 4; OS: Windows 10.0 Build 19045; UI render: Skia/Raster; VCL: win
Locale: en-US (en_US); UI: en-US
Calc: CL threaded

In order to not be too much trouble to the community, I’ve been using ChatGPT when I can; however, due to some incomplete documentation of the UNO API (and probably some clumsiness on my end), GPT keeps steering me in directions that return 0 for the left paragraph margin, even though that is obviously not correct.

I should provide a little more info. I’m attaching a file that I’m trying to use to test GPT’s approach to determining the margins.
Margin Testing.odt (14.2 KB)
Also, the latest (failing) version that my chat with GPT produced is:

Option Explicit

Sub MarginTest_EffectiveIndentation()
    Dim oDoc As Object : oDoc = ThisComponent
    Dim oText As Object : oText = oDoc.Text
    Dim oTextEnum As Object : oTextEnum = oText.createEnumeration()
    Dim oPar As Object
    Dim oParaStyles As Object : oParaStyles = oDoc.StyleFamilies.getByName("ParagraphStyles")
    Dim oNumStyles As Object
    Dim i As Long : i = 0

    Dim sParaStyleName As String
    Dim sListStyleName As String
    Dim dblParLeft As Double
    Dim dblParFirst As Double
    Dim dblParRight As Double
    Dim dblStyleLeft As Double
    Dim dblStyleFirst As Double
    Dim dblStyleRight As Double
    Dim dblListLeft As Double
    Dim dblListFirst As Double
    Dim dblEffectiveLeft As Double
    Dim dblEffectiveFirst As Double
    Dim dblTextStart As Double
    Dim nLevel As Integer
    Dim sMessage As String

    ' Try to get numbering styles collection (may fail if none)
    On Error Resume Next
    oNumStyles = oDoc.StyleFamilies.getByName("NumberingStyles")
    On Error GoTo 0

    While oTextEnum.hasMoreElements()
        oPar = oTextEnum.nextElement()
        If oPar.supportsService("com.sun.star.text.Paragraph") Then
            i = i + 1

            ' --- paragraph direct values (may be 0 if inherited) ---
            dblParLeft = 0
            dblParFirst = 0
            dblParRight = 0
            On Error Resume Next
            dblParLeft = oPar.ParaLeftMargin
            dblParFirst = oPar.ParaFirstLineIndent
            dblParRight = oPar.ParaRightMargin
            On Error GoTo 0
            ' --- (end paragraph direct values) ---

            ' --- get paragraph style values (defaults) ---
            sParaStyleName = ""
            dblStyleLeft = 0
            dblStyleFirst = 0
            dblStyleRight = 0
            On Error Resume Next
            sParaStyleName = oPar.ParaStyleName
            If sParaStyleName = "Standard" Then sParaStyleName = "Default Paragraph Style"
            If sParaStyleName <> "" Then
                Dim oParaStyle As Object
                oParaStyle = oParaStyles.getByName(sParaStyleName)
                dblStyleLeft = oParaStyle.ParaLeftMargin
                dblStyleFirst = oParaStyle.ParaFirstLineIndent
                dblStyleRight = oParaStyle.ParaRightMargin
            End If
            On Error GoTo 0
            ' --- (end style values) ---

            ' --- Determine base effective values from style + overrides ---
            If Abs(dblParLeft) > 0.0000001 Then
                dblEffectiveLeft = dblParLeft
            Else
                dblEffectiveLeft = dblStyleLeft
            End If

            If Abs(dblParFirst) > 0.0000001 Then
                dblEffectiveFirst = dblParFirst
            Else
                dblEffectiveFirst = dblStyleFirst
            End If

            If Abs(dblParRight) > 0.0000001 Then
                dblParRight = dblParRight
            Else
                dblParRight = dblStyleRight
            End If

            ' --- Handle numbering/list indentation ---
            dblListLeft = 0
            dblListFirst = 0
            sListStyleName = ""
            nLevel = 0
            On Error Resume Next
            sListStyleName = oPar.NumberingStyleName
            nLevel = oPar.NumberingLevel
            On Error GoTo 0

            Dim oNumRuleSource As Object : oNumRuleSource = Nothing

            ' Try numbering style from style family
            If sListStyleName <> "" And Not IsNull(oNumStyles) Then
                On Error Resume Next
                Dim oNumStyle As Object
                oNumStyle = oNumStyles.getByName(sListStyleName)
                If Err.Number <> 0 Then
                    Err.Clear
                    Dim nm As Variant
                    For Each nm In oNumStyles.ElementNames
                        If nm = sListStyleName Then
                            oNumStyle = oNumStyles.getByName(nm)
                            Exit For
                        End If
                    Next nm
                End If
                On Error GoTo 0
                If Not IsNull(oNumStyle) Then oNumRuleSource = oNumStyle
            End If

            ' Fallback: paragraph-level numbering rules
            If IsNull(oNumRuleSource) Or IsEmpty(oNumRuleSource) Then
                On Error Resume Next
                If Not IsNull(oPar.NumberingRules) Then oNumRuleSource = oPar.NumberingRules
                On Error GoTo 0
            End If

            ' Extract indentation from numbering rules
            If Not IsNull(oNumRuleSource) And Not IsEmpty(oNumRuleSource) Then
                On Error Resume Next
                Dim rules As Variant
                rules = oNumRuleSource.NumberingRules
                If Err.Number <> 0 Then
                    Err.Clear
                    rules = oNumRuleSource
                End If
                On Error GoTo 0

                On Error Resume Next
                Dim levelProps As Object
                If IsArray(rules) Then
                    If nLevel >= LBound(rules) And nLevel <= UBound(rules) Then
                        levelProps = rules(nLevel)
                    End If
                ElseIf HasUnoInterfaces(rules, "com.sun.star.container.XIndexAccess") Then
                    levelProps = rules.getByIndex(nLevel)
                End If
                On Error GoTo 0

                If Not IsNull(levelProps) And Not IsEmpty(levelProps) Then
                    On Error Resume Next
                    dblListLeft = levelProps.getPropertyValue("LeftMargin")
                    If Err.Number <> 0 Then Err.Clear : dblListLeft = 0
                    dblListFirst = levelProps.getPropertyValue("FirstLineIndent")
                    If Err.Number <> 0 Then Err.Clear : dblListFirst = 0
                    On Error GoTo 0
                End If
            End If

            ' --- Combine list contribution ---
            dblEffectiveLeft = dblEffectiveLeft + dblListLeft
            dblEffectiveFirst = dblEffectiveFirst + dblListFirst

            ' --- Compute text start ---
            dblTextStart = dblEffectiveLeft + dblEffectiveFirst
            sMessage = "Paragraph " & i & ":" & Chr(10)
            sMessage = sMessage & "  Style: " & sParaStyleName & Chr(10)
            sMessage = sMessage & "  List style (raw): " & sListStyleName & "  level: " & (nLevel + 1) & Chr(10)
            sMessage = sMessage & "  Contributes " & Format(dblListLeft / 100 / 25.4, "0.00") & " in to the left margin" & Chr(10)
            sMessage = sMessage & "  and " & Format(dblListFirst / 100 / 25.4, "0.00") & " in to the first line." & Chr(10)
            sMessage = sMessage & "  Effective left margin: " & Format(dblEffectiveLeft / 100 / 25.4, "0.00") & " in" & Chr(10)
            sMessage = sMessage & "  Effective first-line indent: " & Format(dblEffectiveFirst / 100 / 25.4, "0.00") & " in" & Chr(10)
            sMessage = sMessage & "  Computed first-line text start: " & Format(dblTextStart / 100 / 25.4, "0.00") & " in"
            MsgBox sMessage
        End If
    Wend
End Sub

Instead of trying ideas erratically, tell us what you want to see inside this frame. Since you try to compute the width of paragraph text, obviously, this frame should appear below the paragraph, similar to a bottom border.

Frame width can be automatically adjusted to paragraph area width (unfortunately not to paragraph text area, i.e. inside indents).

A frame style would do the job without macros. The frame definition can be stored in an AutoText entry and its invocation (for insertion) is even simpler than a macro call.

I am waiting for the frame contents to try and design a solution.

PS: don’t end your sentences (or even worse your paragraphs) with double spaces. This usage, dating from the mechanical typewriter era in the US, has been deprecated for decades even in the US. More, with advanced document processors, it disturbs justification algorithms when spaces are expanded or shrinked, potentially breaking between the spaces and ruining your alignment.

The intent here is to put in a frame that is as wide as allowed by margins where a response to the paragraph may be entered (as text) without altering the automatic line numbers in the document. The paragraph might be part of a bulleted list (potentially with outline levels), so that complicates the computation of the effective paragraph margins…
I know my descriptions may seem erratic, but I’m not “trying ideas erratically”. I’m asking ChatGPT for an approach, testing it, and giving feedback where it doesn’t work, and making the suggested fixes, etc. That’s how I got to the macro I posted.

Perhaps I should have started here when looking for a way to do this, instead of starting with ChatGPT…

Then there is a much simpler solution than a frame. Use a “standard” paragraph with a custom style applied.

This style will remove text from line numbering in Outline & List tab. If you want a border around it as you could have done with a frame, add a Border.

To compute a width “compatible” with a bullet/numbered list, attache a new list style to it with a U+0020 SPACE bullet and adjust position properties to be the same as the outer list.

Provide an example with your “answers” and I’ll show you how to do it.

That would push subsequent paragraphs further down, changing their automatic line numbers…

Not at all. Provide a sample file along this line and I’ll adjust the formatting to show you how it works.

Today’s Jobs Example.odt (15.2 KB)
Here’s a minimal example. I want the response to the line “Put it away in the appropriate dressers/closets” to be “Clean, neatly folded clothes are in the drawers.” and the response to “Sweep the floors” to be “Lines in the carpet can be observed, incicating the completion of this task.” Without changing the fact that “Make lunch” is line 10.

You may need to rethink your concept,
I would suppose that anchoring "as character isn’t appropriate. You can’t position and resize such a frame reasonably.
Anchor to paragraph.
Use a frame style with relative width 100% and assign it to your frame.
Understandt that the achievable width isn’t a property of the paragraph, but one of the respective page style.
Why do you think to need a macro?

Well, you can do it by a macro, but in this special case I wouldn’t write it, but base it on a recorded one.
(…despite the fact that I dislike recorded macros.)

See example:
disask_128552_automaticFrameInsertionAndSizing.odt (16.4 KB)

Really?! I’ve never run into issues with two spaces after a sentence, and it’s the convention we were taught when I was in school in a typing class on computers, which would have been around 2003.

And how should your document look after insertion of the frames?
What content should those frames have?

Dont’t follow the wrong dogm that formatting comes before functionality.

(post deleted by author)

A bug prevents proper vertical positioning of frames anchored to a paragraph and positioned at the bottom of the entire paragraph area. So I resorted to using “As Character”, which means the left side of the frame will be controlled by the left margin of the paragraph. This is why I want to adjust the width in the first place. If the paragraph is part of a bulleted list, the frame won’t be able to use the horizontal space to the left of the paragraph’s text, and if the frame width is left as the space allowed by the page’s margins, the right end of the frame will overflow into the right page margin. Thus, my quest to find the effective left margin of the paragraph.

I obviously don’t understand the use case.
If the example has just a lose relation to the actual task, I would anyway consider a Writer document not appropriate, but would prefer Calc.
I’m out now.

That’s not because you never encountered the problem that it does not exist. I assure you that I went into this kind of issue when working on advanced typography for reproducing existing books.

I am always surprised by the reluctance of academics to change centennial routine while they foster innovation in other areas :wink:

Deprecation dates back at least in the '80s but not later than the very beginning of the '90s. So, this illustrates my statement regarding teachers.

What you need is a paragraph style which is not included in line numbering.

I created Response paragraph style with the following properties:

  • derived from Body Text
  • in Outline & List: Include this para in line numbering *unticked, List style No Bullet (more on this below)
  • border added in Borders so that you immediately see which paragraph is which

Since you want to attach a “response” to items at various levels of a list without a bullet I assume, I create a user list style similar to the one you use for your items (beware, you did so with direct formatting, i.e. a toolbar button which applied a “default” style – so your control is not rigorous). This will allow to align easily under your items (take care to copy the Position parameters so that they are equal. The No Bullet list style is a bullet one with the bullet set to a space character

Using a different list style makes sure your lists don’t interfere. And this is semantically consistent.

Another possibility is to attach the same list style to Response and use the trick to insert an “unnumbered” item: press Bksp at the very beginning of the list item to get rif of the bullet/number. But do it only once otherwise you also remove the list property.

Here is my revised sample file:
Today’s Jobs Example.odt (19.8 KB)

Note that line numbering for your “non-response” items is undisturbed.

@ajlittoz you’re the best! I had no idea a pargraph style could be excluded like that. All this time I wasted trying to solve the problem with frames…I definitely should have come here before consulting ChatGPT.

ChatGPT can only repeat solutions it already saw somewhere.

Your mistake was to have opted for a frame as “the” solution. And since frames are really a tough feature and are difficult to master in order to get a predictable and stable result, you decided to add macros in an attempt to get it. Complexity over complexity.

The lesson is: when asking here always come back to the beginning. Forget your present state of achievement. Describe the starting point, i.e. what you want to obtain from an author’s point of view. Leave aside your “solutions”. As you noticed, it is difficult to step on a running train. When I forced you to describe your goal, the solution was obvious and easy.

Writer is feature-rich. It took me a decade to discover them and another decade to understand their rationale and understand to which circumstances they answered.

I bet you never read the manual (or at least not deeply nor thoroughly). You have wrong ideas about frames. Frames create “independent sub-documents”. So they come into play only when you want to insert a side or parallel discourse to your main topic. Such a side discourse can be skipped without changing the meaning of the main topic nor impeding its understanding.

In your case, your “responses” are part of the main topic and should be read in order. Therefore, a frame is not the right choice.

Generally speaking, frames are rarely needed.

PS: if you’re happy with my answer, like it so that it will be flagged for other users benefit.