Building a PDF¶
Added in version 0.35.0.
ubc build pdf renders your project into a single PDF —
the documents your root toctree reaches, in the order it puts them,
concatenated into one paginated file with an outline, internal links and page numbers.
It is ubc doing the typesetting, not Sphinx and not LaTeX:
there is no conf.py, no extension loading, no Python environment
and no TeX installation involved,
so the PDF comes out of the same index the editor and ubc check already use —
the same one ubc build html renders the site from.
Caution
ubc build pdf is in alpha.
The command works and is under active development,
but the typeset output, the theming surface and the available options
may still change between releases,
so it is not yet recommended for production deliverables.
Some constructs are not typeset yet and are drawn as labelled placeholder boxes —
see What is not rendered yet for the list.
Pin your ubc version if you depend on the output.
We would love your feedback.
Quickstart¶
Run the command from anywhere inside your project:
ubc build pdf
It discovers the closest ubproject.toml, indexes the project,
and writes the PDF under that file’s directory:
Building index for: /home/me/project/ubproject.toml
Processing 3 files
3 document(s) rendered to 4 page(s); 0 placeholder(s).
PDF written to: /home/me/project/_build/pdf/index.pdf
Done in 0.07s
The count line is the one to read: how many documents went in, how many sheets came out, and how many constructs were drawn as placeholder boxes rather than typeset (What is not rendered yet). Four pages for three documents is the title page — it is on by default, and it is not counted as a document. On a terminal the run also draws its progress as it goes, one row per phase — see while it works.
The file name comes from [project] root_doc,
so a project whose root document is manual writes _build/pdf/manual.pdf.
Principal options¶
Option |
What it does |
|---|---|
|
The project root to build. Defaults to the current directory. |
|
The file to write — not a directory, unlike |
|
Print the detail of every build and indexing warning. A build that fails the quality gate prints it anyway, so this matters mainly once the gate has been lowered (see Warnings and the quality gate). |
|
Move the failure threshold for this run:
|
|
Fail if more than |
|
Index from scratch rather than reading the on-disk cache. It also re-renders every external PlantUML diagram and refreshes the shared store (see Rebuilds). |
|
Include the document that holds this need, whole. Repeatable. |
|
Include the documents that hold the needs this expression matches, whole. |
|
Additional diagnostic codes to ignore, comma separated. |
|
A configuration override, repeatable —
for example |
Run ubc build pdf --help for the complete list.
Note
--dump-layout and --debug-overlay are debugging aids,
hidden from --help and carrying no stability promise:
the first writes the internal page layout as text beside the PDF,
the second draws every link’s hit-rectangle and every destination’s landing point
onto the pages. Neither belongs in a deliverable.
Building a subset of needs¶
A formal deliverable is very often a filtered set of needs rather than the whole manual, so the selection is part of the command:
$ ubc build pdf --need-id REQ_1
$ ubc build pdf --need-filter 'type == "req" and status != "done"'
--need-filter takes the same Python-like expression over need fields
that ubc query filter takes.
--need-id is repeatable and the ids union,
so --need-id A --need-id B is exactly --need-filter 'id in ["A", "B"]'.
The two flags cannot be combined; use one expression instead.
Whole documents come in, not individual needs. A selected need brings the document that contains it into the PDF entire — its prose, its other needs and its pictures included — in toctree order, because a page of a deliverable that showed one need out of its section would be a different document from the one under review. The run says how far it widened:
1 document(s) rendered to 2 page(s); 0 placeholder(s).
Selection: 1 of 2 need(s) → 1 of 3 document(s) included whole.
That line is a report, not a warning: a deliberate selection is not a defect, and it must not fail your quality gate. Two things can still be reported beside it — references that pointed into a document the selection left out (they are drawn as plain text, and counted on their own line), and needs whose document no toctree reaches, which are not in the PDF at all.
Three exit codes are worth knowing:
an id that names no need in the project is a usage error (2),
so is an expression that will not compile;
and a selection that matches nothing writes no file and exits 1.
The output¶
One file. Each document follows the previous one, starting on a fresh page down to the toctree’s second level; deeper documents flow on where the text left off, as a book’s subsections do. A document at toctree depth d sets its own title at level d and its level-n section at level d + n − 1, so a section that is level 2 on the site can be level 4 on paper.
The outline is the navigation. Every document and section takes a bookmark,
so a reader’s PDF viewer shows the project’s tree in its sidebar.
Internal :ref: and :doc: references become real link annotations
that jump within the file; a reference that does not resolve is drawn as a
mono chip rather than as prose, because a PDF has no address bar to
recover with.
The title page and the running header are on by default.
The cover carries the project name (or the root document’s title),
the version from [project] version,
and the resolved counts of a need selection when one is active.
Every continuation page carries its document’s title in the top margin,
in muted ink under a hairline rule —
not the page a document opens on, where its own heading is already the title,
so a document that fits on one page is never headed.
Set [build.pdf] title_page = false or running_header = false
to turn either off (see PDF document).
The cover is unnumbered and the body is numbered from 1. The page numbers a reader cites are therefore the same whether or not the front matter is there, and the PDF’s own page labels agree with the printed numbers, so “go to page 12” in a viewer lands on the page that says 12.
Charts are vector pictures.
A needpie or needbar is drawn straight into the page
in the same eight-colour palette the site uses,
at the size its canvas asks for and scaled down to fit where it has to be.
It is not a picture of a chart:
the shapes are paths and the labels are real text,
so a value in a chart is selectable and searchable like any other word.
With :legend: the legend follows the plot as a table,
one row per slice or series, each with its colour swatch;
without the flag the table is not drawn,
exactly as it is not shown on the site.
Two builds of unchanged sources are byte-identical.
There is no wall clock in the file:
the cover carries a date only when the SOURCE_DATE_EPOCH
environment variable is set, which is also what makes a dated build reproducible.
Fonts are embedded from a closed set carried in the binary,
so the same sources produce the same document on any machine.
What is not rendered yet¶
A construct the builder cannot typeset yet is drawn as a labelled placeholder
box — or, inline, as a mono chip — and counted on a build.pdf_*
warning naming it. Nothing an author wrote is dropped silently:
a document that omits something without saying so looks complete,
which is worse than one that is visibly incomplete.
Each finding names the file the construct is written in — for an
.. include::``d fragment, the fragment, with the including document beside it
as an ``included-from location, and for a card shown by a needextract,
the need’s own document — which is the same rule ubc check and
ubc build html follow, and the one a per-file-ignores entry is matched
against.
At this release those are:
footnotes and citations, and the references to them —
build.pdf_block_unsupported;maths, both
.. math::blocks and inline:math:— drawn as its TeX source in the mono face and counted onbuild.pdf_math_unrendered, which is readable to the audience that writes it;contents — a local table of contents; the toctree itself is the PDF’s document order and draws nothing in-page;
raw blocks and roles — output written for another writer has no meaning on paper;
video and download — neither has a paper form;
the generated diagram families —
needflow,needsequence,needumlandneedarch— and Mermaid diagrams, onbuild.pdf_diagram_unrendered. Aneeduml/needarch/uml/plantumldiagram is drawn when you have configured an external PlantUML executable (see the shared PlantUML table); what is left in this list is what no external engine here produces;needgantt, the gantt chart, on
build.pdf_block_unsupported;todolist;
any directive or role ubCode recognises and has no PDF renderer for —
build.pdf_directive_unhandledandbuild.pdf_role_unhandled.
Two further findings are not placeholder boxes and are worth reading differently:
A character no embedded font covers is omitted, and reported as
build.pdf_glyph_unavailablewith the codepoint and a count. The six embedded faces carry no emoji and no CJK, Thai, Khmer or Lao glyphs, so a heading written with a leading sparkle emoji (U+2728) is typeset without it, as plain “New Features”. Omitting is deliberate — a row of▯in a formal deliverable reads as a broken product — but it is never silent. The codepoint is named here rather than pasted, so that documenting the behaviour does not itself add a site to this project’s own count.Documents no toctree reaches are excluded, and reported once per build as
build.pdf_documents_excludedwith the count and up to three names. This is a deliberate divergence fromubc build html, which renders every source file: a page nobody links to is a harmless extra URL on a website, while a document silently appended to a signed deliverable is a defect.
The per-construct matrix behind this list — every AST node type, whether it is
rendered, skipped or a placeholder — is maintained with the code in
rust/ubc_build_pdf/design/coverage.md.
Warnings and the quality gate¶
Like ubc build html, the build reports two independent counts —
indexing warnings (the ordinary project diagnostics ubc check reports)
and build warnings raised by the render phase itself, all carrying a
build.pdf_* code — and holds both to one bar.
Any warning-or-worse finding fails the command with exit 1,
and prints its detail without needing -w:
$ ubc build pdf
Building index for: /home/me/project/ubproject.toml
Processing 1 file
1 document(s) rendered to 2 page(s); 1 placeholder(s).
Found 1 build warning(s).
warning[build.pdf_block_unsupported]
--> index.rst:4
contents: 1 site rendered as a placeholder box
PDF written to: /home/me/project/_build/pdf/index.pdf
Build failed the quality gate: 1 warning(s) found, and any warning-or-worse finding fails this build (the default --deny warning).
Every finding that failed the gate is a construct the PDF builder does not render yet (drawn as a labelled placeholder box); no project diagnostic is among them.
Silence a code with ignore = ["<code>"] under [lint] in ubproject.toml.
Or relax the bar: --deny error (errors only), --deny none (never fail).
Done with warnings in 0.06s
The second line is the one to notice. It appears only when every finding under the bar is a construct the builder does not render yet — no indexing diagnostic among them, and no dropped glyph or unrenderable picture — which is the case where a red build reports known gaps in the builder, not defects in your project. A single broken cross-reference, and it is gone.
The PDF is still written. The gate is a judgement on the artifact, not a refusal to produce it, so a pipeline can publish a failing build for inspection.
Three ways to live with it, in the order to reach for them:
[lint]
ignore = ["build.pdf_glyph_unavailable"]
Narrowing beats relaxing: a code silenced through
lint configuration — by code, by file or by message —
never reaches the gate at all,
and --lint-extend-ignore <CODES> does the same for a single run.
[build.pdf]
deny = "error"
declares the project’s bar once, so CI just runs the command.
And --deny on the command line wins over the key for one run,
in both directions:
$ ubc build pdf --deny none # I know — just build it
$ ubc build pdf --deny error # errors only, without editing tracked config
Rebuilds¶
There is no incremental PDF build yet. Pagination makes the page the wrong unit and the document the right one, and every run currently lowers, lays out and emits the whole file. The index underneath it is incremental exactly as it is for every other command, which is where a large project’s time goes, so a second run over unchanged sources is still much faster than the first.
One thing is shared: externally rendered PlantUML diagrams live in a
content-addressed store under the project’s cache directory, the same one
ubc build html writes, so a diagram both builders draw is rendered once
between them. --no-cache bypasses the read of that store and re-renders
every diagram — which is the recovery from swapping a PlantUML executable for
one that reports the same version.
Configuring the document¶
Page size, margins, base type size, the two front-matter keys and the quality
bar live under [build.pdf] in ubproject.toml:
[build.pdf]
page_size = "letter"
base_font_size = 11.0
[build.pdf.margins]
top = 25.0
See the PDF document configuration page for every option.