Poison recovery, OsStr::to_str and char predicates only; S4325 casts are
f64 to f32 and stay.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
fallback_for read the locale (and the environment) on every call, including
the layout hot path; it is computed lazily, only for a character of a script
with usual families.
* Dev profile: line tables only for workspace crates, no debug info for dependencies
* xtask ci: dev-profile gate, nextest, tests without examples, wasm --keep-going
* One integration-test binary per crate
* xtask ci: run effectcraft-gpu's shared-device tests in one cargo test process
* xtask ci: run every stage and report all failures
* xtask ci: name the stage type (clippy type_complexity)
* xtask ci: run every test pass and report all failures
* cargo fmt
The test checked that Japanese text falls back to an installed Japanese
family, but left the process-global CJK locale at the host's: on a zh-CN
host with a Chinese face installed, han_order puts the Chinese families
first for the shared ideographs and the Japanese-family assertion fails.
Set ja-JP under the TEST_CJK_LOCALE lock and clear the request when the
test ends, as the zh-CN test beside it does.
Tests: effectcraft-text 75 lib + 3 integration passed; cargo fmt --all -- --check passed.
The macOS fallback test selected the first installed family from the
Simplified list, so a Mac that also has PingFang SC (or Microsoft YaHei /
Noto Sans CJK SC) picked that one and then failed an assertion that only
accepted the three macOS faces. Accept any family from the list -- every one
is Chinese -- and check that each installed macOS face covers the shared
ideographs instead.
Also exercise fallback_for itself under a zh-CN request, which the direct
resolve_in lookup bypassed, and serialise the process-global locale with the
layout CJK test so a cached family order cannot leak between them.
Tests: effectcraft-text 75 lib + 3 integration passed; effectcraft-render
233 passed, 1 ignored; cargo fmt --all -- --check and cargo clippy -p
effectcraft-text --all-targets -- -D warnings clean.
macOS does not install the on-demand PingFang SC (macOS 27 on an English
install ships Hiragino Sans GB, Heiti SC, Songti SC and STSong instead),
and the Simplified Chinese fallback list named only Windows and Linux
families. A Chinese character therefore fell through to the exhaustive
search, which picks the first installed face that covers it — Hiragino
Sans, the Japanese face — so Chinese text in a composition was drawn
with Japanese glyph forms (#453). Japanese kana and kanji were already
handled, and a Chinese-only character the Japanese face lacks (e.g.
页) still found a Chinese face, which is why the symptom looked
selective.
Add the macOS Chinese faces to the fallback lists: Hiragino Sans GB,
Heiti SC, Songti SC and STSong for Simplified Chinese, Songti TC for
Traditional (Heiti TC was already named). The interface language still
chooses between the scripts and an English interface keeps the
Japanese-first order for shared Han ideographs, as designed in #312;
what changes is that a Chinese-locale request has an installed Chinese
family to answer it on macOS.
Tests: a text-layer render test draws Japanese and Chinese text and
checks glyph coverage (skipped where no CJK face is installed); a layout
test checks every CJK glyph comes from a face that has the character, so
a missing-glyph box cannot creep back; and the fonts test fails without
the list change ("Hiragino Sans GB missing from the Han list"), passes
with it, and walks the Han branch's list to prove the first installed
Simplified family is a Chinese face, not a Japanese one.
Verified with CARGO_BUILD_JOBS=2 RUST_TEST_THREADS=2:
effectcraft-text 64 passed; effectcraft-render 233 passed, 1 ignored;
cargo clippy -p effectcraft-text -p effectcraft-render --all-targets --
-D warnings clean; cargo fmt --all -- --check clean. A 900x240 render of
"骨直真具画 中文测试" with the zh-hans request now uses Hiragino Sans GB
and differs from the Japanese render, which still uses Hiragino Sans.
Windows ships a UI variant of its Japanese and Chinese families (`Meiryo`/`Meiryo UI`, `Yu
Gothic`/`Yu Gothic UI`, `MS Gothic`/`MS UI Gothic`, `Microsoft YaHei`/`Microsoft YaHei UI`,
`Microsoft JhengHei`/`Microsoft JhengHei UI`) whose kana and kanji are drawn for the screen, and its
own link table for `Segoe UI` picks the UI one for everything it draws. The interface should ask for
it - but the same lists feed `fonts::fallback_for`, which shapes composed frames, so reordering them
would change how existing projects render. The two orders are therefore separate: this leaves
`crates/text/src/fonts.rs` exactly as it is and gives the interface its own candidate list in
`install_script_fallbacks`, which falls back to the shared choice when the platform has no UI variant,
none is installed, or it does not cover the probe.
`PMingLiU` is in the interface list for its proportional spacing, not as a sibling of `MingLiU`: it is
MingLiU's proportional relative.
The rendered order is pinned by a test now: the Japanese list keeps `Yu Gothic` ahead of `Yu Gothic
UI`, and no UI name appears in the lists a document renders with. `the_interface_asks_for_the_
platforms_ui_faces` pins the interface list and checks that every other script the install probes has
none, so a script that draws correctly in any face keeps the shared order.
Review fixes: the locale is mapped once (the call passes the language, the helper maps it), the new
test sits after the #394 test it had split from its doc comment, and the fonts.rs test asserts instead
of panicking inside a closure. Rebased on current main; #1395 has not landed yet, so there is nothing
to rebase after it.
The face's own style (Figtree's Light) is its default instance, so it
resolves without axis values and lays out as before, on the static path.
docs/agents.md says how named instances are selected.
The font scan read only whether a face has an fvar table, so a variable
family offered one style (its default instance), fontStyle = "Black"
picked that face with synthetic bold, and an instance's PostScript name
(Figtree-Black, what After Effects scripts use) found no face at all.
The scan now reads the named instances (style, axis values, PostScript
name, or the one Adobe Technical Note #5902 derives). They are styles of
the family in text.fonts and the font menus, a style, PostScript name or
full name selects the instance, and layout uses its axis values as the
base variations; axes set on the text still win.
fallback_for tried every face that was already loaded before the usual
families of the character's script. Which faces are loaded depends on
every earlier lookup. On macOS, /Library/Fonts/Arial Unicode.ttf makes
Arial Unicode MS the first face in the scan, and it covers every script
that has usual families except Sinhala. The variable font search loads
it, and so does a fallback for a character that it covers and the
bundled faces and the usual families lack, such as the Simplified
Chinese 们. After that, Hebrew, Arabic, Indic, Thai and CJK text in a
Latin family fell back to Arial Unicode MS instead of Tahoma, Kohinoor
Devanagari, Tamil Sangam MN, Hiragino Sans or Apple SD Gothic Neo. In
the effectcraft-text unit tests,
other_scripts_fall_back_to_a_plain_system_family and
cjk_falls_back_to_a_system_family failed in 7 of 100 runs on a Mac, and
in 24 of 100 while other builds were running, depending on which faces
the other tests had loaded.
The usual families now come right after the bundled faces. family_faces
lists the installed faces of the script's families in the order they
are tried, each in the preferred face's style. The list is cached per
script and preferred face, and adding faces or changing the CJK locale
clears it. Faces that are already loaded come next, then the per-block
cache of the search over every installed font, then the search itself,
as before. A search result made for another character of the block (an
Armenian character next to Hebrew, Lao next to Thai, or a character
that no face covers) is no longer used for a character that a usual
family covers.
tests/script_fallback.rs runs in a test binary of its own. It records
the fallback for eight characters of those scripts, looks up U+0590,
Armenian and Lao and runs the variable font search, and checks that the
eight get the same faces again. It also checks that U+08A4, which on
macOS only faces outside the Arabic list cover, keeps its face after a
lookup of U+0870, which no face covers.
WenQuanYi Micro Hei is a Simplified-oriented face, so keeping it in the Traditional list - even as a
last resort - could still draw Simplified forms, which is the one thing the split is for. Heiti TC
and Hiragino Sans CNS are the macOS Traditional faces the family's own lists use (photocraft's
`CjkScript::TraditionalChinese`). Dropping the entry does not lose the face: the generic scan behind
the script lists still finds it when nothing Traditional is installed.
The test now walks the `FallbackScript::Han` arm as well, so a list wired to the wrong script fails
it, and it refuses a WenQuanYi entry in the Traditional list.
The Han fallback list carried a Simplified and a Traditional face for every platform (Microsoft YaHei
next to Microsoft JhengHei, PingFang SC next to PingFang TC), Simplified first. The fallback search
keeps the first family whose face covers the character, and the two scripts share their code points -
only the glyph forms differ - so a zh-hant interface was drawn with Simplified forms, and a
Traditional face could not win on a machine that also had a Simplified one.
The list is now two, picked by the script the locale in force asks for: Hant, or a Taiwan, Hong Kong
or Macao region. A bare zh stays Simplified, as the interface's own locale mapping resolves it.
`asks_traditional_chinese` normalises case and separators, and `chinese_families_for` is pure, so the
choice is testable without installed fonts. The Han order and the Japanese and Korean lists are
untouched.
Verified: cargo fmt --all --check clean, cargo clippy -p effectcraft-text --all-targets -- -D warnings
clean, cargo test -p effectcraft-text 54 tests pass.
Problem: on Linux the font list only scanned /usr/share/fonts,
/usr/local/share/fonts, ~/.local/share/fonts and ~/.fonts. The ArtCraft
Launcher installs EffectCraft as a Flatpak, where those folders hold only
the runtime's fonts and the host's fonts live under /run/host, so installed
fonts were missing (25 families instead of 320 on the reporter's setup).
$XDG_DATA_HOME/fonts, $XDG_DATA_DIRS/*/fonts and fontconfig <dir> entries
were never scanned either.
Fix: a new font_dirs module lists the usual folders, the Flatpak host
folders (/run/host/fonts, local-fonts, user-fonts), the XDG data folders
and the <dir>/<remap-dir> entries of the fontconfig configuration
(FONTCONFIG_FILE or /etc/fonts/fonts.conf, /usr/local/etc/fonts/fonts.conf,
and ~/.config/fontconfig), following <include>s with prefix="xdg",
"relative" and ~ expansion. It uses a small tag scanner (no new dependency),
bounds the include depth, the number and size of files read, and reads each
file once. Folders are deduplicated before the walk, and the walk depth
goes from 4 to 8. docs/agents.md lists the folders and how to grant the
Flatpak an extra one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: After Effects 26.3's font menu can show only variable fonts.
EffectCraft's font menus (Character panel, Properties ▸ Text) had no such
filter, and text.fonts / MCP list_fonts didn't say which families are
variable.
Fix: the font scan marks a face variable when its table directory has an
fvar table declaring axes (header read only, no extra file loading). The
font menu gets a "Variable fonts only" checkbox above the results; it
combines with the typed search, keeps the keyboard in the search field and
stays set while the app runs. text.fonts reports `variable` per family and
takes `{variable: true}`; list_fonts passes it through. Catalog rows for
ja, zh-Hans, zh-Hant and uk.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tool can hand EffectCraft its own font library (a path list) without
installing the fonts system-wide; the folders are scanned like the
system ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cargo +1.95 clippy --workspace --all-targets --release -- -D warnings
stopped on seven `if` blocks that clippy wants folded into their match
arm (psd, text, effects x2, pdf x2, render). Each becomes a match guard.
The behaviour is unchanged: when a guard is false the value falls through
to arms that can't match it and then to `_ => {}`. The one exception is
the PDF PostScript calculator, where a `{` past the nesting limit would
fall through to the operator arm; it keeps its own no-op arm, so it is
still skipped rather than pushed as an operator.
Cause: two things kept Arabic out of a layer name on Windows.
- Windows switches the keyboard layout with Alt+Shift. Releasing Shift
before Alt left Alt held alone, and the in-window menu bar (#279)
counted the release as an Alt tap: it took the keyboard focus, the
rename field closed, and the letters typed next were swallowed by the
menu bar. The Project panel's rename field lost them the same way.
- The UI fonts are Inter plus the CJK system fallbacks, so Arabic,
Hebrew, Indic and Thai names that did arrive (pasted, or from a
project) showed as missing-glyph boxes.
Fix:
- An Alt tap only counts when Alt went down with no other modifier
held, so Alt+Shift in either release order is no tap; a plain Alt tap
still focuses the menu bar.
- The UI registers installed system faces for Arabic, Hebrew, Devanagari,
Bengali, Gurmukhi, Gujarati, Tamil, Telugu, Kannada, Malayalam and Thai
after the CJK ones, the same way (native only; the web build has no
system fonts). A face covering several scripts is registered once
(Segoe UI: Arabic and Hebrew; Nirmala UI: every Indic script).
- The text engine's fallback tries each script's plain platform families
(Segoe UI, Nirmala UI, Leelawadee UI, Geeza Pro, Kohinoor, Noto…)
before any covering face, so neither the UI nor a text layer gets a
calligraphic face that merely sorts first.
- The Timeline's rename field registers `timeline.layer.<id>.nameEdit`.
egui 0.36 shapes each run of one face, so Arabic letters join and a word
reads right to left; it has no bidirectional layout, so the words of a
multi-word Arabic name (and mixed Arabic/Latin) still follow each other
left to right.
Tests: ui_inline_edit a_layout_switch_keeps_the_name_field_and_arabic_
names_are_kept (Alt+Shift both ways in the layer rename, Arabic typed and
stored; Hebrew/Devanagari/Thai in the Project panel; a plain Alt tap
still focuses File; failed before: the field closed), theme
installed_script_fallbacks_draw_arabic_hebrew_indic_and_thai_names
(failed before: replacement glyph for Arabic) and
arabic_words_are_joined_and_read_right_to_left, text
other_scripts_fall_back_to_a_plain_system_family and the script
classification.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test picked the first axis that moves an advance and skipped when none did, so a variation path
that broke completely - every axis measuring the same - took the skip and hid exactly the regression
the check is there for. Only a face with no axis carrying a range skips now.
When no ranged axis moves the advance (a monospaced family holds its advances on purpose), the
outline must still move along `wght`, or, on a face without one, along the first axis that carries a
range; a path that moves neither fails with a message that says so.
Verified on Linux with Google Sans Code and Bahnschrift installed:
- the normal path passes (the MONO axis is the one that moves the advance),
- forcing the branch where no axis moves the advance passes, so a legitimate monospaced face does
not fail,
- forcing that branch and freezing the outline area fails with "wght 300..800: neither the advances
nor the outlines move, so variation support looks broken".
`cargo fmt --all --check` and `cargo clippy -p effectcraft-text --all-targets -- -D warnings` clean.