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

PDF Table of Contents showing list entries
Figure 1. Table of Contents with List of Figures, List of Tables, and List of Examples entries
List of Figures rendered in PDF front matter
Figure 2. List of Figures — dot-leader style matching the ToC
List of Listings rendered in PDF front matter
Figure 3. List of Listings
List of Examples rendered in PDF front matter
Figure 4. List of Examples
PDF bookmark outline panel
Figure 5. PDF bookmark outline showing list entries

Features

  • Works with HTML5 (xref links) and asciidoctor-pdf (dot-leader style matching the ToC)

  • No hardcoded [#anchor] required — IDs are auto-generated via SecureRandom.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 — same list-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
|===
  1. The section heading becomes the title in the PDF front matter.

  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.

  3. No [#anchor] needed — IDs are assigned automatically.

Macro Syntax

list-of::<element>[]
list-of::<element>[<positional-options>,key=value,...]

Positional Options

Option Effect

enhanced_rendering

In HTML mode, renders caption and title on separate spans rather than concatenated.

hide_empty_section

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 asciidoctor-lists gem.

exclude_from_toc

PDF only. Omits this list from the PDF Table of Contents while keeping it in the bookmark outline.

exclude_from_outline

PDF only. Omits this list from the PDF bookmark outline while keeping it in the Table of Contents.

strip_period

PDF only. Removes the trailing period from caption signifiers, e.g. "Table 1." → "Table 1". Overrides the theme caption_style for this macro (equivalent to caption_style: strip).

split_caption

PDF only. Renders the caption signifier (e.g. "Table 1") at the left margin and the title indented at the entry_indent offset. Overrides the theme caption_style for this macro (equivalent to caption_style: split).

Named Parameters

Parameter Backend Description

title

Both

Override the list heading. When set, takes precedence over the parent section title.

caption_prefix

Both

Override the caption prefix label (e.g. "Figure"). Currently informational; caption is taken from the element’s own captioned_title.

entry_indent

PDF

Numeric (pt). Override the theme entry_indent for this macro only.

first_entry_margin

PDF

Numeric (pt). Override the theme first_entry_margin for this macro only.

Supported Element Types

Any valid Asciidoctor block context string works:

Macro Collects

list-of::image[]

Blocks with context: :image that have a title or caption.

list-of::table[]

Blocks with context: :table that have a title or caption.

list-of::listing[]

Blocks with context: :listing (source code, literal) that have a title or caption.

list-of::example[]

Blocks with context: :example that have a title or caption.

list-of::video[]

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

:toc-in-toc:

PDF only. Inserts the Table of Contents itself as the first entry in the PDF ToC and bookmark outline, before any list-of:: sections. The entry title is taken from the :toc-title: attribute (default: Table of Contents).

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 :toc: in the document header. asciidoctor-pdf only calls allocate_toc and ink_toc when :toc: is set. Without it, list pages are never allocated or rendered.

  1. allocate_toc — dry-runs each list to measure required page space, then reserves those pages immediately after the ToC.

  2. ink_toc — renders each list with ToC-style dot leaders and page numbers.

  3. 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.

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 (== …)
e.g. a front-matter == List of Figures

Document-wide — collects all matching elements in the entire document.

Inside a subsection (=== …)
e.g. === Chapter 1 Figures inside == Chapter 1

Section-scoped — collects only elements placed inside that same subsection.

Important

For section-scoped lists, place the captioned content before the list-of:: macros within the subsection. This ensures all entries have already been rendered when the list runs, so page numbers show correctly (no ?).

Example: global list plus a per-section list
== 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>
  1. Document-wide: collects all images across the entire document.

  2. Subsection that owns both the content and the scoped list.

  3. Captioned images placed before the macro so page numbers are resolved.

  4. 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

title_heading_level

1

Heading level used to render list titles (e.g. "List of Figures").
1heading_h1 style, matching == chapter headings (default since 1.1.1).
2heading_h2 style, matching === section headings (pre-1.1.1 behaviour).

entry_level

2

Level used to style each list entry row via toc_h{level+1}.
2toc_h3 style (lofte-compatible default).
1toc_h2 style (chapter-level ToC appearance).

caption_style

default

Controls how captioned list entries are rendered.
default — full captioned_title as one string.
split — signifier (e.g. "Table 1") at left margin, title indented at entry_indent.
strip — full title with trailing period removed from the signifier.

entry_indent

0

Left indent (pt) for entry text. In split mode the signifier stays at 0 and the title starts at this offset. In default/strip modes the entire entry is indented.

first_entry_margin

0

Extra vertical space (pt) inserted before the first entry of each list.

<element>.exclude_from_toc

false

Per-element-type default for omitting lists from the PDF Table of Contents. Overridden by the exclude_from_toc positional flag on individual macros.

<element>.exclude_from_outline

false

Per-element-type default for omitting lists from the PDF bookmark outline. Overridden by the exclude_from_outline positional flag on individual macros.

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
|===
  1. Plain usage — collects all images with captions. Appears in PDF ToC and outline by default.

  2. If no tables exist, the "List of Tables" section is omitted entirely.

  3. Per-list title override.

  4. 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:

  1. A .asciidoctor/lib/lists-extended.js linker file is included in this repository — no setup needed.

  2. A .vscode/settings.json is also included that enables workspace extensions:

    {
      "asciidoc.extensions.registerWorkspaceExtensions": true
    }

    For an existing .vscode/settings.json, add the key manually.

  3. Open any .adoc file 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)

:lof-title: List of Figures
:lot-title: List of Tables

== List of Figures
list-of::image[]

== List of Tables
list-of::table[]

:include-lists-in-toc:

Lists are included in the PDF ToC and bookmark outline by default. Use exclude_from_toc or exclude_from_outline on individual macros to opt out, or set per-element-type defaults via theme keys. To add "Table of Contents" itself as the first entry in the ToC/outline, set :toc-in-toc:.

Hardcoded line height / spacing / indentation in lofte converter

Theme-driven via list-of YAML keys (caption_style, entry_indent, first_entry_margin). Macro-level overrides (split_caption, strip_period, entry_indent=X, first_entry_margin=X) still available.

Required [#anchor] on every block

Auto-generated — no anchors needed

PDF only

HTML5 + PDF

4 copy-pasted converter classes (~1200 lines)

1 unified converter class (~550 lines)

PDFConverterModifyRunningContent (custom running content)

Virtual sections get pdf-page-start automatically — base ink_running_content picks up list titles as {chapter-title}.

ModifyOutline (custom outline builder)

add_outline_level override filters excluded lists from the PDF bookmark outline.

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

  1. Macro registration (extensions.rb) — when Asciidoctor parses a list-of::element[] macro, ListMacro#process runs. 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.

  2. Tree processing (extensions.rb) — after the full AST is built, ListTreeprocessor#process runs. It first auto-generates IDs for every captioned/titled element referenced by any list-of:: macro (so manual [#anchor] blocks are not needed). It then delegates to HtmlRenderer#render.

  3. 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. If hide_empty_section is set and no entries exist, it removes the enclosing section entirely.

PDF backend

  1. Macro registration — identical to HTML: UUID placeholder paragraph is inserted into the AST.

  2. Tree processing — auto-generates IDs for referenced elements, then returns early (the placeholder paragraphs are left in place for the PDF converter to find).

  3. allocate_toc (pdf_converter.rb) — called by asciidoctor-pdf before any body rendering, after the ToC pages are reserved. PDFConverterWithLists calls super first (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-runs ink_list_content to measure how many pages the list needs and reserves that space.

  4. ink_toc (pdf_converter.rb) — called by asciidoctor-pdf when it is time to render the front matter. For each allocated list, ink_list navigates to the reserved pages and calls ink_list_content. A virtual Section node is always inserted into the document AST. 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. 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. After all lists are rendered, super runs to render the real Table of Contents.

  5. 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.

  6. 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. For real ToC rendering the override is bypassed and super is called unchanged.

  7. get_entries_for_toc override (pdf_converter.rb) — called by asciidoctor-pdf when collecting sections to render in the Table of Contents. Filters out virtual list sections marked with list-exclude-from-toc before delegating to super. Because the PDF outline builder reads doc.sections directly (not via this method), excluded sections remain visible in the bookmark outline.

  8. add_outline_level override (pdf_converter.rb) — called by asciidoctor-pdf when building the PDF bookmark outline. Filters out any virtual list sections marked with list-exclude-from-outline before delegating to super, implementing the exclude_from_outline flag.

Known Limitations

toc-placement: macro

When :toc-placement: macro is set, asciidoctor-pdf defers its allocate_toc call to the inline toc::[] macro during body rendering. This extension’s allocate_toc override 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