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
|