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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cafcd588c3080dba97a8b811c9a230f83623fa27e1d1449f471d6f93428ef6f9
4
- data.tar.gz: 2b784579fa0453fef3dc0fd1d1d735e31889f9dce3227294225da08064de3300
3
+ metadata.gz: 2382e42c9d8e11c7427f3758ea61f111d4fa390fe99a6daf4c0f4eab46860f64
4
+ data.tar.gz: 7c518c228291a4be4c236c9848f2e97a0e2d4ea94e617efc7bd79e4667ea14ba
5
5
  SHA512:
6
- metadata.gz: 78bb767ee1ea9560346744da1285c95ba72f26f8787f6843b10e046d9ccc575ef607c29d556bc2c7f53ff4bc0b38ed965aa93ab6164d9a597cc811fe1e6de34f
7
- data.tar.gz: c7c1e40de2398e37458e1da098fa669b2fb8881a2505ad821a0fbe75d65f0a1591661d9f0333d79ab0e877d09c3a18314a6a590d0e2c9ba3861bf546480c80cc
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 `backen-webview-html5`.
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
- warn "WARN: dot-leader: The right side too wide to fit on a line; skipping: #{left_side} #{right_side}"
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
- # Calculate the absolute position from page margin for universal grid alignment
158
- # This ensures dots align across tables, lists, and other indented contexts
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
- # Get the absolute position where dots will start
161
- # start_dots is in local bounds coordinates, convert to absolute page coordinates
162
- start_dots_absolute = start_dots + bounds.absolute_left
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
- # Use the page's left margin as the grid origin for universal alignment
165
- # In a normal flow at the top level, bounds.absolute_left equals the page margin
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
- # Calculate the position relative to the page margin for grid alignment
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 page margin
174
- first_dot_grid_index = (start_dots_from_page_margin / dot_leader[:width]).ceil
175
- first_dot_position_absolute = page_margin_left + (first_dot_grid_index * dot_leader[:width])
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 global grid)
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
- right_text_from_page_margin = right_text_start_absolute - page_margin_left
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] <= right_text_from_page_margin
199
- # Simplified: last_dot_grid_index <= (right_text_from_page_margin / dot_leader[:width]) - 1
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 = right_text_from_page_margin - (1.0 * dot_stripped_width)
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
- indent dot_indent, 0 do
218
- typeset_formatted_text [
219
- { text: dot_leader[:text] * num_dots, color: dot_leader[:font_color] }
220
- ], line_metrics, align: :left
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asciidoctor-dot-leader
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 白一百 baiyibai