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.
Replaces and supersedes asciidoctor-pdf-lofte.
Compatible with the asciidoctor-lists macro syntax.
Preview
Features
-
Works with HTML5 (xref links) and asciidoctor-pdf (dot-leader style matching the ToC)
-
No hardcoded
[#anchor]required — IDs are auto-generated viaSecureRandom.uuid -
Any element type — image, table, listing, example, or custom block contexts
-
ToC integration — lists optionally appear in the PDF Table of Contents and bookmark outline
-
Compatible with
asciidoctor-lists— samelist-of::element[]macro syntax
Installation
Ruby (PDF + HTML5)
gem install asciidoctor-lists-extended
Add to a Gemfile:
gem 'asciidoctor-lists-extended'
Require on the command line:
asciidoctor-pdf -r asciidoctor-lists-extended document.adoc
asciidoctor -r asciidoctor-lists-extended document.adoc
Node.js / npm (HTML5, Antora, VSCode)
npm install asciidoctor-lists-extended
Antora
Add to the Antora site’s package.json dependencies, then reference in the playbook
under asciidoc: — not under antora: (those are pipeline hooks, not Asciidoctor.js extensions):
asciidoc:
extensions:
- asciidoctor-lists-extended
The list-of:: macro is then available in all AsciiDoc source files processed by that playbook.
VSCode
Install globally on the machine hosting the workspace (local or Remote-SSH server):
npm install -g asciidoctor-lists-extended
Add to .vscode/settings.json:
{
"asciidoc.asciidoctorExtensions": ["asciidoctor-lists-extended"]
}
For workspaces where a global install is not available, add a one-line shim at
.asciidoctor/lib/lists-extended.js:
module.exports = require('asciidoctor-lists-extended')
The VSCode AsciiDoc extension auto-loads any .js file in .asciidoctor/lib/.
|
Note
|
For Remote-SSH, the npm package must be installed on the remote machine. The VSCode AsciiDoc preview runs server-side. |
Quick Start
= My Document
:toc:
:doctype: book
== List of Figures <b class="conum">(1)</b>
list-of::image[] <b class="conum">(2)</b>
== List of Tables
list-of::table[]
== Chapter 1
.My Architecture Diagram <b class="conum">(3)</b>
image::arch.png[]
.Configuration Parameters
|===
|Name |Value
|timeout |30
|===
-
The section heading becomes the title in the PDF front matter.
-
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.
-
No
[#anchor]needed — IDs are assigned automatically.
Macro Syntax
list-of::<element>[]
list-of::<element>[<positional-options>,key=value,...]
Positional Options
| Option | Effect |
|---|---|
|
In HTML mode, renders caption and title on separate spans rather than concatenated. |
|
Removes the containing section entirely if no captioned/titled elements of the requested type exist in the document.
In HTML mode, omitting this flag leaves an orphaned section heading with no content, which is rarely useful.
Kept as an explicit opt-in for backwards compatibility with the original |
|
PDF only. Omits this list from the PDF Table of Contents while keeping it in the bookmark outline. |
|
PDF only. Omits this list from the PDF bookmark outline while keeping it in the Table of Contents. |
|
PDF only. Removes the trailing period from caption signifiers, e.g. "Table 1." → "Table 1".
Overrides the theme |
|
PDF only. Renders the caption signifier (e.g. "Table 1") at the left margin and the title indented at the |
Named Parameters
| Parameter | Backend | Description |
|---|---|---|
|
Both |
Override the list heading. When set, takes precedence over the parent section title. |
|
Both |
Override the caption prefix label (e.g. |
|
Numeric (pt). Override the theme |
|
|
Numeric (pt). Override the theme |
Supported Element Types
Any valid Asciidoctor block context string works:
| Macro | Collects |
|---|---|
|
Blocks with |
|
Blocks with |
|
Blocks with |
|
Blocks with |
|
Any custom block type registered with that context name. |
Elements without a title or caption are silently skipped.
To explicitly exclude a titled/captioned element from all list-of:: lists, add the exclude-from-listof role:
[.exclude-from-listof]
.Internal Diagram
image::internal.png[]
This works for any block type — images, tables, listings, examples, or custom contexts.
Note: do not use [discrete] for this purpose; that style is only valid on heading blocks and will cause an error on other block types.
Document Attributes
Extension Attributes
| Attribute | Effect |
|---|---|
|
PDF only. Inserts the Table of Contents itself as the first entry in the PDF ToC and bookmark outline, before any |
Caption Attributes (standard Asciidoctor)
:figure-caption: Figure
:table-caption: Table
:listing-caption: Listing
:example-caption: Example
These control the prefix that appears in each list entry, e.g. Figure 1. My Diagram.
PDF Behaviour
In PDF mode the extension hooks into `asciidoctor-pdf’s ToC allocation/rendering lifecycle.
|
Important
|
PDF output requires |
-
allocate_toc— dry-runs each list to measure required page space, then reserves those pages immediately after the ToC. -
ink_toc— renders each list with ToC-style dot leaders and page numbers. -
The
== List of Figuressection and itslist-of::macro are removed from the document body so they do not appear again as body text.
Placement
Document-wide lists (macros inside a top-level == … section) are placed in the front matter (directly after the ToC) regardless of where in the source the list-of:: macros appear.
The macro order in the source determines the order of the lists in the front matter.
Blocks placed before the list-of:: macro in the same section (admonitions, notes, paragraphs) are moved to the front matter and rendered between the list heading and the first entry.
Blocks placed after the macro in the same section — for example a sidebar or admonition following a <<< page break — are re-inserted into the document body at the section’s former position and rendered there, not in the front matter.
The enclosing section is removed from the body so it does not appear twice.
Placing list-of:: macros inside any subsection generates section-scoped lists.
The scope is determined automatically from where the macro appears in the document hierarchy:
| Macro placement | Collection scope |
|---|---|
Inside a top-level section ( |
Document-wide — collects all matching elements in the entire document. |
Inside a subsection ( |
Section-scoped — collects only elements placed inside that same subsection. |
|
Important
|
For section-scoped lists, place the captioned content before the |
== List of Figures <b class="conum">(1)</b>
list-of::image[]
== Chapter 1
=== Chapter 1 Figures <b class="conum">(2)</b>
.Overview Diagram <b class="conum">(3)</b>
image::arch.png[]
.Detailed Flow
image::flow.png[]
list-of::image[] <b class="conum">(4)</b>
-
Document-wide: collects all images across the entire document.
-
Subsection that owns both the content and the scoped list.
-
Captioned images placed before the macro so page numbers are resolved.
-
Section-scoped: collects only images placed inside
=== Chapter 1 Figures.
Styling
Lists inherit all ToC styling from your PDF theme:
toc:
font_size: 10
line_height: 1.5
dot_leader:
font_color: '#CCCCCC'
content: '. '
levels: all
A list-specific title style can be added with a theme key based on the element type:
image_list_title:
font_size: 16
font_style: bold
text_align: center
Theme Keys (list-of)
List entry rendering can be configured globally via your PDF theme YAML under the list-of namespace.
Macro-level attributes always override theme values.
list-of:
title_heading_level: 1 # 1 = heading_h1 (== chapter); 2 = heading_h2 (===)
entry_level: 2 # 2 = toc_h3 (lofte-compat); 1 = toc_h2 (chapter style)
caption_style: split # default | split | strip
entry_indent: 58 # pt — left indent for entry title text
first_entry_margin: 10 # pt — vertical space before the first entry
image:
exclude_from_toc: false # default exclude for list-of::image[]
exclude_from_outline: false
table:
exclude_from_toc: false
exclude_from_outline: false
listing:
exclude_from_toc: false
exclude_from_outline: false
example:
exclude_from_toc: false
exclude_from_outline: false
| Key | Default | Description |
|---|---|---|
|
|
Heading level used to render list titles (e.g. "List of Figures"). |
|
|
Level used to style each list entry row via |
|
|
Controls how captioned list entries are rendered. |
|
|
Left indent (pt) for entry text.
In |
|
|
Extra vertical space (pt) inserted before the first entry of each list. |
|
|
Per-element-type default for omitting lists from the PDF Table of Contents.
Overridden by the |
|
|
Per-element-type default for omitting lists from the PDF bookmark outline.
Overridden by the |
Override priority: macro attribute > theme key > built-in default.
HTML Behaviour
In HTML5 mode the macro is replaced in-place with a list of cross-reference links:
<p><a href="#uuid-of-figure-1">Figure 1.</a> Architecture Diagram<br>
<a href="#uuid-of-figure-2">Figure 2.</a> Data Model<br></p>
The containing section and heading are rendered normally.
Full Example
= Technical Reference
:doctype: book
:toc:
:toclevels: 3
:figure-caption: Figure
:table-caption: Table
:listing-caption: Listing
== List of Figures
list-of::image[] <b class="conum">(1)</b>
== List of Tables
list-of::table[hide_empty_section] <b class="conum">(2)</b>
== List of Code Examples
list-of::listing[title="Code Examples"] <b class="conum">(3)</b>
== List of Appendix Examples
list-of::example[exclude_from_outline] <b class="conum">(4)</b>
== Chapter 1: Architecture
.System Overview
image::arch.png[]
.Configuration Parameters
|===
|Name |Default |Description
|timeout |30 |Seconds
|===
-
Plain usage — collects all images with captions. Appears in PDF ToC and outline by default.
-
If no tables exist, the "List of Tables" section is omitted entirely.
-
Per-list title override.
-
Appears in the PDF Table of Contents but not in the PDF bookmark outline.
Running the Tests
HTML test (requires only asciidoctor gem):
ruby test/run_html.rb
PDF test
Install dependencies once from inside the test/ directory:
cd test && bundle install
Then run the suite:
bundle exec ruby test/run_pdf.rb
Output files are written to test/out/.
VSCode AsciiDoc preview (HTML5)
The AsciiDoc extension for VS Code preview is supported:
-
A
.asciidoctor/lib/lists-extended.jslinker file is included in this repository — no setup needed. -
A
.vscode/settings.jsonis also included that enables workspace extensions:{ "asciidoc.extensions.registerWorkspaceExtensions": true }For an existing
.vscode/settings.json, add the key manually. -
Open any
.adocfile and use the AsciiDoc preview button (or kbd:[Ctrl+Shift+V]).
The preview uses the JS extension (js/lib/extension.js) via Asciidoctor.js.
The list-of:: macros render as <ul> lists with <a href="#…"> links.
Migration from asciidoctor-pdf-lofte
| Old (lofte) | New (lists-extended) |
|---|---|
|
|
|
Lists are included in the PDF ToC and bookmark outline by default.
Use |
Hardcoded line height / spacing / indentation in lofte converter |
Theme-driven via |
Required |
Auto-generated — no anchors needed |
PDF only |
HTML5 + PDF |
4 copy-pasted converter classes (~1200 lines) |
1 unified converter class (~550 lines) |
|
Virtual sections get |
|
|
Architecture Overview
lib/
asciidoctor-lists-extended.rb Main entry point (conditional PDF loading)
asciidoctor-lists-extended/
extensions.rb ListMacro + ListTreeprocessor
html_renderer.rb UUID → xref replacement for HTML5
pdf_renderer.rb Drawing primitives (ink_list_content, etc.)
pdf_converter.rb Lifecycle orchestration (allocate_toc, ink_toc)
Why pdf_converter.rb and pdf_renderer.rb are separate
pdf_converter.rb subclasses the asciidoctor-pdf converter and overrides the lifecycle hooks that the PDF engine calls during document generation.
It is responsible for orchestration: when to run, which pages to reserve, and in what order to render each list.
pdf_renderer.rb is a module mixed into the converter.
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.
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.
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.
Process flow
HTML backend
-
Macro registration (
extensions.rb) — when Asciidoctor parses alist-of::element[]macro,ListMacro#processruns. 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. -
Tree processing (
extensions.rb) — after the full AST is built,ListTreeprocessor#processruns. It first auto-generates IDs for every captioned/titled element referenced by anylist-of::macro (so manual[#anchor]blocks are not needed). It then delegates toHtmlRenderer#render. -
UUID replacement (
html_renderer.rb) —HtmlRendererfinds every UUID placeholder paragraph, looks up its config, collects the matching elements, and replaces the placeholder with xref links. Ifhide_empty_sectionis set and no entries exist, it removes the enclosing section entirely.
PDF backend
-
Macro registration — identical to HTML: UUID placeholder paragraph is inserted into the AST.
-
Tree processing — auto-generates IDs for referenced elements, then returns early (the placeholder paragraphs are left in place for the PDF converter to find).
-
allocate_toc(pdf_converter.rb) — called by asciidoctor-pdf before any body rendering, after the ToC pages are reserved.PDFConverterWithListscallssuperfirst (reserving the real ToC), then iterates over every UUID placeholder. 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-runsink_list_contentto measure how many pages the list needs and reserves that space. -
ink_toc(pdf_converter.rb) — called by asciidoctor-pdf when it is time to render the front matter. For each allocated list,ink_listnavigates to the reserved pages and callsink_list_content. A virtualSectionnode is always inserted into the document AST. Ifexclude_from_tocis set, the section is markedlist-exclude-from-tocso theget_entries_for_tocoverride hides it from the visible ToC — but it remains indoc.sections, so the PDF bookmark outline still sees it. Ifexclude_from_outlineis set, the section is markedlist-exclude-from-outlineso theadd_outline_leveloverride filters it from the bookmark tree. After all lists are rendered,superruns to render the real Table of Contents. -
ink_list_content(pdf_renderer.rb) — draws the heading (using the==section title captured in step 3) and then callsink_toc_levelto draw the dot-leader rows. -
ink_toc_leveloverride (pdf_converter.rb) — when@rendering_listis true (set only during list rendering), dispatches toink_list_toc_levelwhich usescaptioned_title(e.g. "Figure 1. My Diagram") for non-section entries instead of the bare title. For real ToC rendering the override is bypassed andsuperis called unchanged. -
get_entries_for_tocoverride (pdf_converter.rb) — called by asciidoctor-pdf when collecting sections to render in the Table of Contents. Filters out virtual list sections marked withlist-exclude-from-tocbefore delegating tosuper. Because the PDF outline builder readsdoc.sectionsdirectly (not via this method), excluded sections remain visible in the bookmark outline. -
add_outline_leveloverride (pdf_converter.rb) — called by asciidoctor-pdf when building the PDF bookmark outline. Filters out any virtual list sections marked withlist-exclude-from-outlinebefore delegating tosuper, implementing theexclude_from_outlineflag.
Known Limitations
toc-placement: macro-
When
:toc-placement: macrois set, asciidoctor-pdf defers itsallocate_toccall to the inlinetoc::[]macro during body rendering. This extension’sallocate_tocoverride therefore never runs, so UUID placeholder paragraphs are not processed and appear as raw text in the output. Use the default:toc:attribute (which places the ToC at the top of the front matter) instead. - Bare macros excluded from ToC and outline
-
When a
list-of::macro is placed outside any section heading (document preamble), the generated list has no title and is therefore excluded from the PDF Table of Contents and bookmark outline. Wrap the macro in a==section to give it a title and have it appear in the ToC. - CJK font support
-
asciidoctor-pdf does not bundle CJK fonts. Documents using Chinese, Japanese, or Korean text require a custom PDF theme that specifies a CJK-capable font family.
Licence
MIT