diff --git a/CHANGELOG.md b/CHANGELOG.md index 5507626..79e6ae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,14 @@ and the project follows [Semantic Versioning](https://semver.org/). needs the import updated. `BuiltinRenderers` drops from ~750 to ~440 lines. ### Added +- **Book-style page-numbered table of contents — `BookTocRenderer`.** An opt-in alternative to + the default `[TOC]` link list: swap the `TocNode` renderer + (`MarkdownTheme.builder(base).renderer(TocNode.class, new BookTocRenderer("Contents"))`) and + the marker renders as dot-leader contents rows — "Introduction ….. 3" — with **page numbers + resolved automatically from the laid-out document** (the engine's `addTableOfContents`; no + manual two-pass) and every label a clickable jump. Heading nesting indents the label; an + optional title row; empty-heading and no-heading documents degrade exactly like the default + renderer. Runnable `BookTocExample` (paired with the page-number footer). - **Vector colour emoji out of the box — optional `graph-compose-emoji` support.** With the companion artifact on the classpath (declared `optional`, mirroring `graph-compose-fonts`), `:shortcode:` emoji render as crisp Noto vector glyphs at any size — no user-supplied images. diff --git a/README.md b/README.md index 8f1665c..e8000ee 100644 --- a/README.md +++ b/README.md @@ -254,7 +254,15 @@ declares a **GitHub-style anchor**, so `[text](#heading)` links jump straight to it as native PDF go-to actions (footnote markers jump to their note and back the same way). A standalone **`[TOC]`** (or `[[_TOC_]]`) line expands into an **auto-generated, clickable table of contents** — one link per heading, -nested by level — that you can drop anywhere, including above the headings it lists. +nested by level — that you can drop anywhere, including above the headings it lists. Prefer +a print-style contents page? Swap in the **book TOC** — dot leaders and **live page +numbers**, resolved from the laid-out document: + +```java +MarkdownTheme book = MarkdownTheme.builder(DefaultMarkdownTheme.light()) + .renderer(TocNode.class, new BookTocRenderer("Contents")) + .build(); +``` Content the library does not model (raw HTML blocks, inline HTML) is **surfaced as raw text rather than silently dropped**; `MarkdownComposer.builder().strictMode(true)` diff --git a/examples/README.md b/examples/README.md index ec40e17..75cd3db 100644 --- a/examples/README.md +++ b/examples/README.md @@ -34,6 +34,7 @@ From this `examples/` directory (use `../mvnw.cmd` on Windows): | `…examples.FooterExample` | A running footer with `Page N of M` page numbers (`FooterTokens.pageNumbers()`) | `footer.pdf` | | `…examples.PngExportExample` | Rasterize straight to PNG page images via `toImages(dpi)` — no PDF round-trip | `png-export-p*.png` | | `…examples.VectorEmojiExample` | `:shortcode:` emoji as crisp vector glyphs via the optional `graph-compose-emoji` artifact | `vector-emoji.pdf` | +| `…examples.BookTocExample` | Book-style `[TOC]`: dot leaders + live page numbers via `BookTocRenderer` | `book-toc.pdf` | | `…examples.EmojiExample` | Emoji shortcodes → inline images via `ClasspathEmojiResolver` | `emoji.pdf` | | `…examples.FrontMatterExample` | A YAML `---` front-matter title block | `front-matter.pdf` | diff --git a/examples/src/main/java/io/github/demchaav/markdown/examples/BookTocExample.java b/examples/src/main/java/io/github/demchaav/markdown/examples/BookTocExample.java new file mode 100644 index 0000000..a7a6343 --- /dev/null +++ b/examples/src/main/java/io/github/demchaav/markdown/examples/BookTocExample.java @@ -0,0 +1,50 @@ +package io.github.demchaav.markdown.examples; + +import io.github.demchaav.markdown.composer.MarkdownComposer; +import io.github.demchaav.markdown.model.TocNode; +import io.github.demchaav.markdown.render.BookTocRenderer; +import io.github.demchaav.markdown.theme.DefaultMarkdownTheme; +import io.github.demchaav.markdown.theme.MarkdownTheme; +import io.github.demchaav.markdown.theme.tokens.FooterTokens; + +import java.nio.file.Path; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * A book-style, page-numbered table of contents: swap the {@code TocNode} renderer for + * {@link BookTocRenderer} and a {@code [TOC]} marker renders as dot-leader rows — + * "Introduction ..... 3" — with the page numbers resolved automatically from the laid-out + * document and every label a clickable jump. Paired here with the page-number footer, the + * classic report look. The default renderer (a plain clickable link list) stays as-is for + * screen-oriented documents. + * + *
+ * ../mvnw -f pom.xml exec:java \ + * -Dexec.mainClass=io.github.demchaav.markdown.examples.BookTocExample + *+ */ +public final class BookTocExample { + + private BookTocExample() { + } + + public static void main(String[] args) throws Exception { + String filler = IntStream.range(0, 45) + .mapToObj(i -> "Filler prose paragraph so the chapters land on later pages.") + .collect(Collectors.joining("\n\n")); + String markdown = "[TOC]\n\n# Introduction\n\nWelcome.\n\n" + filler + + "\n\n# Getting started\n\nSetup steps.\n\n## Configuration\n\nDetails.\n\n" + filler + + "\n\n# Appendix\n\nReference material.\n"; + + MarkdownTheme base = DefaultMarkdownTheme.light(); + MarkdownTheme bookTheme = MarkdownTheme.builder(base) + .renderer(TocNode.class, new BookTocRenderer("Contents")) // page-numbered TOC + .tokens(base.tokens().withFooter(FooterTokens.pageNumbers())) // matching footer + .build(); + + Path out = Path.of("book-toc.pdf"); + MarkdownComposer.create(bookTheme).render(markdown).writePdf(out); + System.out.println("Wrote " + out.toAbsolutePath() + " (dot-leader contents with live page numbers)"); + } +} diff --git a/src/main/java/io/github/demchaav/markdown/render/BookTocRenderer.java b/src/main/java/io/github/demchaav/markdown/render/BookTocRenderer.java new file mode 100644 index 0000000..47f6955 --- /dev/null +++ b/src/main/java/io/github/demchaav/markdown/render/BookTocRenderer.java @@ -0,0 +1,96 @@ +package io.github.demchaav.markdown.render; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.style.DocumentLeader; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import io.github.demchaav.markdown.model.TocNode; +import io.github.demchaav.markdown.theme.style.InlineStyle; + +import java.util.List; + +/** + * A book-style alternative to {@link TocRenderer}: renders a {@code [TOC]} marker as a native, + * page-numbered table of contents — each heading becomes a row whose clickable label jumps to + * the heading, a dotted leader fills the gap, and the page number is resolved automatically + * from the laid-out document (the engine performs the second layout pass; no manual + * bookkeeping). Opt in by swapping the renderer for the {@code TocNode} type: + * + *
{@code
+ * MarkdownTheme theme = MarkdownTheme.builder(DefaultMarkdownTheme.light())
+ * .renderer(TocNode.class, new BookTocRenderer())
+ * .build();
+ * }
+ *
+ * The default {@link TocRenderer} (a plain clickable link list, no page numbers) stays the + * screen-oriented default. Heading nesting is shown by indenting the label with non-breaking spaces — + * the engine's contents rows are flat. Empty-text headings are skipped; a document with no + * headings renders nothing.
+ */ +public final class BookTocRenderer implements NodeRenderer