Fonts: a declared package is not a drawn glyph #74

Closed
opened 2026-09-06 21:32:26 +00:00 by tiagoagueda · 1 comment
Owner

Observation

Raised while doing #70. Adding the African languages meant adding Arabic and Ethiopic, and
the container could not draw either: it installed fonts-dejavu-core, which covers Latin,
Greek and Cyrillic and stops there. An Amharic CV rendered in that image would have been a
page of empty boxes, and a box on somebody's CV is worse than the same CV in English —
one is a missing translation, the other looks like the applicant sent a broken file.

That immediate hole is fixed: fonts-noto-core is installed, and tests/test_fonts.py
reads postulo.core.languages.SCRIPTS and the Dockerfile and insists they agree, so a
language added in a later phase cannot quietly ship a PDF nobody can read.

What is left is everything that fix does not cover.

Why 0.4.0

Two of the five points below are live gaps today; the other three are forced by #71, which
brings Chinese, Japanese, Korean, Devanagari, Thai, Khmer, Burmese and Sinhala. Doing them
separately would mean touching the image, the themes and the checks twice. If the two live
gaps should be pulled forward into 0.3.0 instead, say so and they will be — they are marked
below.

Shape

1. The scripts #71 needs, and the size they cost

fonts-noto-core does not include CJK: that is fonts-noto-cjk, and it is a different
order of size (tens of megabytes against a few). Some of the South and South-East Asian
scripts are their own packages too. Each is a deliberate decision, not an apt line:

Script Package Rough cost
Han, Hiragana, Katakana, Hangul fonts-noto-cjk large
Devanagari, Bengali, Tamil, Telugu, Gujarati, Gurmukhi fonts-noto-core already there
Thai, Lao fonts-noto-core already there
Khmer, Burmese, Sinhala needs checking per package small
Hebrew, Persian, Urdu (Arabic script) fonts-noto-core already there

Whether the image carries CJK by default is the decision this issue exists to make.
Doubling an image so that every installation can render a language almost none of them will
use is a real cost; so is a Japanese CV coming out as boxes. A middle answer exists — a
POSTULO_EXTRA_PACKAGES-style hook already exists in the Dockerfile for plugins, and fonts
could use the same door — but it has to be chosen rather than fallen into.

2. Nobody checks that the fonts are actually there (live gap, could move to 0.3.0)

tests/test_fonts.py checks a declaration. It reads the Dockerfile. It does not check a
running system, and it cannot: an operator running Postulo from a virtualenv under systemd,
or on a distribution whose font packages are named differently, gets whatever that machine
has, and nothing tells them. The first sign is a CV that went out full of boxes.

Wanted: something that reports, at runtime, which of the scripts this instance offers it can
actually draw. WeasyPrint exposes the font configuration Pango resolved, so this is
answerable without shelling out to fc-list. It belongs on Server settings → Overview
beside the other facts about the instance, and probably in a manage.py command so it can
be checked before anything is sent.

3. The interface, not only the documents (live gap, could move to 0.3.0)

Everything above is about WeasyPrint. The browser side has the same problem and no
mitigation at all: --font-sans is a system stack, so an Amharic interface on a machine
with no Ethiopic font is boxes too. The person reading it is the account holder rather than
a recruiter, which makes it less damaging and not less broken.

Postulo bundles no webfonts today, deliberately — no request, nothing for font-src to
allow, and the strict content security policy stays as it is. Adding subsetted Noto faces
for the non-Latin scripts would change that; so would doing nothing. Decide.

4. The document themes name fonts that cannot draw these scripts

classic asks for "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif and
plain for "Helvetica Neue", Helvetica, Arial, sans-serif. None of those has Ethiopic or
CJK, so an Arabic CV in the classic theme falls all the way through to the generic serif
and gets whatever fontconfig picks — which may be fine and may be a face that looks nothing
like the rest of the document.

A theme should state a per-script fallback rather than land on one by accident.

5. The check should be about scripts, not about a hard-coded map

tests/test_fonts.py::COVERAGE maps a script to the Debian packages that draw it, by hand.
That is honest and small today. If it grows past about a dozen entries it should be derived
from what the packages actually contain rather than from what somebody believed they
contained.

