Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@ sections that affect its template. Version numbers follow the

- `theme.favicon` points at `theme/brand-symbol.svg` rather than the
wordmark.
- The PDF cover carries a line naming the theme that rendered it. A PDF
leaves the repository that produced it, so without it the artifact says
nothing about what to reproduce a rendering complaint against.

### api-reference

- `theme.favicon` points at `theme/brand-symbol.svg` rather than the
wordmark.
- The PDF cover carries a line naming the theme that rendered it. A PDF
leaves the repository that produced it, so without it the artifact says
nothing about what to reproduce a rendering complaint against.

### scripts

Expand All @@ -40,6 +46,11 @@ sections that affect its template. Version numbers follow the
project documenting a product with its own mark can put it on the
cover. A path that does not resolve is an error rather than a silent
fall back to the company mark.
- `build-docs-pdf.sh` reads `docs/theme/.meta` for the cover's provenance
line, so a drifted tree cannot misreport itself as a release. A
`--source` install renders `docs-theme (local build)` rather than the
version it was asked for, which never resolved a release. A missing
`.meta` is an error.

## [0.2.0] - 2026-08-08

Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,22 @@ with more than one needs:

It defaults to `mkdocs.yml`. Any other argument shape is a usage error.

#### Build Provenance

Every cover carries a line naming the theme that rendered it —
`docs-theme 1.2.3` for a downloaded install. A PDF is handed to a reader
and leaves the repository that produced it, so without this there is
nothing in the artifact to reproduce a rendering complaint against, and
once several projects bump the theme on their own schedules the answer
stops being inferable.

The value comes from `docs/theme/.meta`, the record of what is installed,
rather than from the request in `docs/theme.toml` — a hand-edited or
drifted tree would otherwise misreport itself. A `--source` install has a
version that never resolved a release, so it prints `docs-theme (local
build)` instead of a number. It is not configurable, and a missing
`.meta` is an error rather than a quietly omitted line.

#### Cover and Output Configuration

All cover text and the output filename come from an `extra.pdf` block
Expand Down
39 changes: 39 additions & 0 deletions scripts/build-docs-pdf.sh
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,39 @@ if not os.path.isdir(theme_dir):
fail("docs/theme/ not found. Install the theme first:\n"
" ./scripts/fetch-theme.sh")

# Which docs-theme rendered this file. A PDF is handed to a reader and
# leaves the repository that produced it, so unless the artifact says so
# there is nothing to reproduce a rendering complaint against -- and once
# several projects bump the theme on their own schedules, the answer
# stops being inferable. .meta is the record of what is installed; the
# requested value in docs/theme.toml would misreport a drifted tree.
meta_path = os.path.join(theme_dir, ".meta")
if not os.path.isfile(meta_path):
fail(f"{meta_path} not found. The installed theme is incomplete; "
"re-run ./scripts/fetch-theme.sh.")

installed = {}
with open(meta_path, "r", encoding="utf-8") as f:
for line in f:
key, sep, value = line.partition("=")
if sep:
installed[key.strip()] = value.strip().strip('"')

theme_version = installed.get("version")
theme_source = installed.get("source")
if not theme_version or not theme_source:
fail(f"{meta_path} records no version or source; re-run "
"./scripts/fetch-theme.sh.")

# A --source install takes its version from docs/theme.toml without ever
# resolving a release, so printing that number would assert something
# untrue. Name the build instead.
if theme_source == "release":
theme_provenance = f"docs-theme {theme_version}"
else:
theme_provenance = ("docs-theme (local build)" if locale == "en"
else "docs-theme (로컬 빌드)")

