asciidoctor-dot-leader 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/CHANGELOG.adoc +5 -0
 - data/README.adoc +1 -1
 - data/lib/asciidoctor/dot_leader/dot_leader_pdf.rb +139 -32
 - metadata +1 -1
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 2382e42c9d8e11c7427f3758ea61f111d4fa390fe99a6daf4c0f4eab46860f64
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 7c518c228291a4be4c236c9848f2e97a0e2d4ea94e617efc7bd79e4667ea14ba
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 726405e93b9ed14555643a2ba7d6a490f0f6ab075c54b7dbf16da785c638807f104dd812a0ca94a378316adcae5b8b2ff6df80f770daaee6018916b856f74da7
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 81cbc4675e9ebef09663bdf94460a94ad80b024c2f52e1f800bdde550c616fae9d23f551e2d925711e895d2796728cafee1ab07d56650ea8975c47aa69b70be8
         
     | 
    
        data/CHANGELOG.adoc
    CHANGED
    
    | 
         @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. 
     | 
|
| 
       5 
5 
     | 
    
         
             
            The format is based on https://keepachangelog.com/en/1.0.0/[Keep a Changelog],
         
     | 
| 
       6 
6 
     | 
    
         
             
            and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Versioning].
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
      
 8 
     | 
    
         
            +
            == [1.2.0] - 2025-10-31
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            This release adds support for column_boxes in asciidoctor-pdf link:https://asciidoctor.zulipchat.com/#narrow/channel/288690-users.2Fasciidoctor-pdf/topic/Implicit.20page.20break.20after.20column_box/near/503833058[^].
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
       8 
13 
     | 
    
         
             
            == [1.1.0] - 2025-10-31
         
     | 
| 
       9 
14 
     | 
    
         | 
| 
       10 
15 
     | 
    
         
             
            === Fixed
         
     | 
    
        data/README.adoc
    CHANGED
    
    | 
         @@ -80,7 +80,7 @@ This project provides an inline macro, `pass:[dot:leader["left", "right"]]`, whi 
     | 
|
| 
       80 
80 
     | 
    
         | 
| 
       81 
81 
     | 
    
         
             
            === Target Application
         
     | 
| 
       82 
82 
     | 
    
         | 