Classification

Enhancement, with a bug's consequences: a missing glyph is not a degraded experience but a
document that cannot be read. Not breaking.

Depends on

#71, which brings the scripts that force most of this. #70 is where the first hole was
found and fixed.

Open question

Does the default image carry CJK, or is it an opt-in through a build argument? Proposal:
opt-in, documented in Installing Postulo, with the runtime check from point 2 saying
plainly which scripts the running instance can and cannot draw — so somebody who needs it
finds out before a recruiter does, not after.

## Observation Raised while doing #70. Adding the African languages meant adding Arabic and Ethiopic, and the container could not draw either: it installed `fonts-dejavu-core`, which covers Latin, Greek and Cyrillic and stops there. An Amharic CV rendered in that image would have been a page of empty boxes, and **a box on somebody's CV is worse than the same CV in English** — one is a missing translation, the other looks like the applicant sent a broken file. That immediate hole is fixed: `fonts-noto-core` is installed, and `tests/test_fonts.py` reads `postulo.core.languages.SCRIPTS` and the Dockerfile and insists they agree, so a language added in a later phase cannot quietly ship a PDF nobody can read. What is left is everything that fix does **not** cover. ## Why 0.4.0 Two of the five points below are live gaps today; the other three are forced by #71, which brings Chinese, Japanese, Korean, Devanagari, Thai, Khmer, Burmese and Sinhala. Doing them separately would mean touching the image, the themes and the checks twice. If the two live gaps should be pulled forward into 0.3.0 instead, say so and they will be — they are marked below. ## Shape ### 1. The scripts #71 needs, and the size they cost `fonts-noto-core` does not include CJK: that is `fonts-noto-cjk`, and it is a different order of size (tens of megabytes against a few). Some of the South and South-East Asian scripts are their own packages too. Each is a deliberate decision, not an apt line: | Script | Package | Rough cost | | --- | --- | --- | | Han, Hiragana, Katakana, Hangul | `fonts-noto-cjk` | large | | Devanagari, Bengali, Tamil, Telugu, Gujarati, Gurmukhi | `fonts-noto-core` | already there | | Thai, Lao | `fonts-noto-core` | already there | | Khmer, Burmese, Sinhala | needs checking per package | small | | Hebrew, Persian, Urdu (Arabic script) | `fonts-noto-core` | already there | **Whether the image carries CJK by default is the decision this issue exists to make.** Doubling an image so that every installation can render a language almost none of them will use is a real cost; so is a Japanese CV coming out as boxes. A middle answer exists — a `POSTULO_EXTRA_PACKAGES`-style hook already exists in the Dockerfile for plugins, and fonts could use the same door — but it has to be chosen rather than fallen into. ### 2. Nobody checks that the fonts are actually there *(live gap, could move to 0.3.0)* `tests/test_fonts.py` checks a **declaration**. It reads the Dockerfile. It does not check a running system, and it cannot: an operator running Postulo from a virtualenv under systemd, or on a distribution whose font packages are named differently, gets whatever that machine has, and nothing tells them. The first sign is a CV that went out full of boxes. Wanted: something that reports, at runtime, which of the scripts this instance offers it can actually draw. WeasyPrint exposes the font configuration Pango resolved, so this is answerable without shelling out to `fc-list`. It belongs on *Server settings → Overview* beside the other facts about the instance, and probably in a `manage.py` command so it can be checked before anything is sent. ### 3. The interface, not only the documents *(live gap, could move to 0.3.0)* Everything above is about WeasyPrint. The **browser** side has the same problem and no mitigation at all: `--font-sans` is a system stack, so an Amharic interface on a machine with no Ethiopic font is boxes too. The person reading it is the account holder rather than a recruiter, which makes it less damaging and not less broken. Postulo bundles no webfonts today, deliberately — no request, nothing for `font-src` to allow, and the strict content security policy stays as it is. Adding subsetted Noto faces for the non-Latin scripts would change that; so would doing nothing. Decide. ### 4. The document themes name fonts that cannot draw these scripts `classic` asks for `"Palatino Linotype", Palatino, Georgia, "Times New Roman", serif` and `plain` for `"Helvetica Neue", Helvetica, Arial, sans-serif`. None of those has Ethiopic or CJK, so an Arabic CV in the classic theme falls all the way through to the generic `serif` and gets whatever fontconfig picks — which may be fine and may be a face that looks nothing like the rest of the document. A theme should state a per-script fallback rather than land on one by accident. ### 5. The check should be about scripts, not about a hard-coded map `tests/test_fonts.py::COVERAGE` maps a script to the Debian packages that draw it, by hand. That is honest and small today. If it grows past about a dozen entries it should be derived from what the packages actually contain rather than from what somebody believed they contained. ## Classification Enhancement, with a bug's consequences: a missing glyph is not a degraded experience but a document that cannot be read. Not breaking. ## Depends on #71, which brings the scripts that force most of this. #70 is where the first hole was found and fixed. ## Open question Does the default image carry CJK, or is it an opt-in through a build argument? Proposal: opt-in, documented in *Installing Postulo*, with the runtime check from point 2 saying plainly which scripts the running instance can and cannot draw — so somebody who needs it finds out before a recruiter does, not after.
tiagoagueda added this to the 0.4.0 milestone 2026-09-06 21:32:26 +00:00
tiagoagueda modified the milestone from 0.4.0 to 0.5.0 2026-09-19 10:31:13 +00:00
Author
Owner

Landed on main as d88715901, with two follow-ups (b47674329, d4becf269) that made the probe work against the HarfBuzz and Pango the image actually carries. Closing; the five points, against what is in the tree:

Point Where it stands
1. The scripts #71 needs, and whether the image carries CJK Decided: opt-in, a build argument on the image rather than a default, documented in Installing Postulo. The default image stays the size it was.
2. Nobody checks that the fonts are actually there manage.py check_fonts, and a row on Server settings → Overview, say which of the scripts the offered languages need this instance can draw. It asks the running system, not the Dockerfile.
3. The interface, not only the documents Decided: no webfonts. The interface stays on the reader's own system stack, so there is still nothing for font-src to allow; the reasoning is written down in assets/css/app.css.
4. The themes name fonts that cannot draw these scripts The document themes name a per-script fallback rather than landing on one by accident.
5. The check should be about scripts, not a hard-coded map tests/test_fonts.py holds the probe table to the languages the declaration knows and checks the cmap parsers against synthetic tables. The hand-written package map stays while it is under a dozen entries, which is what this point asked for.

The issue says it depends on #71. It does not any more: the decisions are taken and the check is about whatever scripts the instance offers, so the day #71 adds Han or Devanagari the check reports on them without being changed.

Landed on `main` as `d88715901`, with two follow-ups (`b47674329`, `d4becf269`) that made the probe work against the HarfBuzz and Pango the image actually carries. Closing; the five points, against what is in the tree: | Point | Where it stands | | --- | --- | | 1. The scripts #71 needs, and whether the image carries CJK | Decided: **opt-in**, a build argument on the image rather than a default, documented in *Installing Postulo*. The default image stays the size it was. | | 2. Nobody checks that the fonts are actually there | `manage.py check_fonts`, and a row on *Server settings → Overview*, say which of the scripts the offered languages need this instance can draw. It asks the running system, not the Dockerfile. | | 3. The interface, not only the documents | Decided: no webfonts. The interface stays on the reader's own system stack, so there is still nothing for `font-src` to allow; the reasoning is written down in `assets/css/app.css`. | | 4. The themes name fonts that cannot draw these scripts | The document themes name a per-script fallback rather than landing on one by accident. | | 5. The check should be about scripts, not a hard-coded map | `tests/test_fonts.py` holds the probe table to the languages the declaration knows and checks the cmap parsers against synthetic tables. The hand-written package map stays while it is under a dozen entries, which is what this point asked for. | The issue says it depends on #71. It does not any more: the decisions are taken and the check is about whatever scripts the instance offers, so the day #71 adds Han or Devanagari the check reports on them without being changed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo#74
No description provided.