Review: the test chose its Thai face, and checked each glyph's face, by
U+0E01, which the sample doesn't contain. It now asks for a face that has
every Thai character of the sample, and checks that each glyph's face has
the characters of its cluster.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thai typed into a layer whose font has no Thai glyphs (Inter, which new
text starts in) was drawn as missing-glyph boxes, on the desktop and on
the web, even with a Thai face embedded or installed: FALLBACK_FAMILIES
listed Latin, CJK, Hangul, Arabic and symbol families but no Thai one.
Noto Sans Thai, Noto Sans Thai UI, Leelawadee UI, Thonburi and Tahoma go
after Noto Sans Arabic; they are only consulted for characters the
layer's own font lacks. Tests: the list has a Thai face for each
platform, and with one installed or embedded every Thai cluster in a
Latin layer, marks included, is drawn from it (skips without one).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signs such as U+2316 and U+2912 are in no text face. The fallback list
had the macOS and Windows symbol fonts (Apple Symbols, Segoe UI Symbol)
but nothing a Linux install has, so they drew as empty boxes there even
with Noto Sans Symbols 2 or Noto Sans Math installed. Adds Noto Sans
Symbols, Noto Sans Symbols 2 (and the Symbols2 spelling some packages
use) and Noto Sans Math after them.
Found while testing crates/text/src/layout.rs:1070 (shape_segment calls TextCase::apply_after one char at a time; crates/doc/src/text.rs:124 apply_after)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
JIS X 4051's line-start (行頭禁則) and line-end (行末禁則) sets were mostly
there — closing punctuation and brackets, small kana, the prolonged sound
mark, iteration marks and opening brackets already never start or end a line —
but a few categories fell through:
- the halfwidth small kana ァ–ッ (U+FF67..U+FF6F) and the halfwidth voicing
marks ゙゚ (U+FF9E/U+FF9F) were not line-start prohibited, so a wrapped line
could begin with them;
- the spacing (semi-)voiced sound marks ゛゜ (U+309B/U+309C) and the kana
iteration marks 〱〲〳〴〵 (U+3031..U+3035) likewise;
- the halfwidth parentheses ⦅⦆ were missing from the line-end/line-start
sets.
Add them (ranges in `class`, the two parentheses in the OPEN and CLOSE
strings), and pin every JIS category in the behaviour suite: the existing 22
cases stay and 9 join them (halfwidth small kana and small tsu, halfwidth and
spacing voicing marks, kana and vertical kana iteration marks, both halfwidth
parentheses), plus a wrap case where a halfwidth small kana moves down with
its base (キャ) and a case showing a manual newline may still start a line with
a prohibited character (禁則 is for automatic wrapping only).
`break_opportunities` fails on the old sets at the first new halfwidth
small-kana case.
Latin wrapping, ideograph breaks and the rest of the behaviour suite are
unchanged, and the headline numbers stay as they are.
Verified: cargo test -p figcraft-text --lib 92 passed; cargo clippy
-p figcraft-text --all-targets -- -D warnings clean; rustfmt clean on the
touched files.
Docs: docs/target-app-parity.md text behaviour row and ROADMAP progress log,
revision history and header.
Adds crates/ui-egui/locales/ka.toml (ქართული): every key of en.toml (1,730 UI
strings), every command label (567) and every menu wording (31), marked as a
draft. Georgian has no letter case and keeps a noun singular after a numeral,
so plural entries give the same text for one and other. System picks it for
ka, ka-GE and ka_GE.UTF-8; pref.language's help text lists ka.
Inter and egui's fonts have no Georgian, so the UI draws it in craft-fonts'
Noto Sans Georgian: it joins the built-in faces in theme::install_fonts and
the web build keeps it (crates/text/build.rs). craft-fonts-ref moves to
a71c20e3 (storytold/craft-fonts#27, the commit that adds the face) in the
release and FreeBSD workflows, and the release builds the web version with
craft-fonts (web-craft-fonts: true).
Tests: the Georgian locale tags, draft status and count-neutral plurals, and
that the built-in Georgian face draws every Georgian letter in the catalog
(skipped without craft-fonts). Docs: localization-parity, translating, mcp,
README and ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
`caret` put the index right after a line break (a newline, or a line
separator) at the end of the line above, so after Enter the caret showed
on the previous line until something was typed, and Up/Down counted
lines from there. The index before the break stays at the end of its
line; the one after it is now on the next line, as after a wrap.
Selection highlights already started such lines at their own start
(#665); their comment no longer describes the old caret.
Figma stores Shift+Enter as U+2028. `.fig` imports keep it, but layout
only broke lines at a newline, so imported text ran soft-broken lines
together ("Option 1SL: …"), lost the empty line of a separator followed
by a newline, and put glyph bullets on one line.
A line separator now ends its line without ending the paragraph: no
paragraph spacing and no new list marker after it, the next line keeps
the item's indent, and it draws no glyph. A trailing one leaves an empty
last line, and a line it cuts short isn't stretched when justified.
Carets, clicks and selections asked `para_end && !last_line` to mean
"the line ends with a break char". Record that on the line instead, so a
break that doesn't end the paragraph can use the same rules. No
behaviour change: today only a newline ends a line early.
- text.rescanFonts offers a loaded family the files it hasn't read (styles
installed since) and refits every family that gained faces; `added` still
lists only the families that were unknown
- text.refit refuses an `ids` that isn't a list instead of refitting the
whole document
- text.loadFont with a file already loaded returns its families without
keeping its bytes or adding its faces again
- doc comments on the functions this change touches
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The system fonts are indexed once per process, so a font installed while
FigCraft (or `figcraft-cli mcp`) runs stayed missing until a restart.
- text.rescanFonts walks the font folders again and offers the families
installed since (loaded families keep their faces) → {added}
- text.loadFont {path | data} adds a font file without installing it → {families}
- both clear the layout cache so text lays out again in the new face
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Current macOS keeps PingFang and other on-demand fonts in its asset
store, which the font scan never walked, so Chinese text showed as
boxes. The scan now also walks the com_apple_MobileAsset_Font* folders
of /System/Library/AssetsV2 (and the older /System/Library/Assets) with
the same depth, size and file checks, and Hiragino Sans GB and Heiti SC
(preinstalled) join the Chinese UI faces and the text fallbacks.
Tested with a folder laid out the same way; not run on a Mac.
From review:
- The check ran after any file.open/openRecent/import result, so a
pasted or dropped image, reopening a file that was already open, or
Clear recent brought the window back. It now runs only for results
with a `summary` (a file opened, or pages imported), and never in the
browser.
- Text > Missing fonts... before the startup scan finished listed
installed fonts as missing. It now says it is checking the installed
fonts and lists them once that is done
(`figcraft_text::installed_fonts_known`, also true when system fonts
are opted out, where checks used to wait forever).
- ROADMAP progress log and file-format-parity status line.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Text in a family that isn't installed was drawn in a fallback font
without a word. Now:
- `text.missingFonts` lists the families a document names that no
installed or built-in font provides: text layers and their styled
ranges, text styles, and string variables bound to a font family
(every mode, through aliases), with the number of text layers.
- The desktop app shows a Missing fonts window after the user opens a
file that has any: once installed fonts are indexed (before that every
one of them would count as missing) and after any import report is
closed. Files named on the command line count. Programmatic opens and
the browser build never open it by themselves. Text > Missing fonts...
shows it for the file in front; Escape closes it.
- `figcraft-cli info` prints `missing font` lines (`missingFonts` with
--json).
- file-format-parity: the row claimed REST import warnings that no code
produced.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The .fig export now writes a numbered list's start on each of its lines,
so a reader that takes it per line or per list counts the same. Bullets
no longer keep a start. The field is named start_offset (it holds the
offset: 1 shows "2."). get_node reports listStartOffsets. The parity doc
lists what's still not read from .fig lists.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Figma stores where a numbered list starts counting on each text line
(listStartOffset; a list shown as "2." has offset 1). The import dropped
it, so every list counted from 1.
A paragraph's list settings now carry a start number (written only when
set, so older files open and save unchanged). Numbering counts on from
it where a list starts; items that go on counting ignore it. The .fig
import reads it, the .fig export writes it on a list's first line, and
plain paragraphs drop it. Toggling a list off ignores differing starts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Fonts activated through Adobe Fonts rendered in the fallback font without
a warning: Creative Cloud keeps them in its own folder as hidden files
named by number, which the font scan never read. The macOS scan now
includes ~/Library/Application Support/Adobe/CoreSync/plugins/livetype,
and files without an extension are indexed when they start with a font
signature. Fonts are read in place.
Closes#298
Text in the default Inter (or any Latin font) drew the missing-glyph box for every Hangul
syllable on macOS and Windows: no Korean family was on the fallback list, so the Korean
fonts those systems ship were never tried, and there is no default-font setting to work
around it. Apple SD Gothic Neo (macOS), Malgun Gothic (Windows) and Noto Sans CJK KR /
Noto Sans KR (Linux) now come after the Chinese and Japanese entries, so Han characters
resolve as before.
Fallback faces also follow the run's style: each fallback family gives its face nearest the
run's style instead of always its Regular, so Hangul (or kana) in a bold run comes out bold.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MAPjgJo17KasfbzMTbV65
Require resolved font entries to be regular files before indexing them. Preserve normal font symlinks and cover named pipes in a bounded child-process regression.
Assisted-by: OpenAI Codex (GPT-6)
Signed-off-by: IanHollow <72767437+IanHollow@users.noreply.github.com>