| 
       83 
     | 
    
         
            -
            This has only been tested with the  Microsoft Visual Studio Code's AsciiDoc extension's _Preview_ function which uses the ` 
     | 
| 
      
 83 
     | 
    
         
            +
            This has only been tested with the  Microsoft Visual Studio Code's AsciiDoc extension's _Preview_ function which uses the `backend-webview-html5`.
         
     | 
| 
       84 
84 
     | 
    
         | 
| 
       85 
85 
     | 
    
         
             
            === Usage
         
     | 
| 
       86 
86 
     | 
    
         | 
| 
         @@ -44,6 +44,12 @@ class DotLeader < (Asciidoctor::Converter.for 'pdf') 
     | 
|
| 
       44 
44 
     | 
    
         | 
| 
       45 
45 
     | 
    
         
             
                left_side = node.attr('l_text') || ''
         
     | 
| 
       46 
46 
     | 
    
         
             
                right_side = node.attr('r_text') || ''    
         
     | 
| 
      
 47 
     | 
    
         
            +
                
         
     | 
| 
      
 48 
     | 
    
         
            +
                # DEBUG: Initial context information
         
     | 
| 
      
 49 
     | 
    
         
            +
                is_column_box = defined?(::Prawn::Document::ColumnBox) && bounds.is_a?(::Prawn::Document::ColumnBox)
         
     | 
| 
      
 50 
     | 
    
         
            +
                current_column = is_column_box ? bounds.current_column : 'N/A'
         
     | 
| 
      
 51 
     | 
    
         
            +
                # log :info, %(DOT_LEADER_DEBUG: Starting - left="#{left_side}", right="#{right_side}", page=#{page_number}, cursor=#{cursor}, is_column=#{is_column_box}, current_column=#{current_column})
         
     | 
| 
      
 52 
     | 
    
         
            +
                
         
     | 
| 
       47 
53 
     | 
    
         
             
                if left_side.empty? # Allow items only on the right
         
     | 
| 
       48 
54 
     | 
    
         
             
                  left_side = ' '
         
     | 
| 
       49 
55 
     | 
    
         
             
                end
         
     | 
| 
         @@ -76,6 +82,8 @@ class DotLeader < (Asciidoctor::Converter.for 'pdf') 
     | 
|
| 
       76 
82 
     | 
    
         
             
                  }
         
     | 
| 
       77 
83 
     | 
    
         
             
                end
         
     | 
| 
       78 
84 
     | 
    
         | 
| 
      
 85 
     | 
    
         
            +
                # log :info, %(DOT_LEADER_DEBUG: Dot leader config - text="#{dot_leader[:text]}", width=#{dot_leader[:width]}, spacer_width=#{dot_leader[:spacer_width]})
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
       79 
87 
     | 
    
         
             
                # 2025-07-17 BYB: Set toc_font_info
         
     | 
| 
       80 
88 
     | 
    
         
             
                toc_font_info = theme_font :toc do
         
     | 
| 
       81 
89 
     | 
    
         
             
                  { font: font, size: @font_size }
         
     | 
| 
         @@ -103,14 +111,12 @@ class DotLeader < (Asciidoctor::Converter.for 'pdf') 
     | 
|
| 
       103 
111 
     | 
    
         
             
                line_metrics = calc_line_metrics @base_line_height
         
     | 
| 
       104 
112 
     | 
    
         | 
| 
       105 
113 
     | 
    
         
             
                # 2025-07-17 BYB: This needs to be modified
         
     | 
| 
       106 
     | 
    
         
            -
                right_side_placeholder_width = rendered_width_of_string right_side * @toc_max_pagenum_digits
         
     | 
| 
       107 
     | 
    
         
            -
                # puts right_side_placeholder_width.inspect # Various widths
         
     | 
| 
       108 
     | 
    
         
            -
                right_side_placeholder_width = 0 # BYB: Not a problem for now
         
     | 
| 
       109 
     | 
    
         
            -
                
         
     | 
| 
       110 
114 
     | 
    
         
             
                right_side_placeholder_width = rendered_width_of_fragments right_side_fragments, scale, node.roles.to_a
         
     | 
| 
       111 
115 
     | 
    
         | 
| 
       112 
116 
     | 
    
         
             
                left_side_test_width = rendered_width_of_fragments left_side_fragments, scale, node.roles.to_a
         
     | 
| 
       113 
117 
     | 
    
         | 
| 
      
 118 
     | 
    
         
            +
                # log :info, %(DOT_LEADER_DEBUG: Text widths - left=#{left_side_test_width}, right=#{right_side_placeholder_width})
         
     | 
| 
      
 119 
     | 
    
         
            +
                
         
     | 
| 
       114 
120 
     | 
    
         
             
                # 2025-07-17 Set hanging_indent to toc; potentially this could be styled
         
     | 
| 
       115 
121 
     | 
    
         
             
                hanging_indent = @theme.toc_hanging_indent
         
     | 
| 
       116 
122 
     | 
    
         
             
                start_page_number = page_number
         
     | 
| 
         @@ -123,7 +129,7 @@ class DotLeader < (Asciidoctor::Converter.for 'pdf') 
     | 
|
| 
       123 
129 
     | 
    
         
             
                  end
         
     | 
| 
       124 
130 
     | 
    
         | 
| 
       125 
131 
     | 
    
         
             
                  if right_side_placeholder_width > bounds.width
         
     | 
| 
       126 
     | 
    
         
            -
                     
     | 
| 
      
 132 
     | 
    
         
            +
                    log :warn, %(DOT_LEADER_DEBUG: Right side too wide; skipping: #{left_side} - right_width=#{right_side_placeholder_width}, bounds_width=#{bounds.width})
         
     | 
| 
       127 
133 
     | 
    
         
             
                    return nil
         
     | 
| 
       128 
134 
     | 
    
         
             
                  end
         
     | 
| 
       129 
135 
     | 
    
         | 
| 
         @@ -135,6 +141,8 @@ class DotLeader < (Asciidoctor::Converter.for 'pdf') 
     | 
|
| 
       135 
141 
     | 
    
         
             
                  start_cursor = last_fragment_cursor if last_fragment_position.page_number > start_page_number || (start_cursor - last_fragment_cursor) > line_metrics.height
         
     | 
| 
       136 
142 
     | 
    
         
             
                end
         
     | 
| 
       137 
143 
     | 
    
         | 
| 
      
 144 
     | 
    
         
            +
                # log :info, %(DOT_LEADER_DEBUG: After left text - start_dots=#{start_dots}, start_cursor=#{start_cursor})
         
     | 
| 
      
 145 
     | 
    
         
            +
                
         
     | 
| 
       138 
146 
     | 
    
         
             
                # 2025-07-17 No loop
         
     | 
| 
       139 
147 
     | 
    
         
             
                # NOTE: this will leave behind a gap where this entry would have been
         
     | 
| 
       140 
148 
     | 
    
         
             
                # break unless start_dots
         
     | 
| 
         @@ -154,29 +162,115 @@ class DotLeader < (Asciidoctor::Converter.for 'pdf') 
     | 
|
| 
       154 
162 
     | 
    
         
             
                    set_font toc_font_info[:font], dot_leader[:font_size]
         
     | 
| 
       155 
163 
     | 
    
         
             
                    font_style dot_leader[:font_style]
         
     | 
| 
       156 
164 
     | 
    
         
             
                    # For true vertical alignment, dots must be at consistent absolute positions across all lines
         
     | 
| 
       157 
     | 
    
         
            -
                    #  
     | 
| 
       158 
     | 
    
         
            -
                    #  
     | 
| 
      
 165 
     | 
    
         
            +
                    # CONTEXT-AWARE GRID ALIGNMENT:
         
     | 
| 
      
 166 
     | 
    
         
            +
                    # Use appropriate grid origin for each context to ensure proper alignment
         
     | 
| 
      
 167 
     | 
    
         
            +
                    # while maintaining correct positioning within bounds
         
     | 
| 
       159 
168 
     | 
    
         | 
| 
       160 
     | 
    
         
            -
                    #  
     | 
| 
       161 
     | 
    
         
            -
                    # start_dots  
     | 
| 
       162 
     | 
    
         
            -
                     
     | 
| 
      
 169 
     | 
    
         
            +
                    # Calculate the absolute position where dots will start
         
     | 
| 
      
 170 
     | 
    
         
            +
                    # Note: start_dots may already be absolute in some contexts (like right column)
         
     | 
| 
      
 171 
     | 
    
         
            +
                    # Check if start_dots is already in absolute coordinates
         
     | 
| 
      
 172 
     | 
    
         
            +
                    if start_dots > bounds.width
         
     | 
| 
      
 173 
     | 
    
         
            +
                      # start_dots appears to be already absolute (larger than column width)
         
     | 
| 
      
 174 
     | 
    
         
            +
                      start_dots_absolute = start_dots
         
     | 
| 
      
 175 
     | 
    
         
            +
                    else
         
     | 
| 
      
 176 
     | 
    
         
            +
                      # start_dots is relative to bounds, convert to absolute
         
     | 
| 
      
 177 
     | 
    
         
            +
                      start_dots_absolute = start_dots + bounds.absolute_left
         
     | 
| 
      
 178 
     | 
    
         
            +
                    end
         
     | 
| 
      
 179 
     | 
    
         
            +
                    
         
     | 
| 
      
 180 
     | 
    
         
            +
                    if defined?(::Prawn::Document::ColumnBox) && bounds.is_a?(::Prawn::Document::ColumnBox)
         
     | 
| 
      
 181 
     | 
    
         
            +
                      # In column context: use UNIVERSAL grid origin (page margin) for alignment across all columns
         
     | 
| 
      
 182 
     | 
    
         
            +
                      # This ensures dots align consistently across left and right columns
         
     | 
| 
      
 183 
     | 
    
         
            +
                      
         
     | 
| 
      
 184 
     | 
    
         
            +
                      # Get column information for debugging
         
     | 
| 
      
 185 
     | 
    
         
            +
                      current_col = bounds.current_column
         
     | 
| 
      
 186 
     | 
    
         
            +
                      parent_bounds = bounds.instance_variable_get(:@parent)
         
     | 
| 
      
 187 
     | 
    
         
            +
                      total_columns = bounds.instance_variable_get(:@columns) || 2
         
     | 
| 
      
 188 
     | 
    
         
            +
                      
         
     | 
| 
      
 189 
     | 
    
         
            +
                      # Calculate the actual column dimensions from the parent
         
     | 
| 
      
 190 
     | 
    
         
            +
                      parent_width = parent_bounds.width
         
     | 
| 
      
 191 
     | 
    
         
            +
                      column_width = parent_width / total_columns
         
     | 
| 
      
 192 
     | 
    
         
            +
                      total_spacer_width = parent_width - (column_width * total_columns)
         
     | 
| 
      
 193 
     | 
    
         
            +
                      spacer_width = total_columns > 1 ? total_spacer_width / (total_columns - 1) : 0
         
     | 
| 
      
 194 
     | 
    
         
            +
                      column_offset = current_col * (column_width + spacer_width)
         
     | 
| 
      
 195 
     | 
    
         
            +
                      
         
     | 
| 
      
 196 
     | 
    
         
            +
                      # UNIVERSAL GRID: Use page margin as grid origin for all columns
         
     | 
| 
      
 197 
     | 
    
         
            +
                      # This ensures dots align across the entire page
         
     | 
| 
      
 198 
     | 
    
         
            +
                      grid_origin = page.margins[:left]
         
     | 
| 
      
 199 
     | 
    
         
            +
                      
         
     | 
| 
      
 200 
     | 
    
         
            +
                      # log :info, %(DOT_LEADER_DEBUG: Column context - current_column=#{current_col}, total_columns=#{total_columns}, column_width=#{column_width}, spacer_width=#{spacer_width}, column_offset=#{column_offset}, bounds_left=#{bounds.absolute_left}, grid_origin=#{grid_origin})
         
     | 
| 
      
 201 
     | 
    
         
            +
                    else
         
     | 
| 
      
 202 
     | 
    
         
            +
                      # In normal context: use the page margin as grid origin to ignore indentation
         
     | 
| 
      
 203 
     | 
    
         
            +
                      # This ensures consistent alignment across all list levels
         
     | 
| 
      
 204 
     | 
    
         
            +
                      grid_origin = page.margins[:left]
         
     | 
| 
      
 205 
     | 
    
         
            +
                      # log :info, %(DOT_LEADER_DEBUG: Normal context - bounds_left=#{bounds.absolute_left}, page_margin=#{page.margins[:left]}, grid_origin=#{grid_origin})
         
     | 
| 
      
 206 
     | 
    
         
            +
                    end
         
     | 
| 
       163 
207 
     | 
    
         | 
| 
       164 
     | 
    
         
            -
                    #  
     | 
| 
       165 
     | 
    
         
            -
                     
     | 
| 
       166 
     | 
    
         
            -
                    # Use a reference from the document's margin settings
         
     | 
| 
       167 
     | 
    
         
            -
                    page_margin_left = page.margins[:left]
         
     | 
| 
      
 208 
     | 
    
         
            +
                    # Calculate the position relative to the grid origin for alignment
         
     | 
| 
      
 209 
     | 
    
         
            +
                    start_dots_from_grid_origin = start_dots_absolute - grid_origin
         
     | 
| 
       168 
210 
     | 
    
         | 
| 
       169 
     | 
    
         
            -
                    #  
     | 
| 
       170 
     | 
    
         
            -
                    start_dots_from_page_margin = start_dots_absolute - page_margin_left
         
     | 
| 
      
 211 
     | 
    
         
            +
                    # log :info, %(DOT_LEADER_DEBUG: Position calculations - start_dots_absolute=#{start_dots_absolute}, start_dots_from_grid_origin=#{start_dots_from_grid_origin})
         
     | 
| 
       171 
212 
     | 
    
         | 
| 
       172 
213 
     | 
    
         
             
                    # Find the first grid position after the left text ends
         
     | 
| 
       173 
     | 
    
         
            -
                    # Grid positions are at multiples of dot_leader[:width] from the  
     | 
| 
       174 
     | 
    
         
            -
                    first_dot_grid_index = ( 
     | 
| 
       175 
     | 
    
         
            -
                    first_dot_position_absolute =  
     | 
| 
      
 214 
     | 
    
         
            +
                    # Grid positions are at multiples of dot_leader[:width] from the grid origin
         
     | 
| 
      
 215 
     | 
    
         
            +
                    first_dot_grid_index = (start_dots_from_grid_origin / dot_leader[:width]).ceil
         
     | 
| 
      
 216 
     | 
    
         
            +
                    first_dot_position_absolute = grid_origin + (first_dot_grid_index * dot_leader[:width])
         
     | 
| 
       176 
217 
     | 
    
         | 
| 
       177 
218 
     | 
    
         
             
                    # Convert back to the local bounds coordinate system for rendering
         
     | 
| 
      
 219 
     | 
    
         
            +
                    # Use universal grid but fix coordinate conversion for right column
         
     | 
| 
       178 
220 
     | 
    
         
             
                    first_dot_position = first_dot_position_absolute - bounds.absolute_left
         
     | 
| 
       179 
221 
     | 
    
         | 
| 
      
 222 
     | 
    
         
            +
                    # Special handling for columns beyond the first (right column in 2-column layout)
         
     | 
| 
      
 223 
     | 
    
         
            +
                    # We need to recalculate using the universal grid to ensure proper alignment
         
     | 
| 
      
 224 
     | 
    
         
            +
                    if defined?(::Prawn::Document::ColumnBox) && bounds.is_a?(::Prawn::Document::ColumnBox)
         
     | 
| 
      
 225 
     | 
    
         
            +
                      current_col = bounds.current_column
         
     | 
| 
      
 226 
     | 
    
         
            +
                      if current_col > 0
         
     | 
| 
      
 227 
     | 
    
         
            +
                        # Right column case: recalculate using universal grid
         
     | 
| 
      
 228 
     | 
    
         
            +
                        # We need to find the first grid position within the column that comes AFTER the left text
         
     | 
| 
      
 229 
     | 
    
         
            +
                      
         
     | 
| 
      
 230 
     | 
    
         
            +
                      # Calculate the left text width to ensure dots start after it
         
     | 
| 
      
 231 
     | 
    
         
            +
                      left_text_width = rendered_width_of_fragments left_side_fragments, scale, node.roles.to_a
         
     | 
| 
      
 232 
     | 
    
         
            +
                      
         
     | 
| 
      
 233 
     | 
    
         
            +
                      # Find the equivalent universal grid position that aligns with other columns
         
     | 
| 
      
 234 
     | 
    
         
            +
                      # Calculate where text ends (without spacer first)
         
     | 
| 
      
 235 
     | 
    
         
            +
                      # Use the absolute left position of the current column plus the text width
         
     | 
| 
      
 236 
     | 
    
         
            +
                      text_end_no_spacer = bounds.absolute_left + left_text_width
         
     | 
| 
      
 237 
     | 
    
         
            +
                      # Add the spacer to get the absolute position where dots should start
         
     | 
| 
      
 238 
     | 
    
         
            +
                      text_end_absolute = text_end_no_spacer + dot_leader[:spacer_width]
         
     | 
| 
      
 239 
     | 
    
         
            +
                      
         
     | 
| 
      
 240 
     | 
    
         
            +
                      # Find the first universal grid position after the text ends
         
     | 
| 
      
 241 
     | 
    
         
            +
                      # This ensures alignment with single column and other contexts
         
     | 
| 
      
 242 
     | 
    
         
            +
                      text_end_from_grid_origin = text_end_absolute - grid_origin
         
     | 
| 
      
 243 
     | 
    
         
            +
                      universal_grid_index = (text_end_from_grid_origin / dot_leader[:width]).ceil
         
     | 
| 
      
 244 
     | 
    
         
            +
                      
         
     | 
| 
      
 245 
     | 
    
         
            +
                      # Check if the previous grid position would still fit (with minimal spacing)
         
     | 
| 
      
 246 
     | 
    
         
            +
                      # This allows us to fit one more dot if the previous grid position comes after the actual text
         
     | 
| 
      
 247 
     | 
    
         
            +
                      if universal_grid_index > 0
         
     | 
| 
      
 248 
     | 
    
         
            +
                        previous_grid_position = grid_origin + ((universal_grid_index - 1) * dot_leader[:width])
         
     | 
| 
      
 249 
     | 
    
         
            +
                        # Check spacing from actual text end (not including the full spacer)
         
     | 
| 
      
 250 
     | 
    
         
            +
                        spacing_from_text = previous_grid_position - text_end_no_spacer
         
     | 
| 
      
 251 
     | 
    
         
            +
                        # Use previous position if it's at least 1pt after the text (minimal spacing)
         
     | 
| 
      
 252 
     | 
    
         
            +
                        if spacing_from_text >= 1.0
         
     | 
| 
      
 253 
     | 
    
         
            +
                          universal_grid_index -= 1
         
     | 
| 
      
 254 
     | 
    
         
            +
                        end
         
     | 
| 
      
 255 
     | 
    
         
            +
                      end
         
     | 
| 
      
 256 
     | 
    
         
            +
                      
         
     | 
| 
      
 257 
     | 
    
         
            +
                      # Calculate the absolute position using the universal grid
         
     | 
| 
      
 258 
     | 
    
         
            +
                      adjusted_position_absolute = grid_origin + (universal_grid_index * dot_leader[:width])
         
     | 
| 
      
 259 
     | 
    
         
            +
                      first_dot_position = adjusted_position_absolute - bounds.absolute_left
         
     | 
| 
      
 260 
     | 
    
         
            +
                      first_dot_position_absolute = adjusted_position_absolute
         
     | 
| 
      
 261 
     | 
    
         
            +
                      # CRITICAL: Update the grid index to match the adjusted position
         
     | 
| 
      
 262 
     | 
    
         
            +
                      first_dot_grid_index = universal_grid_index
         
     | 
| 
      
 263 
     | 
    
         
            +
                      
         
     | 
| 
      
 264 
     | 
    
         
            +
                        # log :info, %(DOT_LEADER_DEBUG: Right column adjustment - left_text_width=#{left_text_width}, text_end_absolute=#{text_end_absolute}, text_end_from_grid_origin=#{text_end_from_grid_origin}, universal_grid_index=#{universal_grid_index}, adjusted_grid_index=#{first_dot_grid_index}, adjusted_position=#{first_dot_position})
         
     | 
| 
      
 265 
     | 
    
         
            +
                      else
         
     | 
| 
      
 266 
     | 
    
         
            +
                        # log :info, %(DOT_LEADER_DEBUG: Left column - no adjustment needed)
         
     | 
| 
      
 267 
     | 
    
         
            +
                      end
         
     | 
| 
      
 268 
     | 
    
         
            +
                    else
         
     | 
| 
      
 269 
     | 
    
         
            +
                      # log :info, %(DOT_LEADER_DEBUG: Standard conversion - first_dot_position_absolute=#{first_dot_position_absolute}, bounds.absolute_left=#{bounds.absolute_left}, first_dot_position=#{first_dot_position})
         
     | 
| 
      
 270 
     | 
    
         
            +
                    end
         
     | 
| 
      
 271 
     | 
    
         
            +
                    
         
     | 
| 
      
 272 
     | 
    
         
            +
                    # log :info, %(DOT_LEADER_DEBUG: First dot - grid_index=#{first_dot_grid_index}, position_absolute=#{first_dot_position_absolute}, position_local=#{first_dot_position})
         
     | 
| 
      
 273 
     | 
    
         
            +
                    
         
     | 
| 
       180 
274 
     | 
    
         
             
                    # Check if the first dot is too close to the left text
         
     | 
| 
       181 
275 
     | 
    
         
             
                    # Calculate the actual left text width for precise spacing check
         
     | 
| 
       182 
276 
     | 
    
         
             
                    left_text_width = rendered_width_of_fragments left_side_fragments, scale, node.roles.to_a
         
     | 
| 
         @@ -187,26 +281,33 @@ class DotLeader < (Asciidoctor::Converter.for 'pdf') 
     | 
|
| 
       187 
281 
     | 
    
         | 
| 
       188 
282 
     | 
    
         
             
                    # Calculate where the right text will start (in absolute coordinates)
         
     | 
| 
       189 
283 
     | 
    
         
             
                    right_text_start_local = bounds.width - right_side_width - dot_leader[:spacer_width]
         
     | 
| 
      
 284 
     | 
    
         
            +
                    
         
     | 
| 
      
 285 
     | 
    
         
            +
                    # Convert to absolute coordinates: bounds.absolute_left already accounts for column position
         
     | 
| 
       190 
286 
     | 
    
         
             
                    right_text_start_absolute = right_text_start_local + bounds.absolute_left
         
     | 
| 
       191 
287 
     | 
    
         | 
| 
       192 
     | 
    
         
            -
                    # Find the last grid position before the right text starts (using  
     | 
| 
      
 288 
     | 
    
         
            +
                    # Find the last grid position before the right text starts (using the same grid origin)
         
     | 
| 
       193 
289 
     | 
    
         
             
                    # Ensure the COMPLETE dot (start + width) fits before right text
         
     | 
| 
       194 
     | 
    
         
            -
                     
     | 
| 
      
 290 
     | 
    
         
            +
                    right_text_from_grid_origin = right_text_start_absolute - grid_origin
         
     | 
| 
       195 
291 
     | 
    
         | 
| 
       196 
292 
     | 
    
         
             
                    # Calculate the last grid index where a complete dot would fit
         
     | 
| 
       197 
293 
     | 
    
         
             
                    # A dot at position N has its right edge at N + dot_leader[:width]
         
     | 
| 
       198 
     | 
    
         
            -
                    # The target: (last_dot_grid_index * dot_leader[:width]) + dot_leader[:width] <=  
     | 
| 
       199 
     | 
    
         
            -
                    # Simplified: last_dot_grid_index <= ( 
     | 
| 
      
 294 
     | 
    
         
            +
                    # The target: (last_dot_grid_index * dot_leader[:width]) + dot_leader[:width] <= right_text_from_grid_origin
         
     | 
| 
      
 295 
     | 
    
         
            +
                    # Simplified: last_dot_grid_index <= (right_text_from_grid_origin / dot_leader[:width]) - 1
         
     | 
| 
       200 
296 
     | 
    
         
             
                    # Add some buffer space to ensure the dot does not touch the right text
         
     | 
| 
       201 
     | 
    
         
            -
                    last_possible_dot_end =  
     | 
| 
      
 297 
     | 
    
         
            +
                    last_possible_dot_end = right_text_from_grid_origin - (1.0 * dot_stripped_width)
         
     | 
| 
       202 
298 
     | 
    
         
             
                    last_dot_grid_index = (last_possible_dot_end / dot_leader[:width]).floor
         
     | 
| 
       203 
299 
     | 
    
         | 
| 
      
 300 
     | 
    
         
            +
                    # log :info, %(DOT_LEADER_DEBUG: Right text - start_local=#{right_text_start_local}, start_absolute=#{right_text_start_absolute}, from_grid_origin=#{right_text_from_grid_origin})
         
     | 
| 
      
 301 
     | 
    
         
            +
                    
         
     | 
| 
       204 
302 
     | 
    
         
             
                    # Calculate the number of dots that fit on the grid between left and right text
         
     | 
| 
       205 
303 
     | 
    
         
             
                    num_dots = [last_dot_grid_index - first_dot_grid_index + 1, 0].max
         
     | 
| 
       206 
     | 
    
         
            -
                    
         
     | 
| 
      
 304 
     | 
    
         
            +
                    # log :info, %(DOT_LEADER_DEBUG: Last dot - last_possible_end=#{last_possible_dot_end}, last_grid_index=#{last_dot_grid_index}, num_dots=#{num_dots})
         
     | 
| 
      
 305 
     | 
    
         
            +
             
     | 
| 
       207 
306 
     | 
    
         
             
                    # Calculate the indent to position the first dot at the correct grid position
         
     | 
| 
       208 
307 
     | 
    
         
             
                    dot_indent = first_dot_position
         
     | 
| 
       209 
     | 
    
         
            -
             
     | 
| 
      
 308 
     | 
    
         
            +
             
     | 
| 
      
 309 
     | 
    
         
            +
                    # log :info, %(DOT_LEADER_DEBUG: Final render - dot_indent=#{dot_indent}, num_dots=#{num_dots}, dot_text="#{dot_leader[:text]}")
         
     | 
| 
      
 310 
     | 
    
         
            +
             
     | 
| 
       210 
311 
     | 
    
         
             
                    fragment_positions = []
         
     | 
| 
       211 
312 
     | 
    
         
             
                    right_side_fragments.each do |fragment|
         
     | 
| 
       212 
313 
     | 
    
         
             
                      fragment_positions << (fragment_position = ::Asciidoctor::PDF::FormattedText::FragmentPositionRenderer.new)
         
     | 
| 
         @@ -214,10 +315,15 @@ class DotLeader < (Asciidoctor::Converter.for 'pdf') 
     | 
|
| 
       214 
315 
     | 
    
         
             
                    end
         
     | 
| 
       215 
316 
     | 
    
         | 
| 
       216 
317 
     | 
    
         
             
                    # Render the dots with a proper indent to align to the grid
         
     | 
| 
       217 
     | 
    
         
            -
                     
     | 
| 
       218 
     | 
    
         
            -
                       
     | 
| 
       219 
     | 
    
         
            -
                         
     | 
| 
       220 
     | 
    
         
            -
             
     | 
| 
      
 318 
     | 
    
         
            +
                    if num_dots > 0
         
     | 
| 
      
 319 
     | 
    
         
            +
                      indent dot_indent, 0 do
         
     | 
| 
      
 320 
     | 
    
         
            +
                        typeset_formatted_text [
         
     | 
| 
      
 321 
     | 
    
         
            +
                          { text: dot_leader[:text] * num_dots, color: dot_leader[:font_color] }
         
     | 
| 
      
 322 
     | 
    
         
            +
                        ], line_metrics, align: :left
         
     | 
| 
      
 323 
     | 
    
         
            +
                      end
         
     | 
| 
      
 324 
     | 
    
         
            +
                      # log :info, %(DOT_LEADER_DEBUG: Dots rendered successfully)
         
     | 
| 
      
 325 
     | 
    
         
            +
                    else
         
     | 
| 
      
 326 
     | 
    
         
            +
                      log :warn, %(DOT_LEADER_DEBUG: No dots to render - num_dots=#{num_dots})
         
     | 
| 
       221 
327 
     | 
    
         
             
                    end
         
     | 
| 
       222 
328 
     | 
    
         | 
| 
       223 
329 
     | 
    
         
             
                    # Move cursor back to render right text
         
     | 
| 
         @@ -237,12 +343,13 @@ class DotLeader < (Asciidoctor::Converter.for 'pdf') 
     | 
|
| 
       237 
343 
     | 
    
         
             
                  ], line_metrics, align: :right
         
     | 
| 
       238 
344 
     | 
    
         
             
                  end
         
     | 
| 
       239 
345 
     | 
    
         
             
                else
         
     | 
| 
      
 346 
     | 
    
         
            +
                  # log :info, %(DOT_LEADER_DEBUG: Skipping dot rendering - width=#{dot_leader[:width]}, levels_check=#{dot_leader[:levels] ? (dot_leader[:levels].include? entry_level.pred) : true})
         
     | 
| 
       240 
347 
     | 
    
         
             
                  typeset_formatted_text [{ text: right_side, color: @font_color, anchor: entry_anchor }], line_metrics, align: :right
         
     | 
| 
       241 
348 
     | 
    
         
             
                end
         
     | 
| 
       242 
349 
     | 
    
         
             
                move_cursor_to end_cursor
         
     | 
| 
       243 
350 
     | 
    
         | 
| 
      
 351 
     | 
    
         
            +
                # log :info, %(DOT_LEADER_DEBUG: Completed - final_cursor=#{cursor})
         
     | 
| 
       244 
352 
     | 
    
         
             
                # Return nil to avoid numbers being printed out
         
     | 
| 
       245 
353 
     | 
    
         
             
                nil
         
     | 
| 
       246 
     | 
    
         
            -
             
     | 
| 
       247 
354 
     | 
    
         
             
              end
         
     | 
| 
       248 
     | 
    
         
            -
            end
         
     | 
| 
      
 355 
     | 
    
         
            +
            end
         
     |