Skip to content

Feature/pdf export - #268

Merged
seebi merged 14 commits into
mainfrom
feature/pdf-export
Sep 14, 2026
Merged

seebi merged 14 commits into
mainfrom
feature/pdf-export

Conversation

@rpietzsch

@rpietzsch rpietzsch commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

add a single-file PDF export of the whole documentation

💪 What

  • Adds dec-tool build-pdf, wired to task pdf: builds the site, assembles one linearized HTML document,
    serves it and prints it with headless Chrome to dist/documentation-eccenca-com-<version>.pdf.
  • tools/build_pdf.py walks nav.yml, splices each page's rendered <article> out of site/, namespaces
    per-page element ids so 657 pages can share one document, resolves cross-page links into in-PDF anchors,
    absolutizes assets, and demotes headings by nav depth so the PDF outline mirrors the navigation tree.
  • Adds overrides/print_cover.html, the cover page stamped with the build version, the generation date and
    the copyright line.
  • Points links the document cannot resolve within itself — downloadable resources, full-size screenshots,
    pages outside the navigation — at the published site under the built version, or under latest for a dev
    build.
  • Declares beautifulsoup4 and pyyaml in pyproject.toml: tools/build_pdf.py imports both directly, and
    bs4 used to arrive transitively via mkdocs-swagger-ui-tag, which the zensical migration dropped.
  • Hides the cookie consent dialog and its backdrops in print output.

🤔 Why

  • The export was built on mkdocs-print-site-plugin, which supplied the /print_page/ skeleton. Zensical
    does not run MkDocs plugins, so that page stopped being generated and task pdf had been dead since the
    migration.
  • nav.yml replaces the plugin as the page order. It is generated from the .pages files and gated by
    check:navigation, so the PDF cannot drift from the sidebar, and it carries nav depth explicitly — which
    the heading demotion previously had to reconstruct from URL slugs.
  • Unresolved links were left root-absolute, so Chrome baked the address of the throwaway print server into the
    PDF: 697 links to http://127.0.0.1:<port>/, plus the logo on the cover.
  • The script started out as a standalone helper invoked as python tools/build_pdf.py, against what
    tools/README.md says about the directory — every script there is a dec-tool subcommand and none is meant
    to be called directly. It is now a click command like the others: four options with environment-variable
    fallbacks instead of env-only tunables, click.ClickException instead of print-to-stderr plus a return
    code, and a row in the command table.
  • Also drops what the retired plugin left behind: an @page block that styled page footers the plugin used to
    generate — dead now that the export prints with --no-pdf-header-footer, and never honoured anyway since
    Chrome implements no CSS margin boxes.

👩‍🔬 How to validate

Needs a Chromium-family browser. The default is macOS Google Chrome; override with --chrome or CHROME=.

task pdf                              # writes dist/documentation-eccenca-com-<version>.pdf
poetry run dec-tool build-pdf --help  # the options and their defaults
  • Last run: 657 pages merged, 0 missing, 1422 PDF pages, ~119 MB.
  • Page 1 is the cover, complete on one page — brand, title, version, generation date, site
    copyright line. Page 2 starts the first content page.
  • The bookmark outline nests the same way the sidebar does.
  • Follow an internal cross-reference — it jumps inside the PDF. Follow a download link or a
    screenshot — it opens documentation.eccenca.com under the built version.
  • Confirm the scope is intended: the homepage, tags.md, testing.md and the deliberately off
    Started tutorial are not included.
  • dec-tool build-pdf --chrome /nonexistent exits 1 with a plain Error: line rather than a t
  • task check passes. The PDF itself is not part of the gate and dist/ is gitignored.

rpietzsch and others added 8 commits June 5, 2026 19:02
Catches the branch up on three months of main, including the Zensical
migration, which rewrote every file this branch touches.

Conflict resolutions:

- pyproject.toml, poetry.lock: take main's Zensical stack, keep
  mkdocs-print-site-plugin. Add beautifulsoup4 explicitly: build_pdf.py
  imports it and used to get it transitively from mkdocs-swagger-ui-tag,
  which the migration dropped. Lock regenerated rather than hand-merged.
- mkdocs.yml: take main's plugin list, reinstate the print-site block in
  the same documented-as-inert style main uses for `social` and
  `git-revision-date-localized`.
- Taskfile.yml: take main's `zensical serve`; keep the `pdf` target,
  rewired from `mkdocs build` onto `task build`. Drop the Insiders
  switching tasks, which main removed along with the MATERIAL_* vars
  they read.
- docs/assets/extra.css: keep both - the print-media consent rules and
  main's .md-clear-highlight button style are independent.

`task check` passes. `task pdf` does not: Zensical does not run MkDocs
plugins, so /print_page/ is never generated and build_pdf.py stops on its
own guard. Porting the PDF pipeline off mkdocs-print-site-plugin is
outstanding work, noted in mkdocs.yml and the task summary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The PDF export was built on mkdocs-print-site-plugin, which supplied the
/print_page/ skeleton. Zensical does not run MkDocs plugins, so that page
stopped being generated and `task pdf` had been dead since the migration.

nav.yml replaces it as the page order. It is generated from the .pages
files and gated by `check:navigation`, so the PDF can no longer drift from
the sidebar, and it carries nav depth explicitly - which the heading
demotion previously had to reconstruct from URL slugs.

