asciidoctor-lists-extended 1.0.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 +7 -0
- data/LICENSE +21 -0
- data/README.adoc +424 -0
- data/lib/asciidoctor-lists-extended/extensions.rb +91 -0
- data/lib/asciidoctor-lists-extended/html_renderer.rb +90 -0
- data/lib/asciidoctor-lists-extended/pdf_converter.rb +395 -0
- data/lib/asciidoctor-lists-extended/pdf_renderer.rb +167 -0
- data/lib/asciidoctor-lists-extended.rb +22 -0
- metadata +67 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 763b4829cf12a23c7ae9c993f9f10fc394919af9adb8c4e5eaf34f82a017ce76
|
|
4
|
+
data.tar.gz: 1a22a3265585f57715f25d0f14f8d3c44889eb9efd9976698dd7aa42b760dc46
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3260b2cbe8a43684258c6a2a9e33f0e236b01453e99dc4cec4dcf967f15011c1f8f96daf833160d7e24d18593838d6752271050b22fc53a6f8edfcd58ccc77bd
|
|
7
|
+
data.tar.gz: f42056096abc9bd690b97abbffcc40ee61ad3dc8f64b4f041eec42ec5fbdf87de9c61e5605cdf608eb4e73b5e0105ec0ce04c74ff79e99e70ffe290035f4c3cf
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 白一百 baiyibai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.adoc
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
= asciidoctor-lists-extended
|
|
2
|
+
:toc: left
|
|
3
|
+
:toclevels: 2
|
|
4
|
+
:source-highlighter: rouge
|
|
5
|
+
|
|
6
|
+
A converter-aware asciidoctor extension that generates a *List of Figures*, *List of Tables*, *List of Listings*, and *List of Examples* — or any custom captioned block type.
|
|
7
|
+
|
|
8
|
+
Replaces and supersedes `asciidoctor-pdf-lofte`.
|
|
9
|
+
Compatible with the `asciidoctor-lists` macro syntax.
|
|
10
|
+
|
|
11
|
+
== Features
|
|
12
|
+
|
|
13
|
+
* Works with *HTML5* (xref links) and *asciidoctor-pdf* (dot-leader style matching the ToC)
|
|
14
|
+
* *No hardcoded `[#anchor]` required* — IDs are auto-generated via `SecureRandom.uuid`
|
|
15
|
+
* *Any element type* — image, table, listing, example, or custom block contexts
|
|
16
|
+
* *ToC integration* — lists optionally appear in the PDF Table of Contents and bookmark outline
|
|
17
|
+
* *Compatible with `asciidoctor-lists`* — same `list-of::element[]` macro syntax
|
|
18
|
+
|
|
19
|
+
== Installation
|
|
20
|
+
|
|
21
|
+
Add to your `Gemfile`:
|
|
22
|
+
|
|
23
|
+
[source,ruby]
|
|
24
|
+
----
|
|
25
|
+
gem 'asciidoctor-lists-extended', path: '/path/to/asciidoctor-lists-extended'
|
|
26
|
+
----
|
|
27
|
+
|
|
28
|
+
Or require directly:
|
|
29
|
+
|
|
30
|
+
[source,bash]
|
|
31
|
+
----
|
|
32
|
+
asciidoctor-pdf -r /path/to/lib/asciidoctor-lists-extended.rb document.adoc
|
|
33
|
+
----
|
|
34
|
+
|
|
35
|
+
For HTML5 only (no `asciidoctor-pdf` needed):
|
|
36
|
+
|
|
37
|
+
[source,bash]
|
|
38
|
+
----
|
|
39
|
+
asciidoctor -r /path/to/lib/asciidoctor-lists-extended.rb document.adoc
|
|
40
|
+
----
|
|
41
|
+
|
|
42
|
+
== Quick Start
|
|
43
|
+
|
|
44
|
+
[source,asciidoc]
|
|
45
|
+
----
|
|
46
|
+
= My Document
|
|
47
|
+
:toc:
|
|
48
|
+
:doctype: book
|
|
49
|
+
|
|
50
|
+
\== List of Figures <1>
|
|
51
|
+
list-of::image[] <2>
|
|
52
|
+
|
|
53
|
+
\== List of Tables
|
|
54
|
+
list-of::table[]
|
|
55
|
+
|
|
56
|
+
\== Chapter 1
|
|
57
|
+
|
|
58
|
+
.My Architecture Diagram <3>
|
|
59
|
+
image::arch.png[]
|
|
60
|
+
|
|
61
|
+
.Configuration Parameters
|
|
62
|
+
|===
|
|
63
|
+
|Name |Value
|
|
64
|
+
|timeout |30
|
|
65
|
+
|===
|
|
66
|
+
----
|
|
67
|
+
<1> The section heading becomes the title in the PDF front matter.
|
|
68
|
+
<2> The macro generates the list. In PDF mode the section is consumed and moved to the front matter; in HTML mode it is rendered inline.
|
|
69
|
+
<3> No `[#anchor]` needed — IDs are assigned automatically.
|
|
70
|
+
|
|
71
|
+
== Macro Syntax
|
|
72
|
+
|
|
73
|
+
[source,asciidoc]
|
|
74
|
+
----
|
|
75
|
+
list-of::<element>[]
|
|
76
|
+
list-of::<element>[<positional-options>,key=value,...]
|
|
77
|
+
----
|
|
78
|
+
|
|
79
|
+
=== Positional Options
|
|
80
|
+
|
|
81
|
+
[cols="1,3"]
|
|
82
|
+
|===
|
|
83
|
+
|Option |Effect
|
|
84
|
+
|
|
85
|
+
|`enhanced_rendering`
|
|
86
|
+
|In HTML mode, renders caption and title on separate spans rather than concatenated.
|
|
87
|
+
|
|
88
|
+
|`hide_empty_section`
|
|
89
|
+
|Removes the containing section entirely if no captioned/titled elements of the requested type exist in the document.
|
|
90
|
+
In HTML mode, omitting this flag leaves an orphaned section heading with no content, which is rarely useful.
|
|
91
|
+
Kept as an explicit opt-in for backwards compatibility with the original `asciidoctor-lists` gem.
|
|
92
|
+
|
|
93
|
+
|`exclude_from_toc`
|
|
94
|
+
|PDF only. Omits this list from the PDF Table of Contents while keeping it in the bookmark outline.
|
|
95
|
+
|
|
96
|
+
|`exclude_from_outline`
|
|
97
|
+
|PDF only. Omits this list from the PDF bookmark outline while keeping it in the Table of Contents.
|
|
98
|
+
|===
|
|
99
|
+
|
|
100
|
+
=== Named Parameters
|
|
101
|
+
|
|
102
|
+
[cols="1,1,3"]
|
|
103
|
+
|===
|
|
104
|
+
|Parameter |Backend |Description
|
|
105
|
+
|
|
106
|
+
|`title`
|
|
107
|
+
|Both
|
|
108
|
+
|Override the list heading. When set, takes precedence over the parent section title.
|
|
109
|
+
|
|
110
|
+
|`caption_prefix`
|
|
111
|
+
|Both
|
|
112
|
+
|Override the caption prefix label (e.g. `"Figure"`). Currently informational; caption is taken from the element's own `captioned_title`.
|
|
113
|
+
|===
|
|
114
|
+
|
|
115
|
+
=== Supported Element Types
|
|
116
|
+
|
|
117
|
+
Any valid Asciidoctor block context string works:
|
|
118
|
+
|
|
119
|
+
[cols="1,2"]
|
|
120
|
+
|===
|
|
121
|
+
|Macro |Collects
|
|
122
|
+
|
|
123
|
+
|`list-of::image[]`
|
|
124
|
+
|Blocks with `context: :image` that have a title or caption.
|
|
125
|
+
|
|
126
|
+
|`list-of::table[]`
|
|
127
|
+
|Blocks with `context: :table` that have a title or caption.
|
|
128
|
+
|
|
129
|
+
|`list-of::listing[]`
|
|
130
|
+
|Blocks with `context: :listing` (source code, literal) that have a title or caption.
|
|
131
|
+
|
|
132
|
+
|`list-of::example[]`
|
|
133
|
+
|Blocks with `context: :example` that have a title or caption.
|
|
134
|
+
|
|
135
|
+
|`list-of::video[]`
|
|
136
|
+
|Any custom block type registered with that context name.
|
|
137
|
+
|===
|
|
138
|
+
|
|
139
|
+
Elements *without* a title or caption are silently skipped.
|
|
140
|
+
|
|
141
|
+
== Document Attributes
|
|
142
|
+
|
|
143
|
+
=== Caption Attributes (standard Asciidoctor)
|
|
144
|
+
|
|
145
|
+
[source,asciidoc]
|
|
146
|
+
----
|
|
147
|
+
:figure-caption: Figure
|
|
148
|
+
:table-caption: Table
|
|
149
|
+
:listing-caption: Listing
|
|
150
|
+
:example-caption: Example
|
|
151
|
+
----
|
|
152
|
+
|
|
153
|
+
These control the prefix that appears in each list entry, e.g. `Figure 1. My Diagram`.
|
|
154
|
+
|
|
155
|
+
== PDF Behaviour
|
|
156
|
+
|
|
157
|
+
In PDF mode the extension hooks into `asciidoctor-pdf`'s ToC allocation/rendering lifecycle:
|
|
158
|
+
|
|
159
|
+
. *`allocate_toc`* — dry-runs each list to measure required page space, then reserves those pages immediately after the ToC.
|
|
160
|
+
. *`ink_toc`* — renders each list with ToC-style dot leaders and page numbers.
|
|
161
|
+
. The `== List of Figures` section and its `list-of::` macro are *removed from the document body* so they do not appear again as body text.
|
|
162
|
+
|
|
163
|
+
=== Placement
|
|
164
|
+
|
|
165
|
+
All lists are placed in the *front matter* (directly after the ToC) regardless of where the `list-of::` macros appear in the source.
|
|
166
|
+
The macro order in the source determines the order of the lists in the front matter.
|
|
167
|
+
|
|
168
|
+
You can place additional `list-of::` macros inside chapter subsections to generate *chapter-scoped* lists.
|
|
169
|
+
The scope is determined automatically from where the macro appears in the document hierarchy:
|
|
170
|
+
|
|
171
|
+
[cols="1,2"]
|
|
172
|
+
|===
|
|
173
|
+
|Macro placement |Collection scope
|
|
174
|
+
|
|
175
|
+
|Inside a top-level section (`== …`) +
|
|
176
|
+
e.g. a front-matter `== List of Figures`
|
|
177
|
+
|*Document-wide* — collects all matching elements in the entire document.
|
|
178
|
+
|
|
179
|
+
|Inside a subsection (`=== …`) nested within a chapter +
|
|
180
|
+
e.g. `=== Chapter 1 Figures` inside `== Chapter 1`
|
|
181
|
+
|*Chapter-scoped* — collects only elements from that chapter and its child sections.
|
|
182
|
+
|===
|
|
183
|
+
|
|
184
|
+
.Example: global list plus a per-chapter list
|
|
185
|
+
[source,asciidoc]
|
|
186
|
+
----
|
|
187
|
+
\== List of Figures <1>
|
|
188
|
+
list-of::image[]
|
|
189
|
+
|
|
190
|
+
\== Chapter 1
|
|
191
|
+
|
|
192
|
+
.Overview Diagram
|
|
193
|
+
image::arch.png[]
|
|
194
|
+
|
|
195
|
+
\=== Chapter 1 Figures <2>
|
|
196
|
+
list-of::image[]
|
|
197
|
+
|
|
198
|
+
.Detailed Flow
|
|
199
|
+
image::flow.png[]
|
|
200
|
+
----
|
|
201
|
+
<1> Document-wide: collects all images.
|
|
202
|
+
<2> Chapter-scoped: collects only Chapter 1's images.
|
|
203
|
+
|
|
204
|
+
=== Styling
|
|
205
|
+
|
|
206
|
+
Lists inherit all ToC styling from your PDF theme:
|
|
207
|
+
|
|
208
|
+
[source,yaml]
|
|
209
|
+
----
|
|
210
|
+
toc:
|
|
211
|
+
font_size: 10
|
|
212
|
+
line_height: 1.5
|
|
213
|
+
dot_leader:
|
|
214
|
+
font_color: '#CCCCCC'
|
|
215
|
+
content: '. '
|
|
216
|
+
levels: all
|
|
217
|
+
----
|
|
218
|
+
|
|
219
|
+
You can add a list-specific title style with a theme key based on the element type:
|
|
220
|
+
|
|
221
|
+
[source,yaml]
|
|
222
|
+
----
|
|
223
|
+
image_list_title:
|
|
224
|
+
font_size: 16
|
|
225
|
+
font_style: bold
|
|
226
|
+
text_align: center
|
|
227
|
+
----
|
|
228
|
+
|
|
229
|
+
== HTML Behaviour
|
|
230
|
+
|
|
231
|
+
In HTML5 mode the macro is replaced *in-place* with a list of cross-reference links:
|
|
232
|
+
|
|
233
|
+
[source,html]
|
|
234
|
+
----
|
|
235
|
+
<p><a href="#uuid-of-figure-1">Figure 1.</a> Architecture Diagram<br>
|
|
236
|
+
<a href="#uuid-of-figure-2">Figure 2.</a> Data Model<br></p>
|
|
237
|
+
----
|
|
238
|
+
|
|
239
|
+
The containing section and heading are rendered normally.
|
|
240
|
+
|
|
241
|
+
== Full Example
|
|
242
|
+
|
|
243
|
+
[source,asciidoc]
|
|
244
|
+
----
|
|
245
|
+
= Technical Reference
|
|
246
|
+
:doctype: book
|
|
247
|
+
:toc:
|
|
248
|
+
:toclevels: 3
|
|
249
|
+
:figure-caption: Figure
|
|
250
|
+
:table-caption: Table
|
|
251
|
+
:listing-caption: Listing
|
|
252
|
+
|
|
253
|
+
\== List of Figures
|
|
254
|
+
list-of::image[] <1>
|
|
255
|
+
|
|
256
|
+
\== List of Tables
|
|
257
|
+
list-of::table[hide_empty_section] <2>
|
|
258
|
+
|
|
259
|
+
\== List of Code Examples
|
|
260
|
+
list-of::listing[title="Code Examples"] <3>
|
|
261
|
+
|
|
262
|
+
\== List of Appendix Examples
|
|
263
|
+
list-of::example[exclude_from_outline] <4>
|
|
264
|
+
|
|
265
|
+
\== Chapter 1: Architecture
|
|
266
|
+
|
|
267
|
+
.System Overview
|
|
268
|
+
image::arch.png[]
|
|
269
|
+
|
|
270
|
+
.Configuration Parameters
|
|
271
|
+
|===
|
|
272
|
+
|Name |Default |Description
|
|
273
|
+
|timeout |30 |Seconds
|
|
274
|
+
|===
|
|
275
|
+
|
|
276
|
+
.bootstrap.sh
|
|
277
|
+
[source,bash]
|
|
278
|
+
----
|
|
279
|
+
#!/bin/bash
|
|
280
|
+
echo "Starting..."
|
|
281
|
+
----
|
|
282
|
+
----
|
|
283
|
+
<1> Plain usage — collects all images with captions. Appears in PDF ToC and outline by default.
|
|
284
|
+
<2> If no tables exist, the "List of Tables" section is omitted entirely.
|
|
285
|
+
<3> Per-list title override.
|
|
286
|
+
<4> Appears in the PDF Table of Contents but not in the PDF bookmark outline.
|
|
287
|
+
|
|
288
|
+
== Running the Tests
|
|
289
|
+
|
|
290
|
+
=== HTML test (requires only `asciidoctor` gem):
|
|
291
|
+
|
|
292
|
+
[source,bash]
|
|
293
|
+
----
|
|
294
|
+
ruby test/run_html.rb
|
|
295
|
+
----
|
|
296
|
+
|
|
297
|
+
=== PDF test (requires `asciidoctor-pdf` and its dependencies):
|
|
298
|
+
|
|
299
|
+
[source,bash]
|
|
300
|
+
----
|
|
301
|
+
ruby test/run_pdf.rb
|
|
302
|
+
----
|
|
303
|
+
|
|
304
|
+
Output files are written to `test/out/`.
|
|
305
|
+
All six test documents are rendered by both runners: every document produces both an HTML and a PDF output.
|
|
306
|
+
|
|
307
|
+
=== VSCode AsciiDoc preview (HTML5)
|
|
308
|
+
|
|
309
|
+
To use the extension with the https://marketplace.visualstudio.com/items?itemName=asciidoctor.asciidoctor-vscode[AsciiDoc extension for VS Code] preview:
|
|
310
|
+
|
|
311
|
+
. A `.asciidoctor/lib/lists-extended.js` linker file is included in this repository — no setup needed.
|
|
312
|
+
. A `.vscode/settings.json` is also included that enables workspace extensions:
|
|
313
|
+
+
|
|
314
|
+
[source,json]
|
|
315
|
+
----
|
|
316
|
+
{
|
|
317
|
+
"asciidoc.extensions.registerWorkspaceExtensions": true
|
|
318
|
+
}
|
|
319
|
+
----
|
|
320
|
+
+
|
|
321
|
+
If you already have a `.vscode/settings.json`, add the key manually.
|
|
322
|
+
. Open any `.adoc` file and use the AsciiDoc preview button (or kbd:[Ctrl+Shift+V]).
|
|
323
|
+
|
|
324
|
+
The preview uses the JS extension (`js/lib/extension.js`) via Asciidoctor.js.
|
|
325
|
+
The `list-of::` macros render as `<ul>` lists with `<a href="#…">` links.
|
|
326
|
+
|
|
327
|
+
== Migration from asciidoctor-pdf-lofte
|
|
328
|
+
|
|
329
|
+
[cols="1,1"]
|
|
330
|
+
|===
|
|
331
|
+
|Old (lofte) |New (lists-extended)
|
|
332
|
+
|
|
333
|
+
|`:lof-title: List of Figures` +
|
|
334
|
+
`:lot-title: List of Tables`
|
|
335
|
+
|`\== List of Figures` +
|
|
336
|
+
`list-of::image[]` +
|
|
337
|
+
+
|
|
338
|
+
`\== List of Tables` +
|
|
339
|
+
`list-of::table[]`
|
|
340
|
+
|
|
341
|
+
|`:include-lists-in-toc:`
|
|
342
|
+
|Lists are included in the PDF ToC and bookmark outline by default.
|
|
343
|
+
Use `exclude_from_toc` or `exclude_from_outline` on individual macros to opt out.
|
|
344
|
+
|
|
345
|
+
|Required `[#anchor]` on every block
|
|
346
|
+
|Auto-generated — no anchors needed
|
|
347
|
+
|
|
348
|
+
|PDF only
|
|
349
|
+
|HTML5 + PDF
|
|
350
|
+
|
|
351
|
+
|4 copy-pasted converter classes (~1200 lines)
|
|
352
|
+
|1 unified converter class (~320 lines)
|
|
353
|
+
|===
|
|
354
|
+
|
|
355
|
+
== Architecture Overview
|
|
356
|
+
|
|
357
|
+
[source]
|
|
358
|
+
----
|
|
359
|
+
lib/
|
|
360
|
+
asciidoctor-lists-extended.rb Main entry point (conditional PDF loading)
|
|
361
|
+
asciidoctor-lists-extended/
|
|
362
|
+
extensions.rb ListMacro + ListTreeprocessor
|
|
363
|
+
html_renderer.rb UUID → xref replacement for HTML5
|
|
364
|
+
pdf_renderer.rb Drawing primitives (ink_list_content, etc.)
|
|
365
|
+
pdf_converter.rb Lifecycle orchestration (allocate_toc, ink_toc)
|
|
366
|
+
----
|
|
367
|
+
|
|
368
|
+
=== Why pdf_converter.rb and pdf_renderer.rb are separate
|
|
369
|
+
|
|
370
|
+
`pdf_converter.rb` subclasses the asciidoctor-pdf converter and overrides the lifecycle hooks that the PDF engine calls during document generation.
|
|
371
|
+
It is responsible for *orchestration*: when to run, which pages to reserve, and in what order to render each list.
|
|
372
|
+
|
|
373
|
+
`pdf_renderer.rb` is a module mixed into the converter.
|
|
374
|
+
It contains the *drawing primitives*: how to render a heading, how to draw a dot-leader row, how to build the dot-leader configuration from the PDF theme.
|
|
375
|
+
|
|
376
|
+
The split exists because `ink_list_content` (the method that draws one complete list section) is called from two different places — once inside a dry run to measure page space, and once during real rendering to ink the output.
|
|
377
|
+
Keeping the drawing logic in a separate mixin makes that reuse clean: the converter orchestrates, the renderer draws, and neither knows the other's internal details.
|
|
378
|
+
|
|
379
|
+
=== Process flow
|
|
380
|
+
|
|
381
|
+
==== HTML backend
|
|
382
|
+
|
|
383
|
+
. *Macro registration* (`extensions.rb`) — when Asciidoctor parses a `list-of::element[]` macro, `ListMacro#process` runs.
|
|
384
|
+
It stores the macro options in a global hash keyed by a UUID and emits a plain paragraph containing only that UUID as a placeholder in the document AST.
|
|
385
|
+
|
|
386
|
+
. *Tree processing* (`extensions.rb`) — after the full AST is built, `ListTreeprocessor#process` runs.
|
|
387
|
+
It first auto-generates IDs for every captioned/titled element referenced by any `list-of::` macro (so manual `[#anchor]` blocks are not needed).
|
|
388
|
+
It then delegates to `HtmlRenderer#render`.
|
|
389
|
+
|
|
390
|
+
. *UUID replacement* (`html_renderer.rb`) — `HtmlRenderer` finds every UUID placeholder paragraph, looks up its config, collects the matching elements, and replaces the placeholder with xref links.
|
|
391
|
+
If `hide_empty_section` is set and no entries exist, it removes the enclosing section entirely.
|
|
392
|
+
|
|
393
|
+
==== PDF backend
|
|
394
|
+
|
|
395
|
+
. *Macro registration* — identical to HTML: UUID placeholder paragraph is inserted into the AST.
|
|
396
|
+
|
|
397
|
+
. *Tree processing* — auto-generates IDs for referenced elements, then returns early (the placeholder paragraphs are left in place for the PDF converter to find).
|
|
398
|
+
|
|
399
|
+
. *`allocate_toc`* (`pdf_converter.rb`) — called by asciidoctor-pdf before any body rendering, after the ToC pages are reserved.
|
|
400
|
+
`PDFConverterWithLists` calls `super` first (reserving the real ToC), then iterates over every UUID placeholder.
|
|
401
|
+
For each one it captures the title of the enclosing `==` section, removes that section from the body AST (so it does not appear again as body text), then dry-runs `ink_list_content` to measure how many pages the list needs and reserves that space.
|
|
402
|
+
|
|
403
|
+
. *`ink_toc`* (`pdf_converter.rb`) — called by asciidoctor-pdf when it is time to render the front matter.
|
|
404
|
+
For each allocated list, `ink_list` navigates to the reserved pages and calls `ink_list_content`.
|
|
405
|
+
A virtual `Section` node is always inserted into the document AST.
|
|
406
|
+
If `exclude_from_toc` is set, the section is marked `list-exclude-from-toc` so the `get_entries_for_toc` override hides it from the visible ToC — but it remains in `doc.sections`, so the PDF bookmark outline still sees it.
|
|
407
|
+
If `exclude_from_outline` is set, the section is marked `list-exclude-from-outline` so the `add_outline_level` override filters it from the bookmark tree.
|
|
408
|
+
After all lists are rendered, `super` runs to render the real Table of Contents.
|
|
409
|
+
|
|
410
|
+
. *`ink_list_content`* (`pdf_renderer.rb`) — draws the heading (using the `==` section title captured in step 3) and then calls `ink_toc_level` to draw the dot-leader rows.
|
|
411
|
+
|
|
412
|
+
. *`ink_toc_level` override* (`pdf_converter.rb`) — when `@rendering_list` is true (set only during list rendering), dispatches to `ink_list_toc_level` which uses `captioned_title` (e.g. "Figure 1. My Diagram") for non-section entries instead of the bare title.
|
|
413
|
+
For real ToC rendering the override is bypassed and `super` is called unchanged.
|
|
414
|
+
|
|
415
|
+
. *`get_entries_for_toc` override* (`pdf_converter.rb`) — called by asciidoctor-pdf when collecting sections to render in the Table of Contents.
|
|
416
|
+
Filters out virtual list sections marked with `list-exclude-from-toc` before delegating to `super`.
|
|
417
|
+
Because the PDF outline builder reads `doc.sections` directly (not via this method), excluded sections remain visible in the bookmark outline.
|
|
418
|
+
|
|
419
|
+
. *`add_outline_level` override* (`pdf_converter.rb`) — called by asciidoctor-pdf when building the PDF bookmark outline.
|
|
420
|
+
Filters out any virtual list sections marked with `list-exclude-from-outline` before delegating to `super`, implementing the `exclude_from_outline` flag.
|
|
421
|
+
|
|
422
|
+
== Licence
|
|
423
|
+
|
|
424
|
+
MIT
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'asciidoctor'
|
|
4
|
+
require 'asciidoctor/extensions'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
|
|
7
|
+
module Asciidoctor
|
|
8
|
+
module ListsExtended
|
|
9
|
+
# Global hash storing macro configurations, keyed by UUID placeholder string.
|
|
10
|
+
# Ruby Hash preserves insertion order since 1.9, so document order of
|
|
11
|
+
# list-of:: macros is maintained automatically.
|
|
12
|
+
ListMacroAttributes = Hash.new
|
|
13
|
+
|
|
14
|
+
# BlockMacroProcessor for the list-of::element[] syntax.
|
|
15
|
+
#
|
|
16
|
+
# Supported parameters:
|
|
17
|
+
# enhanced_rendering — separate caption and title display (positional)
|
|
18
|
+
# hide_empty_section — remove the parent section if no entries found (positional)
|
|
19
|
+
# exclude_from_toc — omit this list from the PDF Table of Contents and outline (positional, PDF only)
|
|
20
|
+
# exclude_from_outline — omit this list from the PDF bookmark outline only (positional, PDF only)
|
|
21
|
+
# caption_prefix — override the caption prefix (e.g. "Figure")
|
|
22
|
+
# title — override the list heading
|
|
23
|
+
#
|
|
24
|
+
# Example:
|
|
25
|
+
# list-of::image[]
|
|
26
|
+
# list-of::table[hide_empty_section]
|
|
27
|
+
# list-of::listing[caption_prefix="Code",title="Code Examples"]
|
|
28
|
+
class ListMacro < ::Asciidoctor::Extensions::BlockMacroProcessor
|
|
29
|
+
use_dsl
|
|
30
|
+
named :'list-of'
|
|
31
|
+
|
|
32
|
+
def process(parent, target, attrs)
|
|
33
|
+
uuid = SecureRandom.uuid
|
|
34
|
+
# Positional flags like [enhanced_rendering] may be stored under integer or string keys
|
|
35
|
+
pos_flags = (1..10).flat_map { |i| [attrs[i], attrs[i.to_s]] }.compact
|
|
36
|
+
ListMacroAttributes[uuid] = {
|
|
37
|
+
element: target,
|
|
38
|
+
enhanced_rendering: attrs['enhanced_rendering'] || pos_flags.include?('enhanced_rendering'),
|
|
39
|
+
hide_empty_section: attrs['hide_empty_section'] || pos_flags.include?('hide_empty_section'),
|
|
40
|
+
exclude_from_toc: attrs['exclude_from_toc'] || pos_flags.include?('exclude_from_toc'),
|
|
41
|
+
exclude_from_outline: attrs['exclude_from_outline'] || pos_flags.include?('exclude_from_outline'),
|
|
42
|
+
caption_prefix: attrs['caption_prefix'],
|
|
43
|
+
title: attrs['title'],
|
|
44
|
+
}
|
|
45
|
+
create_paragraph parent, uuid, {}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Treeprocessor that runs after the full document AST is built.
|
|
50
|
+
#
|
|
51
|
+
# Responsibilities:
|
|
52
|
+
# 1. Auto-generate IDs for all captioned/titled elements referenced by
|
|
53
|
+
# list-of:: macros. This eliminates the need for manual [#anchor] on
|
|
54
|
+
# every block.
|
|
55
|
+
# 2. For HTML5/other backends: replace UUID placeholder paragraphs with
|
|
56
|
+
# xref-based content (delegated to HtmlRenderer).
|
|
57
|
+
# 3. For PDF backend: leave UUID placeholder paragraphs in place so that
|
|
58
|
+
# PDFConverterWithLists can locate and render them during allocate_toc /
|
|
59
|
+
# ink_toc.
|
|
60
|
+
class ListTreeprocessor < ::Asciidoctor::Extensions::Treeprocessor
|
|
61
|
+
def process(document)
|
|
62
|
+
return if ListMacroAttributes.empty?
|
|
63
|
+
|
|
64
|
+
# Auto-generate IDs for all elements referenced by list-of:: macros.
|
|
65
|
+
# Works for both HTML and PDF — the IDs must exist before either renderer runs.
|
|
66
|
+
ListMacroAttributes.each_value do |config|
|
|
67
|
+
document
|
|
68
|
+
.find_by(traverse_documents: true, context: config[:element].to_sym)
|
|
69
|
+
.each do |element|
|
|
70
|
+
next unless element.caption || element.title
|
|
71
|
+
next if element.id
|
|
72
|
+
|
|
73
|
+
element.id = SecureRandom.uuid
|
|
74
|
+
document.catalog[:refs][element.id] = element
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# PDF backend: UUID placeholders are handled by PDFConverterWithLists.
|
|
79
|
+
return if document.backend == 'pdf'
|
|
80
|
+
|
|
81
|
+
# HTML5 and other backends: replace UUID placeholders with xref content.
|
|
82
|
+
HtmlRenderer.new.render(document)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
Asciidoctor::Extensions.register do
|
|
89
|
+
block_macro Asciidoctor::ListsExtended::ListMacro
|
|
90
|
+
treeprocessor Asciidoctor::ListsExtended::ListTreeprocessor
|
|
91
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Asciidoctor
|
|
4
|
+
module ListsExtended
|
|
5
|
+
# Replaces list-of:: UUID placeholder paragraphs with AsciiDoc xref content
|
|
6
|
+
# for HTML5 and other non-PDF backends.
|
|
7
|
+
#
|
|
8
|
+
# Preserves full backwards-compatibility with the original asciidoctor-lists gem:
|
|
9
|
+
# - enhanced_rendering parameter: separates caption and title
|
|
10
|
+
# - hide_empty_section parameter: removes parent section when no entries found
|
|
11
|
+
class HtmlRenderer
|
|
12
|
+
def render(document)
|
|
13
|
+
uuid_blocks = document.find_by do |b|
|
|
14
|
+
b.content_model == :simple &&
|
|
15
|
+
b.lines.size == 1 &&
|
|
16
|
+
ListMacroAttributes.key?(b.lines[0])
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
uuid_blocks.each do |block|
|
|
20
|
+
params = ListMacroAttributes[block.lines[0]]
|
|
21
|
+
enhanced_rendering = params[:enhanced_rendering]
|
|
22
|
+
hide_empty_section = params[:hide_empty_section]
|
|
23
|
+
|
|
24
|
+
scope = scope_node_for(block, document)
|
|
25
|
+
elements = scope.find_by(
|
|
26
|
+
traverse_documents: true,
|
|
27
|
+
context: params[:element].to_sym
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
titled_elements = elements.select { |e| e.caption || e.title }
|
|
31
|
+
|
|
32
|
+
if titled_elements.empty? && hide_empty_section
|
|
33
|
+
block.parent.parent.blocks.delete(block.parent)
|
|
34
|
+
next
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
references_asciidoc = titled_elements.map do |element|
|
|
38
|
+
if enhanced_rendering
|
|
39
|
+
if element.caption
|
|
40
|
+
%(xref:#{element.id}[#{element.caption.rstrip}] #{element.instance_variable_get(:@title)} +)
|
|
41
|
+
else
|
|
42
|
+
%(xref:#{element.id}[#{element.instance_variable_get(:@title)}] +)
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
if element.caption
|
|
46
|
+
%(xref:#{element.id}[#{element.caption.rstrip}] #{element.title} +)
|
|
47
|
+
else
|
|
48
|
+
%(xref:#{element.id}[#{element.title}] +)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
block_index = block.parent.blocks.index { |b| b == block }
|
|
54
|
+
parsed_blocks = parse_asciidoc(block.parent, references_asciidoc)
|
|
55
|
+
parsed_blocks.reverse_each { |b| block.parent.blocks.insert block_index, b }
|
|
56
|
+
block.parent.blocks.delete_at block_index + parsed_blocks.size
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
# Determine collection scope for a UUID placeholder block.
|
|
63
|
+
# Mirrors the logic in PdfRenderer#scope_node_for.
|
|
64
|
+
def scope_node_for(block, document)
|
|
65
|
+
node = block.parent
|
|
66
|
+
return document unless node.respond_to?(:context) && node.context == :section
|
|
67
|
+
node.parent.context == :document ? document : chapter_ancestor(node)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def chapter_ancestor(section)
|
|
71
|
+
node = section
|
|
72
|
+
node = node.parent while node.parent&.context == :section
|
|
73
|
+
node
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Parse AsciiDoc source lines in the context of +parent+, returning the
|
|
77
|
+
# resulting blocks without attaching them to the parent.
|
|
78
|
+
# Adapted from asciidoctor-bibtex / original asciidoctor-lists.
|
|
79
|
+
def parse_asciidoc(parent, content, attributes = {})
|
|
80
|
+
result = []
|
|
81
|
+
reader = ::Asciidoctor::Reader.new content
|
|
82
|
+
while reader.has_more_lines?
|
|
83
|
+
block = ::Asciidoctor::Parser.next_block reader, parent, attributes
|
|
84
|
+
result << block if block
|
|
85
|
+
end
|
|
86
|
+
result
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'asciidoctor/pdf' unless defined? Asciidoctor::PDF
|
|
4
|
+
|
|
5
|
+
require_relative 'pdf_renderer'
|
|
6
|
+
|
|
7
|
+
module Asciidoctor
|
|
8
|
+
module ListsExtended
|
|
9
|
+
# Single unified PDF converter that replaces the four copy-pasted classes
|
|
10
|
+
# (PDFConverterWithLOF, PDFConverterWithLOT, PDFConverterWithLOE,
|
|
11
|
+
# PDFConverterWithLOL) from asciidoctor-pdf-lofte.
|
|
12
|
+
#
|
|
13
|
+
# Hooks into asciidoctor-pdf's allocate_toc / ink_toc lifecycle to:
|
|
14
|
+
# 1. Reserve page space for each list-of:: macro found in the document
|
|
15
|
+
# (allocate_toc → allocate_list via dry_run).
|
|
16
|
+
# 2. Render each list with ToC-style dot leaders (ink_toc → ink_list).
|
|
17
|
+
# 3. Insert virtual Section nodes so lists appear in the PDF ToC and
|
|
18
|
+
# bookmark outline by default. Per-macro flags exclude_from_toc and
|
|
19
|
+
# exclude_from_outline opt individual lists out.
|
|
20
|
+
#
|
|
21
|
+
# ink_toc_level is overridden only for list rendering (guarded by
|
|
22
|
+
# @rendering_list flag), leaving normal ToC behavior untouched.
|
|
23
|
+
class PDFConverterWithLists < ::Asciidoctor::Converter.for('pdf')
|
|
24
|
+
register_for 'pdf'
|
|
25
|
+
|
|
26
|
+
include PdfRenderer
|
|
27
|
+
|
|
28
|
+
def initialize(*args)
|
|
29
|
+
super
|
|
30
|
+
@list_extents = {} # UUID → Extent (front-matter lists only)
|
|
31
|
+
@list_configs_ordered = [] # front-matter list configs in document order
|
|
32
|
+
@inline_list_configs = {} # UUID → config for chapter-scoped inline lists
|
|
33
|
+
@inline_num_front_matter_pages = 0 # saved from ink_toc for inline page-number math
|
|
34
|
+
@rendering_list = false
|
|
35
|
+
@list_toc_insert_idx = 0
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# -----------------------------------------------------------------------
|
|
39
|
+
# ink_toc_level override
|
|
40
|
+
# -----------------------------------------------------------------------
|
|
41
|
+
# When @rendering_list is true (set by PdfRenderer#ink_list_content),
|
|
42
|
+
# dispatch to our list-specific implementation that uses captioned_title
|
|
43
|
+
# for non-section entries. Otherwise delegate to the parent unchanged so
|
|
44
|
+
# that the real Table of Contents is not affected.
|
|
45
|
+
def ink_toc_level(entries, num_levels, dot_leader, num_front_matter_pages)
|
|
46
|
+
if @rendering_list
|
|
47
|
+
ink_list_toc_level entries, num_levels, dot_leader, num_front_matter_pages
|
|
48
|
+
else
|
|
49
|
+
super
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# -----------------------------------------------------------------------
|
|
54
|
+
# allocate_toc hook
|
|
55
|
+
# -----------------------------------------------------------------------
|
|
56
|
+
# Called by asciidoctor-pdf early in document generation to reserve page
|
|
57
|
+
# space for the ToC. We call super first (allocate the real ToC), then
|
|
58
|
+
# iterate over all list-of:: macros and allocate space for each list.
|
|
59
|
+
def allocate_toc(doc, num_levels, toc_start_cursor, break_after_toc)
|
|
60
|
+
result = super
|
|
61
|
+
collect_and_allocate_lists doc, num_levels, toc_start_cursor, break_after_toc
|
|
62
|
+
result
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# -----------------------------------------------------------------------
|
|
66
|
+
# ink_toc hook
|
|
67
|
+
# -----------------------------------------------------------------------
|
|
68
|
+
# Render every allocated list before delegating to super (which renders
|
|
69
|
+
# the real ToC). Always inserts a virtual Section node for each list so
|
|
70
|
+
# it appears in the PDF bookmark outline. exclude_from_toc marks the
|
|
71
|
+
# section so get_entries_for_toc filters it from the visible ToC;
|
|
72
|
+
# exclude_from_outline marks it so add_outline_level skips it.
|
|
73
|
+
def ink_toc(doc, num_levels, toc_page_number, start_cursor, num_front_matter_pages = 0)
|
|
74
|
+
@inline_num_front_matter_pages = num_front_matter_pages
|
|
75
|
+
@list_toc_insert_idx = 0
|
|
76
|
+
|
|
77
|
+
@list_configs_ordered.each do |config|
|
|
78
|
+
extent = @list_extents[config[:uuid]]
|
|
79
|
+
next unless extent
|
|
80
|
+
|
|
81
|
+
ink_list doc, config, extent, num_levels, num_front_matter_pages
|
|
82
|
+
|
|
83
|
+
list_title = config[:title] || config[:section_title]
|
|
84
|
+
list_page_nums = extent.page_range
|
|
85
|
+
list_id = "_list_of_#{config[:element].tr('-', '_')}"
|
|
86
|
+
insert_list_into_toc_section doc, list_title, list_page_nums, list_id, @list_toc_insert_idx,
|
|
87
|
+
exclude_from_toc: config[:exclude_from_toc],
|
|
88
|
+
exclude_from_outline: config[:exclude_from_outline]
|
|
89
|
+
@list_toc_insert_idx += 1
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
super
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# -----------------------------------------------------------------------
|
|
96
|
+
# get_entries_for_toc override
|
|
97
|
+
# -----------------------------------------------------------------------
|
|
98
|
+
# Filters out virtual list sections marked with 'list-exclude-from-toc'
|
|
99
|
+
# so they are hidden from the rendered Table of Contents while remaining
|
|
100
|
+
# present in doc.sections (and therefore still visible in the PDF outline).
|
|
101
|
+
def get_entries_for_toc(node)
|
|
102
|
+
super.reject { |s| s.attr? 'list-exclude-from-toc' }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# -----------------------------------------------------------------------
|
|
106
|
+
# add_outline_level override
|
|
107
|
+
# -----------------------------------------------------------------------
|
|
108
|
+
# Filters out virtual list sections marked with 'list-exclude-from-outline'
|
|
109
|
+
# before delegating to the parent implementation, which builds PDF bookmarks.
|
|
110
|
+
# This lets exclude_from_outline keep a list in the ToC while hiding it
|
|
111
|
+
# from the PDF bookmark outline.
|
|
112
|
+
def add_outline_level(outline, sections, num_levels, expand_levels)
|
|
113
|
+
super outline, sections.reject { |s| s.attr? 'list-exclude-from-outline' }, num_levels, expand_levels
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# -----------------------------------------------------------------------
|
|
117
|
+
# convert_paragraph override
|
|
118
|
+
# -----------------------------------------------------------------------
|
|
119
|
+
# Intercepts UUID placeholder paragraphs belonging to chapter-scoped
|
|
120
|
+
# inline lists (those stored in @inline_list_configs by
|
|
121
|
+
# collect_and_allocate_lists). Renders the list entries at the current
|
|
122
|
+
# cursor position without a heading (the enclosing === section provides
|
|
123
|
+
# the heading) and without any page break before or after.
|
|
124
|
+
# All other paragraphs delegate to super unchanged.
|
|
125
|
+
def convert_paragraph(node)
|
|
126
|
+
if node.content_model == :simple && node.lines.size == 1 &&
|
|
127
|
+
(config = @inline_list_configs[node.lines[0]])
|
|
128
|
+
entries = get_list_entries(config[:scope_node], config[:element])
|
|
129
|
+
unless entries.empty?
|
|
130
|
+
entries.each { |e| e.level = 2 if e.title }
|
|
131
|
+
num_levels = (node.document.attr 'toclevels', 2).to_i
|
|
132
|
+
dot_leader = build_dot_leader_config(num_levels)
|
|
133
|
+
begin
|
|
134
|
+
@rendering_list = true
|
|
135
|
+
ink_toc_level entries, num_levels, dot_leader, @inline_num_front_matter_pages
|
|
136
|
+
ensure
|
|
137
|
+
@rendering_list = false
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
return
|
|
141
|
+
end
|
|
142
|
+
super
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# -----------------------------------------------------------------------
|
|
148
|
+
# collect_and_allocate_lists
|
|
149
|
+
# -----------------------------------------------------------------------
|
|
150
|
+
# Scan the document AST for UUID placeholder paragraphs (in document
|
|
151
|
+
# order, which matches the order the author placed the list-of:: macros).
|
|
152
|
+
# For each placeholder that has at least one captioned/titled element,
|
|
153
|
+
# allocate page space via a dry run.
|
|
154
|
+
def collect_and_allocate_lists(doc, num_levels, start_cursor, break_after)
|
|
155
|
+
uuid_blocks = doc.find_by do |b|
|
|
156
|
+
b.content_model == :simple &&
|
|
157
|
+
b.lines.size == 1 &&
|
|
158
|
+
ListMacroAttributes.key?(b.lines[0])
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
uuid_blocks.each do |block|
|
|
162
|
+
uuid = block.lines[0]
|
|
163
|
+
config = ListMacroAttributes[uuid]
|
|
164
|
+
scope_node = scope_node_for(block)
|
|
165
|
+
entries = get_list_entries(scope_node, config[:element])
|
|
166
|
+
parent = block.parent
|
|
167
|
+
|
|
168
|
+
if scope_node.equal?(doc)
|
|
169
|
+
# ── Document-wide macro ────────────────────────────────────────
|
|
170
|
+
# Remove from body and render in PDF front matter after the ToC.
|
|
171
|
+
# Capture the parent section title first — it becomes the list heading.
|
|
172
|
+
section_title = (parent.context == :section) ? parent.title : nil
|
|
173
|
+
parent.blocks.delete(block)
|
|
174
|
+
if parent.context == :section && parent.blocks.empty?
|
|
175
|
+
parent.parent&.blocks&.delete(parent)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
next if entries.empty?
|
|
179
|
+
|
|
180
|
+
config_with_uuid = config.merge(uuid: uuid, section_title: section_title, scope_node: scope_node)
|
|
181
|
+
@list_configs_ordered << config_with_uuid
|
|
182
|
+
@list_extents[uuid] = allocate_list(doc, config_with_uuid, num_levels, start_cursor, break_after)
|
|
183
|
+
else
|
|
184
|
+
# ── Chapter-scoped macro ───────────────────────────────────────
|
|
185
|
+
# Leave the placeholder in the body; convert_paragraph will render
|
|
186
|
+
# it inline at the macro's position without a page break.
|
|
187
|
+
# The enclosing section heading is preserved as-is.
|
|
188
|
+
config_with_uuid = config.merge(uuid: uuid, section_title: nil, scope_node: scope_node)
|
|
189
|
+
@inline_list_configs[uuid] = config_with_uuid
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# -----------------------------------------------------------------------
|
|
195
|
+
# allocate_list
|
|
196
|
+
# -----------------------------------------------------------------------
|
|
197
|
+
# Dry-run render of one list to measure the space it needs, then reserve
|
|
198
|
+
# that many pages. Follows the same pattern as rhrev's
|
|
199
|
+
# allocate_revision_history_extent and asciidoctor-pdf-lofte's allocate_lof.
|
|
200
|
+
def allocate_list(doc, config, num_levels, _start_cursor, break_after)
|
|
201
|
+
to_page = nil
|
|
202
|
+
extent = dry_run onto: self do
|
|
203
|
+
to_page = ink_list(doc, config, nil, num_levels, 0).last
|
|
204
|
+
theme_margin :block, :bottom unless break_after
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
if to_page && to_page > extent.to.page
|
|
208
|
+
extent.to.page = to_page
|
|
209
|
+
extent.to.cursor = bounds.height
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
if break_after
|
|
213
|
+
extent.each_page { start_new_page }
|
|
214
|
+
else
|
|
215
|
+
extent.each_page { |first_page| start_new_page unless first_page }
|
|
216
|
+
move_cursor_to extent.to.cursor
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
extent
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# -----------------------------------------------------------------------
|
|
223
|
+
# ink_list
|
|
224
|
+
# -----------------------------------------------------------------------
|
|
225
|
+
# Navigate to the allocated pages and render one list section.
|
|
226
|
+
# When called from inside a dry_run block (extent is nil / scratch? true),
|
|
227
|
+
# navigation is skipped and only layout measurement happens.
|
|
228
|
+
# Returns the page range occupied by the list.
|
|
229
|
+
def ink_list(doc, config, extent, num_levels, num_front_matter_pages)
|
|
230
|
+
if extent && !scratch?
|
|
231
|
+
go_to_page extent.from.page
|
|
232
|
+
move_cursor_to extent.from.cursor
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
start_page = page_number
|
|
236
|
+
entries = get_list_entries(config[:scope_node] || doc, config[:element])
|
|
237
|
+
|
|
238
|
+
# Set level = 2 on every entry so ink_list_toc_level uses toc_h3 indent,
|
|
239
|
+
# producing a flat indented list that matches asciidoctor-pdf-lofte output.
|
|
240
|
+
entries.each { |e| e.level = 2 if e.title }
|
|
241
|
+
|
|
242
|
+
ink_list_content doc, config, entries, num_levels, num_front_matter_pages
|
|
243
|
+
|
|
244
|
+
page_range = (start_page..(start_page + (page_number - start_page)))
|
|
245
|
+
go_to_page page_count unless scratch?
|
|
246
|
+
page_range
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# -----------------------------------------------------------------------
|
|
250
|
+
# ink_list_toc_level
|
|
251
|
+
# -----------------------------------------------------------------------
|
|
252
|
+
# Custom ink_toc_level used exclusively when @rendering_list is true.
|
|
253
|
+
# Adapted from asciidoctor-pdf-lofte FormatTOC#ink_toc_level, which is
|
|
254
|
+
# itself a copy of the asciidoctor-pdf ink_toc_level implementation with
|
|
255
|
+
# two additions:
|
|
256
|
+
# a) Non-section entries (image, table, etc.) use captioned_title
|
|
257
|
+
# ("Figure 1. My Diagram") instead of bare title.
|
|
258
|
+
# b) The no-ID skip from the original lofte is removed — auto-generated
|
|
259
|
+
# UUIDs from the TreeProcessor guarantee all entries have IDs.
|
|
260
|
+
def ink_list_toc_level(entries, num_levels, dot_leader, num_front_matter_pages)
|
|
261
|
+
toc_font_info = theme_font(:toc) { { font: font, size: @font_size } }
|
|
262
|
+
hanging_indent = @theme.toc_hanging_indent
|
|
263
|
+
|
|
264
|
+
entries.each do |entry|
|
|
265
|
+
next if (num_levels_for_entry = (entry.attr 'toclevels', num_levels).to_i) <
|
|
266
|
+
(entry_level = entry.level + 1).pred ||
|
|
267
|
+
((entry.option? 'notitle') && entry == entry.document.last_child && entry.empty?)
|
|
268
|
+
|
|
269
|
+
# Base title: sections use numbered_title; blocks use plain title or xreftext
|
|
270
|
+
entry_title = entry.context == :section \
|
|
271
|
+
? entry.numbered_title \
|
|
272
|
+
: (entry.title? ? entry.title : (entry.xreftext 'basic'))
|
|
273
|
+
|
|
274
|
+
# For captioned blocks (image, table, example, listing, custom) prefer
|
|
275
|
+
# captioned_title, e.g. "Figure 1. My Diagram" over "My Diagram".
|
|
276
|
+
if entry.context != :section && entry.respond_to?(:captioned_title)
|
|
277
|
+
captioned = entry.captioned_title
|
|
278
|
+
entry_title = captioned unless captioned.nil_or_empty?
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
next if entry_title.nil_or_empty?
|
|
282
|
+
|
|
283
|
+
theme_font :toc, level: entry_level do
|
|
284
|
+
entry_title = transform_text entry_title, @text_transform if @text_transform
|
|
285
|
+
pgnum_label_placeholder_width = rendered_width_of_string '0' * @toc_max_pagenum_digits
|
|
286
|
+
|
|
287
|
+
if scratch?
|
|
288
|
+
# Dry-run pass: measure space only
|
|
289
|
+
indent 0, pgnum_label_placeholder_width do
|
|
290
|
+
ink_prose entry_title,
|
|
291
|
+
anchor: true,
|
|
292
|
+
normalize: false,
|
|
293
|
+
hanging_indent: hanging_indent,
|
|
294
|
+
normalize_line_height: true,
|
|
295
|
+
margin: 0
|
|
296
|
+
end
|
|
297
|
+
else
|
|
298
|
+
# Real render pass: resolve page number and draw dot leader
|
|
299
|
+
entry_anchor = (entry.attr 'pdf-anchor') || entry.id
|
|
300
|
+
unless (physical_pgnum = entry.attr 'pdf-page-start')
|
|
301
|
+
if (target_page_ref = (get_dest entry_anchor)&.first) &&
|
|
302
|
+
(target_page_idx = state.pages.index { |c| c.dictionary == target_page_ref })
|
|
303
|
+
physical_pgnum = target_page_idx + 1
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
pgnum_label = if physical_pgnum
|
|
308
|
+
virtual_pgnum = physical_pgnum - num_front_matter_pages
|
|
309
|
+
(virtual_pgnum < 1 \
|
|
310
|
+
? Asciidoctor::PDF::RomanNumeral.new(physical_pgnum, :lower) \
|
|
311
|
+
: virtual_pgnum).to_s
|
|
312
|
+
else
|
|
313
|
+
'?'
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
start_page_number = page_number
|
|
317
|
+
start_cursor = cursor
|
|
318
|
+
start_dots = nil
|
|
319
|
+
|
|
320
|
+
entry_title_inherited = (apply_text_decoration ::Set.new, :toc, entry_level)
|
|
321
|
+
.merge(anchor: entry_anchor, color: @font_color)
|
|
322
|
+
entry_title_fragments = text_formatter.format entry_title,
|
|
323
|
+
inherited: entry_title_inherited
|
|
324
|
+
line_metrics = calc_line_metrics @base_line_height
|
|
325
|
+
|
|
326
|
+
indent 0, pgnum_label_placeholder_width do
|
|
327
|
+
fragment_positions = entry_title_fragments.map do |fragment|
|
|
328
|
+
fp = ::Asciidoctor::PDF::FormattedText::FragmentPositionRenderer.new
|
|
329
|
+
(fragment[:callback] ||= []) << fp
|
|
330
|
+
fp
|
|
331
|
+
end
|
|
332
|
+
typeset_formatted_text entry_title_fragments, line_metrics,
|
|
333
|
+
hanging_indent: hanging_indent,
|
|
334
|
+
normalize_line_height: true
|
|
335
|
+
last_fp = fragment_positions.select(&:page_number).last
|
|
336
|
+
break unless last_fp
|
|
337
|
+
|
|
338
|
+
start_dots = last_fp.right + hanging_indent
|
|
339
|
+
last_frag_cursor = last_fp.top + line_metrics.padding_top
|
|
340
|
+
if last_fp.page_number > start_page_number ||
|
|
341
|
+
(start_cursor - last_frag_cursor) > line_metrics.height
|
|
342
|
+
start_cursor = last_frag_cursor
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
break unless start_dots
|
|
347
|
+
|
|
348
|
+
end_cursor = cursor
|
|
349
|
+
move_cursor_to start_cursor
|
|
350
|
+
|
|
351
|
+
if dot_leader[:width] > 0
|
|
352
|
+
pgnum_label_width = rendered_width_of_string pgnum_label
|
|
353
|
+
pgnum_label_font_opts = {
|
|
354
|
+
color: @font_color,
|
|
355
|
+
font: font_family,
|
|
356
|
+
size: @font_size,
|
|
357
|
+
styles: font_styles,
|
|
358
|
+
}
|
|
359
|
+
save_font do
|
|
360
|
+
set_font toc_font_info[:font], dot_leader[:font_size]
|
|
361
|
+
font_style dot_leader[:font_style]
|
|
362
|
+
num_dots = [
|
|
363
|
+
((bounds.width - start_dots - dot_leader[:spacer_width] - pgnum_label_width) /
|
|
364
|
+
dot_leader[:width]).floor,
|
|
365
|
+
0,
|
|
366
|
+
].max
|
|
367
|
+
typeset_formatted_text [
|
|
368
|
+
{ text: dot_leader[:text] * num_dots, color: dot_leader[:font_color] },
|
|
369
|
+
dot_leader[:spacer],
|
|
370
|
+
({ text: pgnum_label, anchor: entry_anchor }.merge pgnum_label_font_opts),
|
|
371
|
+
], line_metrics, align: :right
|
|
372
|
+
end
|
|
373
|
+
else
|
|
374
|
+
typeset_formatted_text [
|
|
375
|
+
{ text: pgnum_label, color: @font_color, anchor: entry_anchor },
|
|
376
|
+
], line_metrics, align: :right
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
move_cursor_to end_cursor
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Recurse for child entries (typically none for images/tables, but
|
|
384
|
+
# preserves compatibility with any custom element type that has children)
|
|
385
|
+
if num_levels_for_entry >= entry_level
|
|
386
|
+
indent @theme.toc_indent do
|
|
387
|
+
ink_toc_level get_entries_for_toc(entry), num_levels_for_entry,
|
|
388
|
+
dot_leader, num_front_matter_pages
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Asciidoctor
|
|
4
|
+
module ListsExtended
|
|
5
|
+
# Mixed into PDFConverterWithLists to provide list-rendering helpers.
|
|
6
|
+
# All methods here run in the context of the PDF converter instance,
|
|
7
|
+
# so asciidoctor-pdf methods (ink_general_heading, theme_font_cascade,
|
|
8
|
+
# add_dest_for_block, ink_toc_level, etc.) are available via self.
|
|
9
|
+
module PdfRenderer
|
|
10
|
+
# Render one list section: title heading + dot-leader entry rows.
|
|
11
|
+
# Called both during the dry run (scratch? == true) and the real ink pass.
|
|
12
|
+
#
|
|
13
|
+
# config keys used here:
|
|
14
|
+
# :element — element type string ('image', 'table', …)
|
|
15
|
+
# :title — explicit title override from macro attribute
|
|
16
|
+
# :section_title — title of the parent section captured before it was removed
|
|
17
|
+
#
|
|
18
|
+
# Sets @rendering_list = true around the ink_toc_level call so that
|
|
19
|
+
# PDFConverterWithLists#ink_toc_level activates the list-aware code path.
|
|
20
|
+
def ink_list_content(doc, config, entries, num_levels, num_front_matter_pages = 0)
|
|
21
|
+
element_type = config[:element]
|
|
22
|
+
dest_id = "_list_of_#{element_type.tr('-', '_')}"
|
|
23
|
+
|
|
24
|
+
list_title = config[:title] || config[:section_title]
|
|
25
|
+
unless list_title.nil_or_empty?
|
|
26
|
+
# Theme key mirrors asciidoctor-pdf-lofte convention, e.g. :image_list_title
|
|
27
|
+
theme_key_sym = :"#{element_type.tr('-', '_')}_list_title"
|
|
28
|
+
theme_font_cascade [[:heading, level: 2], theme_key_sym] do
|
|
29
|
+
align_method = :"#{theme_key_sym}_text_align"
|
|
30
|
+
title_align = (@theme.respond_to?(align_method) ? @theme.send(align_method) : nil) ||
|
|
31
|
+
@theme.heading_h2_text_align ||
|
|
32
|
+
@theme.heading_text_align ||
|
|
33
|
+
@base_text_align
|
|
34
|
+
ink_general_heading doc, list_title,
|
|
35
|
+
align: title_align.to_sym,
|
|
36
|
+
level: 2,
|
|
37
|
+
outdent: true,
|
|
38
|
+
role: theme_key_sym
|
|
39
|
+
add_dest_for_block doc, id: dest_id, y: (at_page_top? ? page_height : nil)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
unless num_levels < 0 || entries.empty?
|
|
44
|
+
dot_leader = build_dot_leader_config(num_levels)
|
|
45
|
+
theme_margin :toc, :top
|
|
46
|
+
begin
|
|
47
|
+
@rendering_list = true
|
|
48
|
+
ink_toc_level entries, num_levels, dot_leader, num_front_matter_pages
|
|
49
|
+
ensure
|
|
50
|
+
@rendering_list = false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Collect all captioned/titled elements of the requested type from scope_node.
|
|
56
|
+
# scope_node is either the whole document (document-wide) or a chapter section
|
|
57
|
+
# (chapter-scoped), as determined by scope_node_for at collection time.
|
|
58
|
+
def get_list_entries(scope_node, element_type)
|
|
59
|
+
scope_node
|
|
60
|
+
.find_by(traverse_documents: true, context: element_type.to_sym)
|
|
61
|
+
.select { |e| e.caption || e.title }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Insert a virtual Section node into the document AST at the correct position
|
|
65
|
+
# so that asciidoctor-pdf's get_entries_for_toc picks it up and renders it
|
|
66
|
+
# in the Table of Contents.
|
|
67
|
+
#
|
|
68
|
+
# insert_idx is the 0-based position among sibling list sections being inserted,
|
|
69
|
+
# which preserves document order in the ToC.
|
|
70
|
+
def insert_list_into_toc_section(doc, list_title, list_page_nums, list_id, insert_idx, exclude_from_toc: false, exclude_from_outline: false)
|
|
71
|
+
if (doc.attr? 'toc-placement', 'macro') && (toc_node = (doc.find_by context: :toc)[0])
|
|
72
|
+
if (parent_section = toc_node.parent).context == :section
|
|
73
|
+
grandparent_section = parent_section.parent
|
|
74
|
+
toc_level = parent_section.level
|
|
75
|
+
base_idx = (grandparent_section.blocks.index parent_section) + 1
|
|
76
|
+
else
|
|
77
|
+
grandparent_section = doc
|
|
78
|
+
toc_level = doc.sections[0]&.level || 1
|
|
79
|
+
base_idx = 0
|
|
80
|
+
end
|
|
81
|
+
toc_dest = toc_node.attr 'pdf-destination'
|
|
82
|
+
else
|
|
83
|
+
grandparent_section = doc
|
|
84
|
+
toc_level = doc.sections[0]&.level || 1
|
|
85
|
+
base_idx = 0
|
|
86
|
+
toc_dest = dest_top list_page_nums.first
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
list_section = Asciidoctor::Section.new grandparent_section, toc_level, false,
|
|
90
|
+
attributes: { 'pdf-destination' => toc_dest }
|
|
91
|
+
list_section.title = list_title
|
|
92
|
+
list_section.id = list_id
|
|
93
|
+
list_section.set_attr 'list-exclude-from-toc', '' if exclude_from_toc
|
|
94
|
+
list_section.set_attr 'list-exclude-from-outline', '' if exclude_from_outline
|
|
95
|
+
grandparent_section.blocks.insert base_idx + insert_idx, list_section
|
|
96
|
+
list_section
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
# Build the dot-leader configuration hash expected by ink_toc_level.
|
|
102
|
+
# Reads theme variables from the asciidoctor-pdf theme loader.
|
|
103
|
+
# Falls back to sensible defaults if the theme does not define them.
|
|
104
|
+
def build_dot_leader_config(num_levels)
|
|
105
|
+
theme_font :toc do
|
|
106
|
+
dot_leader_font_style = @theme.toc_dot_leader_font_style&.to_sym || :normal
|
|
107
|
+
font_style dot_leader_font_style if dot_leader_font_style != font_style
|
|
108
|
+
font_size @theme.toc_dot_leader_font_size
|
|
109
|
+
dot_leader_text = @theme.toc_dot_leader_content || dot_leader_text_default
|
|
110
|
+
{
|
|
111
|
+
font_color: @theme.toc_dot_leader_font_color || @font_color,
|
|
112
|
+
font_style: dot_leader_font_style,
|
|
113
|
+
font_size: font_size,
|
|
114
|
+
levels: build_dot_leader_levels(num_levels),
|
|
115
|
+
text: dot_leader_text,
|
|
116
|
+
width: dot_leader_text.empty? ? 0 : (rendered_width_of_string dot_leader_text),
|
|
117
|
+
spacer: { text: no_break_space, size: (spacer_font_size = @font_size * 0.25) },
|
|
118
|
+
spacer_width: (rendered_width_of_char no_break_space, size: spacer_font_size),
|
|
119
|
+
}
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def build_dot_leader_levels(num_levels)
|
|
124
|
+
dot_leader_l = @theme.toc_dot_leader_levels
|
|
125
|
+
if dot_leader_l == 'none'
|
|
126
|
+
::Set.new
|
|
127
|
+
elsif dot_leader_l && dot_leader_l != 'all'
|
|
128
|
+
dot_leader_l.to_s.split.map(&:to_i).to_set
|
|
129
|
+
else
|
|
130
|
+
(0..num_levels).to_set
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Determine the collection scope for a list-of:: UUID placeholder block.
|
|
135
|
+
#
|
|
136
|
+
# Rule: if the macro's parent section is a top-level section (its parent is
|
|
137
|
+
# the Document), scope is the whole document. If the macro is nested inside
|
|
138
|
+
# a chapter (grandparent is another section), scope is the chapter ancestor —
|
|
139
|
+
# the first ancestor section whose parent is the Document.
|
|
140
|
+
#
|
|
141
|
+
# This means front-matter lists (== List of Figures) collect everything, while
|
|
142
|
+
# per-chapter lists (=== Chapter Figures inside == Chapter 1) collect only
|
|
143
|
+
# their chapter's content automatically.
|
|
144
|
+
def scope_node_for(block)
|
|
145
|
+
node = block.parent
|
|
146
|
+
return block.document unless node.respond_to?(:context) && node.context == :section
|
|
147
|
+
node.parent.context == :document ? block.document : chapter_ancestor(node)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Walk up the parent chain to find the first section whose parent is the Document.
|
|
151
|
+
def chapter_ancestor(section)
|
|
152
|
+
node = section
|
|
153
|
+
node = node.parent while node.parent&.context == :section
|
|
154
|
+
node
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Safe accessor: use the asciidoctor-pdf constant if available, else fall back.
|
|
158
|
+
def dot_leader_text_default
|
|
159
|
+
defined?(DotLeaderTextDefault) ? DotLeaderTextDefault : '. '
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def no_break_space
|
|
163
|
+
defined?(NoBreakSpace) ? NoBreakSpace : "\u00a0"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# asciidoctor-lists-extended
|
|
4
|
+
# Converter-aware extension providing List of Figures, Tables, Listings, and Examples.
|
|
5
|
+
# Supports asciidoctor HTML5, asciidoctor-pdf, and Antora.
|
|
6
|
+
# Compatible with asciidoctor-lists macro syntax: list-of::element[]
|
|
7
|
+
#
|
|
8
|
+
# Macro flags (PDF only):
|
|
9
|
+
# exclude_from_toc — omit this list from the PDF ToC and bookmark outline
|
|
10
|
+
# exclude_from_outline — omit this list from the PDF bookmark outline only
|
|
11
|
+
|
|
12
|
+
require_relative 'asciidoctor-lists-extended/html_renderer'
|
|
13
|
+
require_relative 'asciidoctor-lists-extended/extensions'
|
|
14
|
+
|
|
15
|
+
# PDF support is optional — loaded only if asciidoctor-pdf is present
|
|
16
|
+
begin
|
|
17
|
+
require 'asciidoctor-pdf'
|
|
18
|
+
require_relative 'asciidoctor-lists-extended/pdf_renderer'
|
|
19
|
+
require_relative 'asciidoctor-lists-extended/pdf_converter'
|
|
20
|
+
rescue LoadError
|
|
21
|
+
# asciidoctor-pdf not available; HTML-only mode
|
|
22
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: asciidoctor-lists-extended
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- 白一百 baiyibai
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: asciidoctor
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
description: |
|
|
27
|
+
An asciidoctor extension that generates List of Figures, List of Tables,
|
|
28
|
+
List of Listings, and List of Examples. Works with HTML5 (xref links) and
|
|
29
|
+
asciidoctor-pdf (dot-leader style matching the ToC, optional ToC integration,
|
|
30
|
+
PDF bookmarks). Compatible with asciidoctor-lists macro syntax.
|
|
31
|
+
No hardcoded anchors required — IDs are auto-generated.
|
|
32
|
+
email: rubygems@baiyibai.cyou
|
|
33
|
+
executables: []
|
|
34
|
+
extensions: []
|
|
35
|
+
extra_rdoc_files: []
|
|
36
|
+
files:
|
|
37
|
+
- LICENSE
|
|
38
|
+
- README.adoc
|
|
39
|
+
- lib/asciidoctor-lists-extended.rb
|
|
40
|
+
- lib/asciidoctor-lists-extended/extensions.rb
|
|
41
|
+
- lib/asciidoctor-lists-extended/html_renderer.rb
|
|
42
|
+
- lib/asciidoctor-lists-extended/pdf_converter.rb
|
|
43
|
+
- lib/asciidoctor-lists-extended/pdf_renderer.rb
|
|
44
|
+
homepage: https://gitlab.com/baiyibai-asciidoc/asciidoctor-lists-extended
|
|
45
|
+
licenses:
|
|
46
|
+
- MIT
|
|
47
|
+
metadata:
|
|
48
|
+
source_code_uri: https://gitlab.com/baiyibai-asciidoc/asciidoctor-lists-extended.git
|
|
49
|
+
rdoc_options: []
|
|
50
|
+
require_paths:
|
|
51
|
+
- lib
|
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: 3.0.0
|
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
requirements: []
|
|
63
|
+
rubygems_version: 3.6.9
|
|
64
|
+
specification_version: 4
|
|
65
|
+
summary: Converter-aware list-of-figures/tables/listings/examples for asciidoctor
|
|
66
|
+
(HTML5 + PDF)
|
|
67
|
+
test_files: []
|