theme_pdf = os.path.join(theme_dir, "pdf")
if not os.path.isdir(theme_pdf):
fail("docs/theme/pdf/ not found. The installed template ships no PDF "
Expand Down Expand Up @@ -336,6 +369,12 @@ elif "cover_tagline" in extra:
del extra["cover_tagline"]
data["extra"] = extra

# theme_provenance reaches {{ theme_provenance }} in pdf/cover.html.j2 the
# same way, through `extra`. It is derived from the installed theme, not
# configured, so a consumer value is overwritten rather than honoured.
extra["theme_provenance"] = theme_provenance
data["extra"] = extra

if isinstance(plugins, dict):
plugins["with-pdf"] = options
elif isinstance(plugins, list):
Expand Down
4 changes: 4 additions & 0 deletions templates/api-reference/pdf/cover.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@
<p class="meta-copyright">{{ line | e }}</p>
{% endfor %}
{% endif %}

{% if theme_provenance %}
<p class="meta-provenance">{{ theme_provenance | e }}</p>
{% endif %}
</div>
</article>
13 changes: 13 additions & 0 deletions templates/api-reference/pdf/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,19 @@ p.meta {
color: #71717B;
}

/* Build provenance: which docs-theme rendered this file. A PDF leaves
* the repository that built it, so without this line there is nothing in
* the artifact to reproduce a rendering complaint against. */
p.meta-provenance {
color: #B5B5BC;
font-size: 10px;
font-weight: 400;
line-height: 16px;
letter-spacing: -0.1px;
white-space: nowrap;
margin: 6px 0 0;
}

p.meta-copyright {
color: #9F9FA9;
font-size: 12px;
Expand Down
4 changes: 4 additions & 0 deletions templates/manual/pdf/cover.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@
<p class="meta-copyright">{{ line | e }}</p>
{% endfor %}
{% endif %}

{% if theme_provenance %}
<p class="meta-provenance">{{ theme_provenance | e }}</p>
{% endif %}
</div>
</article>
13 changes: 13 additions & 0 deletions templates/manual/pdf/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,19 @@ p.meta {
color: #71717B;
}

/* Build provenance: which docs-theme rendered this file. A PDF leaves
* the repository that built it, so without this line there is nothing in
* the artifact to reproduce a rendering complaint against. */
p.meta-provenance {
color: #B5B5BC;
font-size: 10px;
font-weight: 400;
line-height: 16px;
letter-spacing: -0.1px;
white-space: nowrap;
margin: 6px 0 0;
}

p.meta-copyright {
color: #9F9FA9;
font-size: 12px;
Expand Down
40 changes: 40 additions & 0 deletions tests/pdf-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -777,4 +777,44 @@ grep -q "brand-print.svg" "$WORK/no-print-logo.log" \
|| die "the error does not name the missing asset"
ok "a missing brand-print.svg fails instead of rendering a logo-less cover"

# --- build provenance on the cover --------------------------------------

# A PDF leaves the repository that built it, so the cover records which
# docs-theme rendered it. The fixtures install with --source, so this is
# the local form: naming a version that never resolved a release would
# assert something untrue.
prov="$WORK/prov"
new_project "$prov"
printf 'site_name: Provenance Fixture\n' > "$prov/mkdocs.yml"
build_pdf "$prov" en
build_pdf "$prov" ko
pdf_text "$prov/site/pdf/provenance-fixture.en.pdf" > "$WORK/prov-en.txt"
pdf_text "$prov/site/pdf/provenance-fixture.ko.pdf" > "$WORK/prov-ko.txt"
contains "$WORK/prov-en.txt" "docs-theme(localbuild)" "the en cover"
contains "$WORK/prov-ko.txt" "docs-theme(로컬빌드)" "the ko cover"
excludes "$WORK/prov-en.txt" "docs-theme1.2.3" "the en cover"
ok "a --source build names itself rather than claiming a release version"

# A downloaded install prints the version .meta recorded. The value is
# read from .meta and not from docs/theme.toml, so a drifted tree cannot
# misreport itself.
sed -i.bak 's/^source = .*/source = "release"/' "$prov/docs/theme/.meta"
rm -f "$prov/docs/theme/.meta.bak"
build_pdf "$prov" en
pdf_text "$prov/site/pdf/provenance-fixture.en.pdf" > "$WORK/prov-release.txt"
contains "$WORK/prov-release.txt" "docs-theme1.2.3" "the cover of a release build"
excludes "$WORK/prov-release.txt" "localbuild" "the cover of a release build"
ok "a release install prints the version recorded in .meta"

# Without .meta there is no provenance to print, and a cover that quietly
# omits it is the silent asset loss this whole check exists to prevent.
rm -f "$prov/docs/theme/.meta"
if (cd "$prov" && ./docs/theme/build-docs-pdf.sh en) > "$WORK/no-meta.log" 2>&1
then
die "a missing .meta should be an error"
fi
grep -q "\.meta" "$WORK/no-meta.log" \
|| die "the error does not name .meta"
ok "a missing .meta fails instead of dropping the provenance line"

echo "All PDF script checks passed."