Skip to content

PDF style pass: bigger chapter titles, labels below title, real TOC nesting, closing License page - #148

Merged
recrwplay merged 7 commits into
devfrom
pdf-style-polish
Oct 8, 2026
Merged

recrwplay merged 7 commits into
devfrom
pdf-style-polish

Conversation

@recrwplay

@recrwplay recrwplay commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Prompted by comparing a real build (docs-java-reference) against the legacy pipeline's own published PDF (neo4j-java-reference-2026.09.pdf):

  • Chapter titles more prominent: bumped h2 (every merged page's own top-level heading) from 18pt to 26pt - it read as barely more prominent than body text next to the legacy pipeline's much larger chapter titles. Tested locally.
  • Full h2-h6 heading scale, not just h2-h4: a page nested two levels deep in its component's nav has its own internal headings shifted down by that same nav depth once merged into this book - a heading the source HTML renders as h3/h4 can land as h5/h6 here. Confirmed directly on docs-java-reference's "Set up a Maven project" (HTML h3, merged-book h5) and "DatabaseSeedProvider" (HTML h4, merged-book h6, Asciidoctor's deepest possible level) - both were completely unstyled before this and fell back to the renderer's tiny default sizes, barely legible next to 10.5pt body text. Now: h3 18pt, h4 14pt, h5 12pt, h6 11pt italic (no more room to shrink into at h6, so it's differentiated by style instead of size).
  • Labels always below the title, never inline to the right: a role-based header label (e.g. label:new[]) used to sit inline to the right of its heading via CSS copied from the real site, where responsive JS adds a .wrapped class to drop it below the title when there's no room. That JS never runs in this static PDF render, so it always took the "plenty of room" layout regardless of the page's actual fixed, narrow content width. Now always renders below the title.
  • A heading's labels can no longer split onto the next page on their own: .header-label-container is always the heading element itself, with its labels div appended as a child, not a sibling - without break-inside: avoid, a heading whose title text just barely fit at the bottom of a page could have its labels div alone pushed onto the next page, split from the title it belongs to. Reported directly after the "labels below title" change above made this visible.
  • TOC nesting/indentation actually works now: this was a complete no-op bug - the CSS targeted sectlevel0/sectlevel1, but Asciidoctor's own TOC markup is 1-indexed (sectlevel1/sectlevel2/sectlevel3, there is no sectlevel0) - so every PDF this pipeline has ever shipped had a flat, unindented TOC regardless of the toclevels setting. Fixed the selectors and bumped toclevels from 2 to 3 to go one level deeper, matching the legacy PDFs' own TOC depth. (Checked the TOC-length impact on Operations Manual specifically, since it's one of the biggest docsets: 153 entries at toclevels 2 vs 256 at toclevels 3 - a real jump, but still well under half the length of that docset's current ~20-page published TOC, so not a regression risk. A per-docset toclevels override is a reasonable future addition if a particular docset wants to dial it back down.)
  • Closing License page: every PDF now gets a CC BY-NC-SA 4.0 license page appended at the end, matching the old Gradle pipeline's own unconditional pdfNav append of the same page. Vendored into this package rather than read from a consuming docset's own content tree - a couple of repos (docs-aura, docs-ops-manager) still carry a copy of this exact page left over from the old pipeline, but it's an orphan page absent from their nav, so this pipeline never saw it either way. [discrete] keeps it out of the TOC, like a real book's colophon page.
  • Copyright/trademark colophon page: every PDF also now gets a one-time Neo4j copyright/trademark notice right after the cover page and before the TOC - distinct from the closing License page above (that's CC BY-NC-SA licensing terms; this is Neo4j's own trademark notice). An alternative to running @neo4j-antora/mark-terms in this pipeline, which marks a term's first use per page - a notion this pipeline's single merged document has no equivalent of.
  • Opt-in MathJax/stem support: patches @djencks/asciidoctor-mathjax's pre-4.x Opal-bridge API calls to work against the 4.x Asciidoctor.js engine this pipeline vendors (same class of fix as the existing macros/remote-include adapters). Registered only when the docset being built actually has that dependency installed, so every other docset's PDF build is unaffected by its absence. Currently unexercised - no docset in the org declares that dependency yet, even ones using stem:: blocks (docs-operations, docs-glossary), which presumably rely on MathJax loaded client-side for HTML - that can't help a static PDF render.
  • Fixed broken cross-manual/javadoc links: every docset's neo4j-docs-base-uri (and the per-manual *-base-uri attributes built from it - javadocs, operations-manual, cypher-manual, ...) is deliberately root-relative (/docs/...) for the live website - correct in a browser, but a standalone PDF opened via file:// has no origin to resolve that against, so the link silently does nothing when clicked (reported directly: a "ding" sound in Preview, no navigation, on the Javadocs link and "Operations Manual -> Securing extensions"). The old Gradle pipeline hardcoded its own base-uri attributes to always be absolute, by name, for every manual - not practical here (no equivalent master list, and it would miss a genuine Antora cross-component xref resolving the same way), so absolute-links-postprocessor.js rewrites every root-relative <a href="/..."> to an absolute URL after rendering instead, catching any cause with one general fix.
  • That origin now reflects the actual publish environment: reusable-docs-pdf-build.yml gained a publish-env input, passed to Antora as --url exactly like reusable-docs-build.yml's own DOCS_PUBLISH_URL already does for HTML - it cascades into the docset's own site-url, which convert.js reads back out to resolve the postprocessor's rewrite origin above. Without this, a PDF built for dev would still link to the real prod site rather than the dev sandbox. Verified directly: passing a sentinel --url through a real build correctly rewrote every cross-manual/javadoc link to that origin. The three repos that call this workflow need their own small follow-up PR to actually pass the new input through from their own resolve-env job - filed and included below.

The colophon and MathJax support were ported fresh from an orphaned pdf-generator-package branch (not cherry-picked - that branch predates several fixes already shipped on dev, e.g. the docdir/-B image fix and the image max-width CSS).

Bumped pdf-generator to 0.1.6.

Dependent PRs (pass publish-env through from each repo's own docs-generate-pdf.yml)

Test plan

  • Rebuilt docs-java-reference's real PDF locally against each change and visually inspected: cover/chapter title size, a labeled heading ("Reload procedures and functions") rendering labels below the title and staying together across page boundaries, the TOC's indentation and bold top-level entries, the closing License page and the new Copyright & Trademarks colophon page each landing on their own page and excluded from the TOC, and the full heading scale down to h6.
  • Confirmed the MathJax adapter is skipped cleanly (no error) when a docset doesn't have @djencks/asciidoctor-mathjax installed, as is currently true for every docset checked.
  • docs-java-reference's own content doesn't have real third-level nav nesting, so the sectlevel3 CSS rule and toclevels: 3 bump are verified correct by inspecting Asciidoctor's own emitted TOC markup directly (sectlevel1/sectlevel2/sectlevel3), and separately by a live three-deep example on docs-getting-started once rebuilt locally.
  • Verified via pikepdf: all 128 link annotations in a real docs-java-reference build now resolve to absolute URLs, zero file:// links remain, including the two originally-reported links.
  • Also tried a build against docs-operations (bigger docset) to stress-test further, but it hit the existing 180s Puppeteer render timeout on a later attempt - a separate, pre-existing issue unrelated to this PR's changes.

…esting, closing License page

Prompted by comparing a real build (docs-java-reference) against the legacy
pipeline's own published PDF:

- Bumped h2 (this book's chapter-title level - every merged page's own
  top-level heading) from 18pt to 26pt; it read as barely more prominent than
  body text next to the legacy pipeline's own much larger chapter titles.
- A role-based header label (e.g. label:new[]) used to sit inline to the
  right of its heading via CSS copied from the real site, where responsive
  JS adds a `.wrapped` class to drop it below the title when there's no room
  - that JS never runs in this static PDF render, so it always took the
  "plenty of room" layout regardless of the page's actual fixed, narrow
  content width. Now always renders below the title, matching the `.wrapped`
  behavior outright.
- Fixed the TOC indentation/bold styling, which was a complete no-op: the
  CSS targeted sectlevel0/sectlevel1, but Asciidoctor's own TOC markup is
  1-indexed (sectlevel1/sectlevel2/sectlevel3, no sectlevel0) - so every PDF
  this pipeline has shipped had a flat, unindented TOC regardless of
  toclevels. Also bumped toclevels from 2 to 3 to go one level deeper,
  matching the legacy PDFs' own TOC depth.
- Every PDF now gets a closing License page (CC BY-NC-SA 4.0), matching the
  old Gradle pipeline's own unconditional pdfNav append of the same page.
  Vendored here rather than read from a consuming docset's own content tree
  - a couple of repos still carry a copy of this exact page left over from
  the old pipeline, but it's an orphan page absent from their nav, so this
  pipeline never saw it either way. [discrete] keeps it out of the TOC, like
  a real book's colophon page.

Bumped pdf-generator to 0.1.6.
…tiny

A page nested two levels deep in its component's nav has its own internal
headings shifted down by that same nav depth once merged into this book,
same as the page's own top heading is. A heading the source HTML renders as
h3/h4 can land as h5/h6 here - confirmed directly on docs-java-reference's
"Set up a Maven project" (HTML h3, merged-book h5) and "DatabaseSeedProvider"
(HTML h4, merged-book h6, Asciidoctor's deepest possible level). h5/h6 had no
size rule at all before this, so they fell back to the renderer's tiny
unstyled defaults - barely legible next to 10.5pt body text. Also bumped h3
(18pt) and h4 (14pt) up a step so the full h2-h6 scale stays proportional.
.header-label-container is always the heading element itself with its
labels div appended as a child, not a sibling - without break-inside: avoid,
a heading whose title text just barely fits at the bottom of a page could
have its labels div alone pushed onto the next page, split from the title it
belongs to. Reported directly: a title landed at the bottom of one page with
its labels flowed onto the top of the next.
Both ported fresh from the orphaned pdf-generator-package branch (not
cherry-picked - that branch predates several fixes already shipped on dev,
e.g. the docdir/-B image fix and the image max-width CSS).

- colophon-postprocessor.js inserts a one-time Neo4j copyright/trademark
  notice right after the cover page and before the TOC, on every PDF -
  distinct from the closing License page (CC BY-NC-SA 4.0 licensing terms)
  convert.js already appends at the end. An alternative to running
  @neo4j-antora/mark-terms in this pipeline, which marks a term's first use
  *per page* - a notion this pipeline's single merged document has no
  equivalent of.
- mathjax-adapter.js patches @djencks/asciidoctor-mathjax's pre-4.x
  Opal-bridge API calls to work against the 4.x Asciidoctor.js engine this
  pipeline vendors (same class of fix as macros-adapter.js/
  remote-include-adapter.js). Registered only when the docset being built
  actually has that dependency installed - it's an opt-in feature a docset
  adds itself for stem/latexmath content, not a dependency of this package,
  so every other docset's PDF build stays unaffected by its absence.
  Currently unexercised: no docset in this org declares that dependency yet,
  even ones using stem:: blocks (docs-operations, docs-glossary) - those
  presumably rely on MathJax loaded client-side for HTML, which can't help
  a static PDF render.
…h no origin)

Every docset's neo4j-docs-base-uri (and the per-manual *-base-uri attributes
built from it - javadocs, operations-manual, cypher-manual, ...) is
deliberately root-relative (/docs/...), correct inside a browser loaded
from https://neo4j.com, where a path starting with "/" resolves against
that origin. A standalone PDF has no such origin: opened via file://, that
same href resolves to a literal, nonexistent local path
(file:///docs/operations-manual/...), so the link does nothing when
clicked - reported directly (a "ding" sound in Preview, no navigation) on
docs-java-reference's own Javadocs and "Operations Manual -> Securing
extensions" links.

The old Gradle pipeline hit the same problem and hardcoded its own base-uri
playbook attributes to always be absolute, by name, for every manual - not
practical here (this pipeline has no equivalent master list, and a
hardcoded list would miss a genuine Antora cross-component xref resolving
to a root-relative href the same way). Rewriting every root-relative link
after rendering, regardless of what produced it, handles both cases with
one general fix.

Verified directly: all 128 link annotations across a real docs-java-reference
build now resolve to https://neo4j.com/... absolute URLs, zero file:// links
remain - confirmed via pikepdf, including the two reported links.
Same dev/prod split as reusable-docs-build.yml's own DOCS_PUBLISH_URL,
passed to Antora as --url - cascades into the docset's own site-url, which
assembler passes on to build.command as a plain -a flag, which convert.js
reads back out to resolve absolute-links-postprocessor.js's rewrite origin
(see pdf-style-polish's own earlier commit). Without this, every PDF always
resolved cross-manual/javadoc links against the docset's own publish.yml
default (the real prod URL), regardless of which environment it was
actually being published to.

Verified directly: passing --url http://sentinel-test.example through a
real build correctly rewrote every cross-manual/javadoc link in the output
to that origin, confirming the override cascades as expected; reverted to
testing the real https://neo4j.com/docs origin afterward.

Callers (docs-generate-pdf.yml in docs-template and already-migrated
consumer repos) still need their own PR to actually pass this new input
through from their own resolve-env job - filed separately per repo.
… site-url

Reads the real site-url @antora/assembler already passes to build.command
(the same canonical URL reusable-docs-build.yml's HTML build uses) back out
of the CLI args, instead of a hardcoded 'https://neo4j.com' constant, so it
can't drift from whatever a docset's own playbook - or, after the previous
commit, publish-env's own --url override - actually resolves to. Falls back
to the same hardcoded production origin if site-url is ever unavailable.
@neo4j-docops-agent

neo4j-docops-agent commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Thanks for the documentation updates.

The preview documentation has now been torn down - reopening this PR will republish it.

@recrwplay
recrwplay merged commit 9af73ee into dev Oct 8, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants