Set Urdu in Nastaliq, and keep Arabic in Naskh; Break Chinese, Korean and Thai lines where a reader expects (#1052, #1054) #1066

Merged
tiagoagueda merged 2 commits from 1054-1052-scripts into main 2026-10-27 22:17:51 +00:00
Owner

Urdu is written in the Arabic script, but in Nastaliq, not in the Naskh that
Arabic uses. The image already had Noto Nastaliq Urdu (fonts-noto-core), and
nothing asked for it, so Urdu came out in Naskh: legible, and foreign-looking.

Interface: [lang]:lang(ur) puts "Noto Nastaliq Urdu" first and opens the line
to 2, through the variables every text-* and leading-* utility reads, so
buttons, chips and table rows grow with it. Measured with the face loaded,
truncate still cut 3 px off descenders (the face reaches 1.9 em up and 0.6
down), so in Urdu a truncated box clips sideways only (overflow-x: clip,
overflow-y: visible, and min-width: 0, which hidden used to give a flex
item for free). With that, the dashboard, applications table, board,
companies, CVs, career and language pages show no clipped words, no sideways
scroll and no axe violation at 1280 and 320 px.

Documents: each classic and plain CV, letter and portfolio theme has a
body:lang(ur) rule with Nastaliq ahead of the theme's own stack and a line
of 2. Arabic never matches it, and keeps Naskh.

Instance check: languages.STYLES maps ur to ISO 15924's Aran (Arabic,
Nastaliq variant), which is said as "Arabic (Nastaliq)". fonts.renderable_scripts
asks for that family by name, and takes only a font of that family whose cmap
maps NOON GHUNNA, since the map's substitute (Naskh) would also say yes for
Urdu. check_fonts and Server settings, Overview list it beside the scripts.
A missing Nastaliq is shown in red and does not make check_fonts fail,
because the script is still drawn.

Tests: the probe, the command, the overview and the image's declaration.
WeasyPrint draws an Urdu CV and an Arabic CV in each theme and reads the
embedded fonts (they skip where the family is not installed). A browser test
reads the computed family and line height in Urdu and Arabic, and walks the
pages for clipping, sideways scroll and axe in Urdu (it skips where the
browser has no Nastaliq).

Closes #1054

Chinese is offered for 0.5, and Japanese, Korean and Thai are on the way. None
of them uses spaces to tell a line where it may break.

Interface: [lang]:lang(zh) and :lang(ja) take line-break: strict, the
full kinsoku set. The browser's auto already keeps ,。)」 off the start of
a line; strict also covers the small kana and the prolonged sound mark.
[lang]:lang(ko) takes word-break: keep-all, so Hangul breaks at its spaces
and not between syllables. The rules that stop long Latin words overflowing
are left alone. Under keep-all, break-word and anywhere break inside a
Korean word only when that word is wider than its line, the same last resort
Latin gets, and the break-all utilities sit only on addresses, tokens and
keys. Thai needs nothing: the browser breaks it with ICU's dictionary.

Documents: WeasyPrint 70 accepts only word-break: normal | break-all and has
no line-break. Adding either to the themes would log a warning on every
render and make the HTML preview differ from the PDF. They are not added.
Pango already breaks Han under UAX #14, which keeps the kinsoku marks off the
wrong end of a line. The themes set neither hyphens: auto nor
text-align: justify, and a test now keeps it that way. Korean in a PDF still
breaks between syllables; WeasyPrint has no way to say otherwise.

Image: Pango breaks Thai between words with libthai's dictionary. Debian's
libpango-1.0-0 already depends on libthai0, so the image had it. The
Dockerfile now names it, so that a later Pango cannot drop it unnoticed.
tests/test_fonts.py holds it there.

Tests:

  • A browser test opens the capture review, the applications table and a CV
    preview at 320 px, in zh-Hans and with Thai content. It checks that the
    page does not scroll sideways, that no line starts with a closing mark or
    ends with an opening one, and that every Thai line start is a word boundary
    for ICU's segmenter. It also reads the computed word-break and
    line-break for ko, zh-Hans, ja and en.
  • WeasyPrint tests lay out a Chinese CV and a Thai CV in each theme. They
    read the line boxes and check the same kinsoku rules, and that every Thai
    line ends where a word ends. They skip where no font draws the script.

Closes #1052

Notes for review

  • The new PDF tests and the Nastaliq browser walk skip in CI. CI runs in node:22-bookworm with only fonts-dejavu-core, not in the image. To have them run, add fonts-noto-core and fonts-noto-cjk to the unit job, and fonts-noto-core to the browser job. That costs CI time, so it is left to the maintainer.
  • #1054 decisions:
    • A missing Nastaliq is reported in red but does not fail check_fonts, because Urdu is still drawn, in Naskh.
    • In documents, Nastaliq leads the Urdu stack, so digits inside Latin text are drawn in it.
    • .truncate and .badge > span clip sideways only in Urdu, so descenders are not cut.
  • #1052 decisions:
    • The themes get no line-break or word-break CSS. WeasyPrint 70 supports neither (it would warn on every render), and Pango already keeps closing punctuation off line starts.
    • Korean PDFs still break between syllables.
    • The Dockerfile names libthai0, which Pango already depends on, so nothing new is installed.

🤖 Generated with Claude Code

Urdu is written in the Arabic script, but in Nastaliq, not in the Naskh that Arabic uses. The image already had Noto Nastaliq Urdu (fonts-noto-core), and nothing asked for it, so Urdu came out in Naskh: legible, and foreign-looking. Interface: `[lang]:lang(ur)` puts "Noto Nastaliq Urdu" first and opens the line to 2, through the variables every `text-*` and `leading-*` utility reads, so buttons, chips and table rows grow with it. Measured with the face loaded, `truncate` still cut 3 px off descenders (the face reaches 1.9 em up and 0.6 down), so in Urdu a truncated box clips sideways only (`overflow-x: clip`, `overflow-y: visible`, and `min-width: 0`, which `hidden` used to give a flex item for free). With that, the dashboard, applications table, board, companies, CVs, career and language pages show no clipped words, no sideways scroll and no axe violation at 1280 and 320 px. Documents: each classic and plain CV, letter and portfolio theme has a `body:lang(ur)` rule with Nastaliq ahead of the theme's own stack and a line of 2. Arabic never matches it, and keeps Naskh. Instance check: `languages.STYLES` maps `ur` to ISO 15924's `Aran` (Arabic, Nastaliq variant), which is said as "Arabic (Nastaliq)". `fonts.renderable_scripts` asks for that family by name, and takes only a font of that family whose cmap maps NOON GHUNNA, since the map's substitute (Naskh) would also say yes for Urdu. `check_fonts` and Server settings, Overview list it beside the scripts. A missing Nastaliq is shown in red and does not make `check_fonts` fail, because the script is still drawn. Tests: the probe, the command, the overview and the image's declaration. WeasyPrint draws an Urdu CV and an Arabic CV in each theme and reads the embedded fonts (they skip where the family is not installed). A browser test reads the computed family and line height in Urdu and Arabic, and walks the pages for clipping, sideways scroll and axe in Urdu (it skips where the browser has no Nastaliq). Closes #1054 Chinese is offered for 0.5, and Japanese, Korean and Thai are on the way. None of them uses spaces to tell a line where it may break. Interface: `[lang]:lang(zh)` and `:lang(ja)` take `line-break: strict`, the full kinsoku set. The browser's `auto` already keeps ,。)」 off the start of a line; strict also covers the small kana and the prolonged sound mark. `[lang]:lang(ko)` takes `word-break: keep-all`, so Hangul breaks at its spaces and not between syllables. The rules that stop long Latin words overflowing are left alone. Under keep-all, `break-word` and `anywhere` break inside a Korean word only when that word is wider than its line, the same last resort Latin gets, and the `break-all` utilities sit only on addresses, tokens and keys. Thai needs nothing: the browser breaks it with ICU's dictionary. Documents: WeasyPrint 70 accepts only `word-break: normal | break-all` and has no `line-break`. Adding either to the themes would log a warning on every render and make the HTML preview differ from the PDF. They are not added. Pango already breaks Han under UAX #14, which keeps the kinsoku marks off the wrong end of a line. The themes set neither `hyphens: auto` nor `text-align: justify`, and a test now keeps it that way. Korean in a PDF still breaks between syllables; WeasyPrint has no way to say otherwise. Image: Pango breaks Thai between words with libthai's dictionary. Debian's libpango-1.0-0 already depends on libthai0, so the image had it. The Dockerfile now names it, so that a later Pango cannot drop it unnoticed. tests/test_fonts.py holds it there. Tests: - A browser test opens the capture review, the applications table and a CV preview at 320 px, in zh-Hans and with Thai content. It checks that the page does not scroll sideways, that no line starts with a closing mark or ends with an opening one, and that every Thai line start is a word boundary for ICU's segmenter. It also reads the computed `word-break` and `line-break` for ko, zh-Hans, ja and en. - WeasyPrint tests lay out a Chinese CV and a Thai CV in each theme. They read the line boxes and check the same kinsoku rules, and that every Thai line ends where a word ends. They skip where no font draws the script. Closes #1052 ## Notes for review - **The new PDF tests and the Nastaliq browser walk skip in CI.** CI runs in `node:22-bookworm` with only `fonts-dejavu-core`, not in the image. To have them run, add `fonts-noto-core` and `fonts-noto-cjk` to the unit job, and `fonts-noto-core` to the browser job. That costs CI time, so it is left to the maintainer. - **#1054 decisions:** - A missing Nastaliq is reported in red but does not fail `check_fonts`, because Urdu is still drawn, in Naskh. - In documents, Nastaliq leads the Urdu stack, so digits inside Latin text are drawn in it. - `.truncate` and `.badge > span` clip sideways only in Urdu, so descenders are not cut. - **#1052 decisions:** - The themes get no `line-break` or `word-break` CSS. WeasyPrint 70 supports neither (it would warn on every render), and Pango already keeps closing punctuation off line starts. - Korean PDFs still break between syllables. - The Dockerfile names `libthai0`, which Pango already depends on, so nothing new is installed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Urdu is written in the Arabic script, but in Nastaliq, not in the Naskh that
Arabic uses. The image already had Noto Nastaliq Urdu (fonts-noto-core), and
nothing asked for it, so Urdu came out in Naskh: legible, and foreign-looking.

Interface: `[lang]:lang(ur)` puts "Noto Nastaliq Urdu" first and opens the line
to 2, through the variables every `text-*` and `leading-*` utility reads, so
buttons, chips and table rows grow with it. Measured with the face loaded,
`truncate` still cut 3 px off descenders (the face reaches 1.9 em up and 0.6
down), so in Urdu a truncated box clips sideways only (`overflow-x: clip`,
`overflow-y: visible`, and `min-width: 0`, which `hidden` used to give a flex
item for free). With that, the dashboard, applications table, board,
companies, CVs, career and language pages show no clipped words, no sideways
scroll and no axe violation at 1280 and 320 px.

Documents: each classic and plain CV, letter and portfolio theme has a
`body:lang(ur)` rule with Nastaliq ahead of the theme's own stack and a line
of 2. Arabic never matches it, and keeps Naskh.

Instance check: `languages.STYLES` maps `ur` to ISO 15924's `Aran` (Arabic,
Nastaliq variant), which is said as "Arabic (Nastaliq)". `fonts.renderable_scripts`
asks for that family by name, and takes only a font of that family whose cmap
maps NOON GHUNNA, since the map's substitute (Naskh) would also say yes for
Urdu. `check_fonts` and Server settings, Overview list it beside the scripts.
A missing Nastaliq is shown in red and does not make `check_fonts` fail,
because the script is still drawn.

Tests: the probe, the command, the overview and the image's declaration.
WeasyPrint draws an Urdu CV and an Arabic CV in each theme and reads the
embedded fonts (they skip where the family is not installed). A browser test
reads the computed family and line height in Urdu and Arabic, and walks the
pages for clipping, sideways scroll and axe in Urdu (it skips where the
browser has no Nastaliq).

Closes #1054

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Break Chinese, Korean and Thai lines where a reader expects (#1052)
Some checks failed
CI / Unit tests and coverage (Python 3.14) (push) Successful in 13m48s
CI / Database tests on PostgreSQL (push) Successful in 3m42s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 2m46s
CI / Security audit (push) Successful in 49s
CI / Browser tests (Chromium) (push) Successful in 32m49s
Every Python / Unit tests (Python 3.12) (push) Successful in 12m34s
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
d424050452
Chinese is offered for 0.5, and Japanese, Korean and Thai are on the way. None
of them uses spaces to tell a line where it may break.

Interface: `[lang]:lang(zh)` and `:lang(ja)` take `line-break: strict`, the
full kinsoku set. The browser's `auto` already keeps ,。)」 off the start of
a line; strict also covers the small kana and the prolonged sound mark.
`[lang]:lang(ko)` takes `word-break: keep-all`, so Hangul breaks at its spaces
and not between syllables. The rules that stop long Latin words overflowing
are left alone. Under keep-all, `break-word` and `anywhere` break inside a
Korean word only when that word is wider than its line, the same last resort
Latin gets, and the `break-all` utilities sit only on addresses, tokens and
keys. Thai needs nothing: the browser breaks it with ICU's dictionary.

Documents: WeasyPrint 70 accepts only `word-break: normal | break-all` and has
no `line-break`. Adding either to the themes would log a warning on every
render and make the HTML preview differ from the PDF. They are not added.
Pango already breaks Han under UAX #14, which keeps the kinsoku marks off the
wrong end of a line. The themes set neither `hyphens: auto` nor
`text-align: justify`, and a test now keeps it that way. Korean in a PDF still
breaks between syllables; WeasyPrint has no way to say otherwise.

Image: Pango breaks Thai between words with libthai's dictionary. Debian's
libpango-1.0-0 already depends on libthai0, so the image had it. The
Dockerfile now names it, so that a later Pango cannot drop it unnoticed.
tests/test_fonts.py holds it there.

Tests:
- A browser test opens the capture review, the applications table and a CV
  preview at 320 px, in zh-Hans and with Thai content. It checks that the
  page does not scroll sideways, that no line starts with a closing mark or
  ends with an opening one, and that every Thai line start is a word boundary
  for ICU's segmenter. It also reads the computed `word-break` and
  `line-break` for ko, zh-Hans, ja and en.
- WeasyPrint tests lay out a Chinese CV and a Thai CV in each theme. They
  read the line boxes and check the same kinsoku rules, and that every Thai
  line ends where a word ends. They skip where no font draws the script.

Closes #1052

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tiagoagueda force-pushed 1054-1052-scripts from d424050452
Some checks failed
CI / Unit tests and coverage (Python 3.14) (push) Successful in 13m48s
CI / Database tests on PostgreSQL (push) Successful in 3m42s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 2m46s
CI / Security audit (push) Successful in 49s
CI / Browser tests (Chromium) (push) Successful in 32m49s
Every Python / Unit tests (Python 3.12) (push) Successful in 12m34s
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
to 1e71829722
Some checks failed
CI / Browser tests (Chromium) (push) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (push) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (push) Has been cancelled
CI / Database tests on PostgreSQL (push) Has been cancelled
CI / Security audit (push) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
Every Python / Unit tests (Python 3.12) (push) Has been cancelled
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
2026-10-27 22:03:38 +00:00
Compare
tiagoagueda force-pushed 1054-1052-scripts from 1e71829722
Some checks failed
CI / Browser tests (Chromium) (push) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (push) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (push) Has been cancelled
CI / Database tests on PostgreSQL (push) Has been cancelled
CI / Security audit (push) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
Every Python / Unit tests (Python 3.12) (push) Has been cancelled
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
to 0c7eddf132
All checks were successful
CI / Browser tests (Chromium) (push) Successful in 33m9s
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 12m58s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 2m42s
CI / Database tests on PostgreSQL (pull_request) Successful in 3m57s
CI / Security audit (pull_request) Successful in 57s
CI / Browser tests (Chromium) (pull_request) Successful in 31m38s
Every Python / Unit tests (Python 3.12) (push) Successful in 13m19s
Every Python / Unit tests (Python 3.13) (push) Successful in 13m26s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 3m3s
CI / Unit tests and coverage (Python 3.14) (push) Successful in 14m23s
CI / Security audit (push) Successful in 1m7s
CI / Database tests on PostgreSQL (push) Successful in 4m17s
2026-10-27 22:14:10 +00:00
Compare
tiagoagueda deleted branch 1054-1052-scripts 2026-10-27 22:17:51 +00:00
Sign in to join this conversation.
No reviewers
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!1066
No description provided.