Commit Graph
108 Commits
Author SHA1 Message Date
acccf913ab Traditional Chinese interface: draw hanzi to the Taiwan standard with an installed TC font (#3380)
* i18n: Traditional Chinese interface draws hanzi with an installed TC font

The zh-hant interface shared the Simplified Chinese font order, so its first
CJK face was the embedded Noto Sans CJK SC and traditional characters were
drawn with mainland glyph shapes (說, 資, 這, 為, 戶).

On the desktop, a Traditional Chinese interface now puts an installed
Traditional Chinese font before every CJK face: PingFang TC on macOS,
Microsoft JhengHei on Windows, Noto Sans CJK TC on Linux. Noto Sans CJK SC
stays after it as the fallback. Without an installed TC font, and on wasm,
the order is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Credits: add TobyWu666 to contributors/people.toml

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n: find Traditional Chinese fonts at the root of a font folder

Linux user font folders (~/.fonts, ~/.local/share/fonts) often hold font
files directly; the Traditional Chinese candidates only named files in
distribution subfolders, so those fonts were missed (CodeRabbit review).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n: skip the Traditional Chinese font test without embedded catalogs

Since the catalogs come from craft-i18n (#4072), a build without
CRAFT_I18N_DIR is English only, so the test now skips like the other
catalog tests. Also rustfmt hant_font_chars.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-29 22:03:12 -04:00
Dylan M. Taylor 0436139c57 Refactor fonts for strict Clippy checks 2026-10-28 01:06:48 +00:00
aiongg 4b8b06fe03 Merge pull request #4602 from Silvertree2010/perf/export-speed
Speed up PDF export by about 24 %
2026-10-27 13:41:24 -04:00
NetterSilverandClaude Opus 5.5 de3bfaf049 Keep harfrust shape plans per face
Each shaping call let harfrust compile a new shape plan (the face's
lookups for the run's script, direction, language and features), which
took about as long as shaping a short run. Each thread now keeps up to 64
plans (the oldest dropped to make room), found by face and harfrust's
ShapePlanKey::matches, and passes the plan to the shaper. A kept plan is
only used for the face it was made for: the face id (from one process-wide
counter, so no two faces share it) together with the font bytes' address,
length and face index. The plan is the one the shaper would have made for
the buffer, so the glyphs are the same; tests shape with kept and fresh
plans and compare, and check the per-thread bound.

PDF export of the export_bench document (259 pages), interleaved with main
(A/A within 1 %): median 421 ms -> 319 ms (-24.2 %), measured on a loaded
machine; on a quieter run earlier 294 ms -> 224 ms (-23.8 %). PNG export of
page 1 (which lays out the whole document): about -34 %. The PDF and PNG
bytes are identical to main's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:39:37 +01:00
NetterSilverandClaude Opus 5.5 ffa127c615 Skip per-character table lookups for plain text in layout and PDF export
Shortcuts that give the same answers as the lookups they skip:

- is_rtl: nothing before U+0590 is right to left, so Latin, Greek and
  Cyrillic text skips the bidi class search.
- strong_rtl and is_mark: ASCII letters are the only strong ASCII
  characters, and no ASCII character is a mark.
- cluster_starts: in ASCII text every character starts a grapheme except LF
  after CR, and no ASCII character joins a cluster.
- display lines: a glyph run's vectors and text are sized up front, and the
  symbol-font check is made once per run instead of once per cluster.
- PDF glyph runs: the baseline (the y most glyphs share) is only worked
  out for italic runs, the only ones that use it, and a run on one baseline
  returns it without counting.

Tests check each shortcut against the full lookup (every code point for the
bidi classes, every ASCII pair for the clusters).

PDF export of the export_bench document, interleaved with main: median
293 ms -> 241 ms (-18 % with the previous commit). The PDF bytes are
identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:39:09 +01:00
Martin BallandClaude Opus 5.5 04ebc4ed42 #3995: Thai one-face fallback tests skip unless a Thai font with Latin is installed
Noto Sans Thai and its siblings have no Latin letters or digits, so on a
machine whose only Thai fonts are Noto no face covers the test run and
fallback_for_run rightly returns None. The guard now asks whether the Thai
substitute covers the whole run, which doesn't go through fallback_for_run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
2026-10-27 09:52:32 +00:00
aiongg a2a0d69193 refactor(fonts): split fontdb.rs into fontdb/ 2026-10-26 21:16:49 +00:00
aiongg d3d7152b45 refactor(fonts): move tests_sysfonts.rs to fontdb/tests/sysfonts.rs 2026-10-26 20:54:02 +00:00
aiongg c5b1efb97d refactor(fonts): move fontdb tests to fontdb/tests.rs 2026-10-26 20:54:02 +00:00
aiongg ffb21794c5 refactor(fonts): move fontdb/user tests to fontdb/user/tests.rs 2026-10-26 20:54:02 +00:00
Dylan M. Taylor d859c673c1 Sonar: reduce cognitive complexity in highlight, review and outline commands 2026-10-26 16:01:36 -04:00
Martin BallandClaude Opus 5.5 328a58dd03 #3869: review — hint_strike is native-only like HINT_STRIKE (wasm build); composite and 0x7FFF contour-count tests
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HUodYU9r4mPHed4pPsjHC2
2026-10-26 02:43:10 +00:00
Martin BallandClaude Opus 5.5 ddda3c4670 #3869: review — the draw that turns hinting off is redrawn unhinted, so the outline cache never keeps a hinted glyph from a tripped breaker
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HUodYU9r4mPHed4pPsjHC2
2026-10-26 02:09:31 +00:00
Martin BallandClaude Opus 5.5 b094b29958 #3869: review — WOFF2 point pre-check reads glyf nContours as u16 like the decoder (only 0xFFFF is composite)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HUodYU9r4mPHed4pPsjHC2
2026-10-26 02:09:24 +00:00
Martin BallandClaude Opus 5.5 2d9bcb6b1b #3692: review — fonts timing test gets a margin a busy CI machine can't break
named_instances_and_collection_members_are_capped: 2 s -> 10 s. The face counts in the test
(one face for a million identical members, MAX_FILE_FACES from add_font) are what catch the old
code, which built a face per member; the time (under 0.1 s on a loaded machine, so 10 s is over
100x) only backs up "read once".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
2026-10-25 21:02:21 -04:00
Martin BallandClaude Opus 5.5 b059da2d4e #3692: review — docs: why the hinting breaker is timed, and what brotli-decompressor brings
The hinting breaker stays wall-clock: skrifa's interpreter budget is fixed,
counts instructions rather than the points each one moves, and reports no
work count, so there is nothing deterministic to bound. The comment now says
the result can depend on the machine by design, and that only faces needing
the interpreter for their contours are hinted at all. Cargo.toml notes that
brotli-decompressor is the crate and version wuff already uses: pure Rust,
forbid(unsafe_code) by default, wasm-ready, BSD-3-Clause/MIT.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
2026-10-25 21:02:21 -04:00
Martin BallandClaude Opus 5.5 57e9e0f244 #3703: review — font store adds run one at a time, so racing adds never share a temporary or a file
CodeRabbit on #3869: two concurrent install_font calls could pick the same
destination and temporary, and the leftover-unlink retry could remove the
other call's open temporary. write_unique now holds a process-wide lock.
In-process only (noted in a ponytail comment).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
2026-10-25 21:02:21 -04:00
Martin BallandClaude Opus 5.5 68d45318bb #3692: review — a colour glyph's clip outline is drawn within the payload left, refused before it is built whole
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
2026-10-25 21:02:11 -04:00
Martin BallandClaude Opus 5.5 d69c386f8f #3692: review — a font's temporary file is created afresh, so a link planted at its name is not written through
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
2026-10-25 21:02:11 -04:00
Martin BallandClaude Opus 5.5 e541a6f62f #3692: hinting turns off only when slow draws are a real share of a face's draws
Ultrareview finding 5: the slow-draw count only grew, so three draws slowed
by a busy machine at any point in a session switched hinting off until
restart, breaking hint-reliant faces (MingLiU, DFKai-SB). Hinting now stops
only when at least three draws were slow and they are at least one in 16 of
the face's hinted draws: a face slow throughout still stops at its third
glyph, and slow glyphs spread among fast ones are still bounded. Counting
interpreter work instead of wall-clock stays with #3827.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:11 -04:00
Martin BallandClaude Opus 5.5 bbe1dad1e7 #3692: hinting turns off after three slow glyphs, not one
CodeRabbit review of finding 62's fix: one hinted draw slowed by a busy
machine (scheduler pause, page fault) switched hinting off for the face for
the rest of the process, leaving hint-reliant faces with timing-dependent
missing strokes. A strike counter now drops hinting only at the third slow
glyph.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:11 -04:00
Martin BallandClaude Opus 5.5 c7148d6ba9 #3692: hint-reliant fonts with huge zones are drawn unhinted, and a slow hinted glyph turns hinting off
Finding 62 (partial): skrifa 0.47 caps a hinting run at 1M instructions
but not the points each moves (SHZ, IUP... walk a whole zone), and a font
opts into the interpreter by its family name. Faces asking for a
twilight zone over 1,024 points or a stack over 4,096 values get no
interpreter; a glyph whose hinting buffer passes 256 KiB is drawn
unhinted; and (native) one hinted glyph over 50 ms turns hinting off for
its face. Residual: a face within the limits can still spend about
1M x zone size point moves on one glyph (and its prep program) before
the strike lands; the full fix is a point-work budget in skrifa or an
isolated worker with a deadline.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:01 -04:00
Martin BallandClaude Opus 5.5 b246ea166a #3692: COLR gradient stops and clip paths count against a per-glyph budget
Finding 61: Recorder copied every gradient's stop list (and every clip
glyph's outline) per op, capped only by op count, so a compact COLR graph
reusing one long stop list could expand to billions of stops. Stops and
path elements now share MAX_PAYLOAD (65,536) per glyph, charged before
copying; past it the glyph falls back to its outline and no further clip
outlines are built. The render cache charges each glyph's real ops,
stops and path bytes (ops_bytes) instead of a flat 64 bytes per op.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:01 -04:00
Martin BallandClaude Opus 5.5 ce7e4ea28e #3716: a font file's faces are capped before they are built
Finding 59: enumerate_faces took a collection's member count straight from
its header (a TTC can list one table directory millions of times) and
font_faces read every fvar named instance (up to 65535, with a linear
style dedupe and a name-table lookup each). Every entry became a FontFace
kept for the session, and add_fonts then checked each new face against
every face in the database.

Now a file yields at most 256 faces (MAX_FILE_FACES; user fonts ask for
one more so they can still be refused as "more than 256 faces"), named
instances are read to the same cap, a face with the family and style of
an earlier one in the file is skipped (repeated collection offsets), and
add_fonts dedupes against an indexed set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:01 -04:00
Martin BallandClaude Opus 5.5 6c6326486f #3692: font names are debug-quoted in log warnings
Finding 46: install_font logged the caller-supplied file name raw when the
font couldn't be stored, and the desktop log writes line breaks as they
come, so a name could forge extra log records. The name is now logged
with {:?} (escaped and quoted), and so are the font-derived family names in
the two glyph-outline warnings, which come from a document's own fonts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:01 -04:00
Martin BallandClaude Opus 5.5 5155d2c18c #3703: WOFF and WOFF2 sizes are checked before the decoder allocates from them
Findings 39 and 70.

39: wuff inflates each WOFF 1.0 table into a buffer sized from the
directory's origLength, before anything checks it against our 128 MB
cap, so a tiny file could ask for gigabytes. The directory's table sizes
are now summed first and a file over MAX_DECODED_BYTES is TooLarge.

70: wuff's WOFF2 glyf reconstruction allocates a glyph's points from its
nPoints counts and checks the 65536-point limit only afterwards. The
brotli stream is now decompressed by us (brotli-decompressor, already in
the tree through wuff) and every transformed glyf table's per-glyph point
totals are checked before wuff unpacks them; a glyph over the limit (which
wuff would refuse anyway) is refused up front.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:02:01 -04:00
Dylan M. Taylor e5cffe7641 Merge pull request #4233 from VoiceLessQ/fix/small-cleanups
Small fixes: MERGEFIELD in any case, 18 SonarCloud closures, one unused constructor
2026-10-25 16:45:39 -04:00
skylar taylor-barrick 5b46f9ba4d M-egui037: fix fontless tests and document migration behavior 2026-10-25 15:24:23 +01:00
VoiceLessQ 65a0dc8dc6 Method paths for 18 closures that only call a method (SonarCloud S1612)
SonarCloud's analysis of main at b4da59f0c lists 23 closures such as `|v| v.as_slice()` that hand their argument straight to one method. This passes the method itself: `Vec::as_slice`, `str::to_ascii_lowercase`, `f64::total_cmp` and the like, all forms the repo already uses elsewhere. Behaviour is unchanged.

Five stay. The four in crates/proof/src/es/mod.rs sit in code that #4191 replaces, and its replacement already uses `PoisonError::into_inner`. In crates/engine/src/io/safe_save.rs the path would be `<dyn std::error::Error + Send + Sync>::is::<Guided>`, which reads worse than `|inner| inner.is::<Guided>()`.
2026-10-25 01:19:28 -02:00
skylartaylor a605fa5ca7 M-EGUI037: reconcile WordCraft main and preserve IME editing 2026-10-25 02:19:38 +02:00
skylartaylor c4974d9e8d egui037: reconcile current main, qualify dependency pins and preview accessibility 2026-10-24 23:22:06 +02:00
aiongg cbc8a23cb2 Merge PR #3888: Release: embed the current craft-fonts, with the Georgian face 2026-10-24 20:38:41 +00:00
Dylan M. TaylorandClaude Sonnet 5.5 ba7e7293e4 Sonar S1612: pass PoisonError::into_inner instead of a closure (#4102)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 04:33:12 -04:00
Dylan M. TaylorandClaude Sonnet 5.5 01d0c63451 Sonar S1612: use fn paths for simple method closures (#4103)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 04:33:08 -04:00
47edcd2ff2 chore: resolve SonarCloud maintainability issues (#4083)
* chore: resolve SonarCloud maintainability issues

* fix: restore compiling refactors, revert behaviour changes, run cargo fmt

- fontdb::norm: keep the closure for filter (char::is_alphanumeric takes
  self, not &self, so it does not fit Iterator::filter).
- story.rs: write_mark_rpr needs &mut self (rev_attrs, rpr_change).
- Revert the viewport meta change and the return 0 added to
  workspace_version/sha256, which changed behaviour or masked failures.
- cargo fmt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Import Document in odt page_parts after dropping glob re-export

* fix: apply_kind_properties binds ContentControl; cell_ref keeps char closures

* fix(engine): pass case helpers by value (clippy needless borrows)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
2026-10-24 03:54:19 -04:00
skylartaylor fd071202e7 E37: migrate WordCraft to egui 0.37 with guarded IME and capture lifecycle 2026-10-24 08:30:31 +02:00
Davit MatchakhelidzeandClaude Opus 5.5 4081b5f285 fonts: expect the bundled Thai face from the embedded font list
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-24 01:21:49 +04:00
Davit MatchakhelidzeandClaude Opus 5.5 b7d07b1b13 fonts: check the bundled Thai face covers the run when craft-fonts embeds one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-24 01:04:14 +04:00
Davit MatchakhelidzeandClaude Opus 5.5 a781f90fc0 Release: embed the current craft-fonts, with the Georgian face
Pin craft-fonts a71c20e3 in ci.yml, freebsd.yml and release.yml; web-craft-fonts so the web
build keeps the Geor face; the interface font definitions add it; the Thai run test
accepts the embedded Thai face that pin brings (#3561).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-23 02:27:22 +04:00
aiongg acfdc0cb5f Merge PR #2500: Resolve installed fonts by PostScript face name
Test-font pins: #3389's kept, #2500's dropped (layout tests.rs, tests_pages.rs, render lib.rs). crates/fonts/src/fontdb.rs: #2500's PostScript-name match moved into #3078's family_paths.
2026-10-22 01:50:29 +00:00
aiongg 3017804459 Integration: cargo fmt 2026-10-22 01:40:29 +00:00
aiongg 61addba013 Merge PR #3078: #1549: a Thai run with Latin finds one installed font covering it all
ROADMAP.md: status line once (this change first, then main's); progress-log and history rows union in date order
docs/layout-parity.md: status line once (this change first, then main's); history rows union in date order
2026-10-22 01:22:12 +00:00
aiongg 029945a313 Integration: #2673 Khmer substitutes return the (list, flag) pair after #3245 2026-10-22 01:21:47 +00:00
aiongg 13e33ad072 Merge PR #2681: Layout: a Symbol bullet's line keeps Symbol's height without the font
docs/layout-parity.md: one status line naming this change first, then main's; history rows union
2026-10-22 01:21:47 +00:00
aiongg cb8d365923 Merge PR #2673: Fonts: Times New Roman keeps Liberation Serif under the roman-font rule (#1613)
docs/file-format-parity.md: one status line naming this change first, then main's
2026-10-22 01:21:32 +00:00
aiongg 32fe53c0ac Merge PR #2136: Linux: scan the Flatpak host font folders (#1937)
crates/fonts/src/fontdb.rs: PR's new test added before main's renamed run_fallback_is_cached_per_char_set
2026-10-22 01:20:42 +00:00
aiongg 78f2ef78c6 Merge PR #3245: Substitute a system Khmer font for a missing Khmer document font 2026-10-22 01:19:49 +00:00
aiongg 23e1985189 Merge PR #2104: Fonts: emoji prefer a colour emoji font on Windows (#1754, #1874) 2026-10-22 01:19:49 +00:00
Магда М.Г. 7f2f45743a Layout: Apple's one-em Symbol takes Windows Symbol's line height, as in Word for Mac
Word for Mac spaces Symbol lines at 1.222 em (measured at 11 and 20 pt)
even with Apple's Symbol installed, whose own line is one em. Symbol's
metrics now apply when the resolved face is one em tall as well as when
the font is substituted (fonts::word::symbol_line).
2026-10-20 12:14:18 +03:00
Mikael Tollefsen 50a89f4fa5 #1549: a run fallback is cached under the face count it chose from
fallback_for_run cached its answer under the face count read after the
search. A face loaded in between by another thread (its own search, or a
family a document names) made a miss look current: later lookups at the
new count got the stale miss (review on #3078). The search now returns
the count of the loaded faces its last choice was made from, and the
answer is cached under that; anything loaded since makes the next lookup
search again. No lock is added, so cached lookups from parallel layout
stay contention-free.
2026-10-20 11:06:44 +02:00