build_pdf.py now assembles the document itself: it walks nav.yml, splices
each page's rendered <article> out of site/, namespaces per-page element
ids so 656 pages can share one document, resolves cross-page links into
in-PDF anchors, absolutizes assets, and demotes headings by nav depth so
the PDF outline mirrors the navigation tree. The link rewriting, cover
stamping and headless-Chrome printing are the branch's original code.

Drops mkdocs-print-site-plugin from mkdocs.yml and pyproject.toml.
Declares beautifulsoup4 and pyyaml, which the script imports directly -
bs4 used to arrive transitively via mkdocs-swagger-ui-tag, dropped in the
migration.

Scope: the 656 pages in nav.yml. The homepage, tags.md, testing.md and the
deliberately off-nav Getting Started tutorial are not included.

Verified: 656 pages merged, 0 missing, 1420 PDF pages, 5799 outline
entries, 659 images all resolving, 17136 internal anchors with no
tool-introduced dangling links. `task check` passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Links the merged document could not resolve inside itself were left
root-absolute, so Chrome baked the address of the throwaway print server
into the PDF: 697 links to http://127.0.0.1:<port>/, plus the logo on the
cover.

- resolve downloadable resources, full-size screenshots and pages outside
  the navigation against the published site under the built version, or
  under latest for a dev build
- leave image sources root-absolute, they are embedded while printing
The @page block styled footers that mkdocs-print-site-plugin generated.
The plugin is gone, build_pdf.py prints with --no-pdf-header-footer, and
Chrome implements no CSS margin boxes, so the rules did nothing.

The cover comment explained its height with an @page margin that
print-site-material.css no longer sets. In Chrome 100vh resolves to the
page area, but the cover starts below it, under the print header and the
main wrapper padding - measured on Letter, 15mm of slack still breaks the
copyright line onto a second page and 20mm fits.

- remove the @page footer block from docs/assets/extra.css
- record the real reason for the 50mm on the cover height
@rpietzsch
rpietzsch requested a review from seebi September 11, 2026 13:21
@rpietzsch
rpietzsch marked this pull request as draft September 11, 2026 13:21
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Test Results

0 tests   0 ✅  0s ⏱️
0 suites  0 💤
1 files    0 ❌

Results for commit ac4db32.

♻️ This comment has been updated with latest results.

@rpietzsch
rpietzsch marked this pull request as ready for review September 11, 2026 13:31
tools/ is the dec-tool package: every script in it is a subcommand and
none is meant to be called directly. build_pdf.py was the exception - it
was invoked as `python tools/build_pdf.py` and took its settings from the
environment only.

- turn main() into the click command `build-pdf` with --chrome,
  --build-version, --output-file and --port, each keeping its environment
  variable as a fallback
- raise click.ClickException instead of printing to stderr and returning
  an exit code
- register the command, call it from `task pdf`, list it in tools/README.md

@seebi seebi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some comments:

  • is the PDF served with the website?
  • Can we add a TOC
  • We have a document template (created by the policy group) - should we re-use this?

The Chrome print of the merged site could not carry the eccenca house
style, and admonitions, tabs and code printed with their web look.

- merge site/ along nav.yml, normalize the HTML, convert it with pandoc
  and a Lua filter, typeset it with Typst (tools/pdf/)
- house style from the policy template: title page, contents on their
  own pages, chapter breaks, running chapter footer, size-base variable
- vendor Roboto, Roboto Mono, Noto Color Emoji and DejaVu fonts with
  their full licence texts, listed in the README
- add headings for nav sections without an index page (Release Notes)
- shrink terminal tables to fit, keep cards whole
- drop the Chrome cover template and its print CSS
- embed the package lifecycle diagram as PNG; its SVG labels are
  foreignObject elements, which Typst renders empty
@rpietzsch

Copy link
Copy Markdown
Contributor Author
  • not served (at least not planned so far), however we could build the pdf with a gitlab action upon main-push?
  • TOC added
  • house-style applied
    • this required extensions to the typst template which need to be merged with the policies template as a follow-up task

@seebi seebi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • the TOC is already long but a little bit useless at the same time, because too less levels are shown

    • I suggest to add another level (for build it does not make sense but most other of the parts have a useful second level
      -- OR we introduce Parts-Structuring so that each part has its own TOC
  • I would also like to see the commit hash in the "version ID"

    • Generated 2026-09-14 -> Generated 2026-09-14 (31b5663)
  • The footer should show more the just the Part Name (e.g Automate) but the next TOC level

  • not all external links are market as external links (e.g. the Red Hat Universal Base Image 10 Minimal)

  • The icons should be stripped from the TOC (similar to inline PDF TOC)

  • Important Question for the TOC: Should we introduce Numbering?

rpietzsch and others added 3 commits September 14, 2026 16:50
Reviewer feedback: the contents were too shallow, the stamp did not name
a commit, the footer showed only the part, and external links looked
like jumps within the book.

- letter the parts (A-I), number their pages from 1, down to A.1.1
- open each part with a cover page and its own contents, and keep only the parts in the front contents
- stamp the commit next to the build date, marked -dirty when tracked files have uncommitted changes
- show part and page in the footer, e.g. A Build > A.1 Introduction
- mark links that leave the PDF with an arrow
- drop icons from headings
- build the PDF on every push to main (.github/workflows/pdf.yml)
- nest the 20.03 and 20.10 cmemc and migration notes under their release
Operators in the task and operator reference and the component notes
of each release had no contents entry.
@seebi
seebi enabled auto-merge September 14, 2026 15:29
@seebi
seebi merged commit c20d74b into main Sep 14, 2026
3 checks passed
@seebi
seebi deleted the feature/pdf-export branch September 14, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants