For a name that is neither a loaded nor an installed family,
FontDb::canonical built the list of names of every loaded face on
every call. Layout asks for a style's face for each span it shapes
and three more times per paragraph, so the time to lay out type set
in a font that is not installed grew with the number of fonts loaded:
a story of 1,000 paragraphs took a median of 409 to 507 ms in a
release build with 400 fonts added.
The database now keeps canonical's answer for each such name and style
until fonts are added or rescanned, and it keeps at most 4,096
answers. The same story takes a median of 56 to 71 ms, and canonical
gives the same answers as before.
Docs: docs/target-app-parity.md.
A character style's OpenType features took nine tags. OtFeatures now
also keeps ss01-ss20, salt, titl, pnum, lnum, sups and subs (off by
default, one bit each in `extra`): they are accepted by the type
commands, stored with the style and passed to the shaper, so a font
that has them uses them.
The Affinity import test used ss01 as its example of a feature kept but
not rendered; it now uses cv01 for that (character variants still
aren't rendered) and keeps ss01 as one that is.
Fixes#1607
The font scan read /usr/share/fonts, /usr/local/share/fonts, ~/.fonts and the
XDG data folders. Packages add folders outside those to fontconfig's
configuration (TeX Gyre under /usr/share/texmf/fonts), so fc-list and other
apps showed fonts VectorCraft didn't have. The scan now also reads the <dir>
folders of /etc/fonts/fonts.conf and the files it includes (conf.d, the user's
own), as the fontconfig documentation describes them. The files are read as
they are: no library is linked and no program is run.
Opening an EPS whose threaded area type frame has a control point at
x = 8e115 failed with an internal error ("capacity overflow").
`eps_text_frames_never_panic` in `import_fuzz` found it with
PROPTEST_CASES=20000. Text layout flattens a frame to within 0.1 pt,
and for such a curve kurbo 0.13.1's `flatten` calls `reserve` for
`usize::MAX` quadratic pieces. Laying out text around a wrap shape with
such a curve failed the same way. Laying out type on a path along such
a curve panicked in kurbo's `solve_itp` ("attempt to shift left with
overflow") in builds with overflow checks, and in a release build it
did not finish within 20 s. Laying out area type in a triangle frame
that comes to a point at its top, at y = 1e20, did not finish either:
adding a lead to a baseline that large leaves the baseline where it is.
Text layout now leaves out a frame, a text wrap shape or the path of
type on a path that has a coordinate beyond 1,000,000,000 pt either
way, control points included, or a NaN coordinate in an anchor point.
Such a frame or path holds no text, and such a wrap shape is ignored.
PDF export uses the same bound (`MAX_COORD` in
`crates/pdf/src/export.rs`).
Text layout also steps a line down a frame cell at most 100,000 times
to find room for it. In a cell taller than 100,000 leads, a step is the
cell's height divided by 100,000; in a shorter cell, a step is one
lead, as before. Before this change, in a dev build, with triangle
frames that come to a point at their top, laying out text in one
2,000,000,000 pt tall took 9.8 s, and laying out a paragraph whose
Space Before is -1e300 pt in one 1,000 pt tall did not finish within
20 s.
A character missing from every installed font was remembered as missing for the whole
session, so installing a font that has it didn't help until a restart. A rescan of the
font folders now forgets those misses, and a search a rescan overtook doesn't record one.
Taken from #992 by sanyamsingh001, without its font file probe.
Co-authored-by: sanyamsingh001 <[email protected]>
The follow-up #966 left room for. With automatic kerning, horizontal type
with Proportional Metrics also applies `apkn`, and vertical type applies
`vkrn` and `vapk` along with `vpal`. Manual kerning turns them off, as it
turns off `kern`.
Vertical kerning used to cancel out: both top-to-bottom shapings had
harfrust's default `vkrn`. The baseline now has neither `vpal` nor the
kerning, so their difference carries both. A third shaping (`vpal` alone)
tells which advances `vpal` itself changed, and mojikumi steps aside only
for those. Vertical type without Proportional Metrics isn't kerned, as
before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adds the Character panel menu's Digits option (with the Middle Eastern /
Indic options, beside Paragraph Direction in the Paragraph panel) as a
character attribute: CharStyle::digits, saved only when set, with
text.setFormat {digits} and text.setRangeStyle {id, start, end, digits}
("default" | "arabic" | "hindi" | "farsi"; a bad value is an error).
Shaping draws every digit of the Western, Arabic-Indic (U+0660–U+0669)
and Eastern Arabic-Indic (U+06F0–U+06F9) sets in the chosen system,
whatever was typed, before font fallback, so a font without the
Arabic-Indic digits gets a fallback for them. The text, its clusters,
the caret, selection and search stay as typed, as with All Caps; SVG
export writes the digits as drawn.
Catalog rows for Digits, Arabic, Hindi and Farsi in the ten translated
languages. Docs: docs/mcp.md (Digits), type-parity.md (digit types
done; Character Direction, kashidas, composers and the split caret
still missing), target-app-parity.md and gaps.md.
Behaviour from the public documentation (a character-level option with
the values Arabic, Hindi and Farsi in the Character panel menu):
https://helpx.adobe.com/indesign/desktop/language-and-proofing/arabic-and-hebrew/choose-digit-types.html
Tests: doc (the mapping and serialization), text (glyphs of "page 3" in
Hindi/Farsi equal the typed Arabic-Indic digits' on the typed clusters;
skips without a font covering the three sets), engine (setFormat,
setRangeStyle, undo, error), svg (digits written as drawn), ui-egui
(the menu shows with the Indic options).
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Releases pinned craft-fonts at its first commit, with the Japanese fonts
only. The pin moves to its current main, as PdfCraft, LightCraft and
WordCraft have moved theirs: desktop builds embed Noto Sans Thai, Noto
Sans Arabic and Noto Sans CJK SC too (the web build still embeds only
the UI font and fetches Thai and Arabic beside the wasm). Packages
already copy every embedded font's licence.
The Thai loanword test no longer assumes the bundled fonts' widths: it
checks that no line ends inside โปรแกรม, whichever Thai font sets it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ctrl/Alt+arrows and double-click treated a run of Thai (Lao, Khmer,
Myanmar) without spaces as one word, and stopped at a word's tone and
vowel marks, which didn't count as part of a word. They now stop at the
word boundaries the line segmenter finds in those scripts (shared with
line breaking, so no more data ships), and combining marks count as
part of a word, also for combining accents in other scripts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The web build has no installed fonts, so Thai there showed as empty
boxes. With a craft-fonts checkout that has Noto Sans Thai, its Thai
font joins the document font database after the Arabic ones (the
fallback for Thai where no Thai font is installed) and the web build
fetches it before the app starts, as it does Noto Sans Arabic.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The font menu's script filter offered Japanese and Latin only, so Thai
fonts had to be found by name among every installed family. It now has
Thai too: the families whose character map has Thai. Latin lists the
families with neither Japanese nor Thai.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
These scripts don't separate words with spaces, and lines only broke at
spaces, hyphens and CJK characters, so a Thai paragraph in area type ran
on to the next space (or was cut wherever it overflowed). Each
paragraph's glyphs now carry the word ends ICU4X's line segmenter finds
in text of those scripts (its dictionaries on the desktop, its far
smaller LSTM models on the web), and a line may break after them as
after a space. Other text keeps its own breaks, and clusters (a
consonant with its vowel and tone marks) still never split.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Type tool's Left and Right arrows and Delete moved one code point at
a time, so the caret could stop between a Thai consonant and its vowel
or tone marks (or inside an accented letter or emoji sequence), where
typing or deleting splits the cluster. They now step over extended
grapheme clusters. Backspace still takes back one character at a time,
as Thai typists expect for a mistyped tone mark.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Thai typed in a font without Thai glyphs (Source Sans 3, a Latin font)
took the first loaded face that had them, or the first installed file
that did, in path order: on one Windows machine a display face (Kanit)
rather than Leelawadee UI. Characters of Thai, Lao, Khmer and Myanmar
now try fonts made for their script first, installed ones loaded on
demand (Leelawadee UI, Tahoma, Thonburi, Sukhumvit Set, Noto Sans Thai,
the TLWG fonts for Thai), then the fallback as before. Latin beside
them keeps the font chosen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The second half of #966. With Proportional Metrics on, a vertical segment is
also shaped top to bottom, with and without `vpal`: the difference gives
each glyph's change of advance down the column and its x and y placement.
An upright glyph (not tate-chu-yoko) in a font with vertical metrics takes
them on top of its cell, so it moves with its shorter cell; a font without
vertical metrics keeps `upright_cell`'s cell, and with the switch off
nothing is shaped twice and the column is as before.
As in horizontal type, Line-end Punctuation Half Width takes nothing more
off the punctuation `vpal` re-spaced and trims the punctuation it leaves
full height as before. Docs: mcp.md, and Proportional Metrics leaves the
missing lists in gaps.md, type-parity.md and target-app-parity.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
crates/text/build.rs (craft_fonts.rs) and crates/ui-egui/build.rs (credits.rs) wrote their
output on every run. Where cargo's recorded times lose their sub-second part (an ecryptfs home
directory, as Mint and Ubuntu offer at install), a file written just before its crate compiles
reads as newer than that compile, so the next build compiled the crate and everything above it
a second time: a full app rebuild for nothing after any build-script rerun. Unchanged output is
now left alone.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
A character attribute (Character panel menu with the East Asian options;
`text.setFormat` / `text.setRangeStyle {proportionalMetrics}`), saved only
when on. In horizontal type it applies `palt`; vertical type sets as before
until the vertical shaping change (`vpal`) follows.
Line-end Punctuation Half Width takes nothing more off the punctuation
`palt` re-spaced (the flush opening bracket, consecutive punctuation, the
line-end half width): the segment is shaped once more without `palt`, and
the glyphs whose advance differs are marked. Punctuation the font leaves
full width is trimmed as before. SVG writes `"palt" 1` with the
character's other features.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
After scaling 12 pt type 2x, the Character panel still showed 12 pt and
typing 10 pt gave 20 pt type. Character controls and text commands now
work in document points: the size, leading and baseline shift shown and
set are the stored ones times the text's scale along its em height, and
horizontal scale includes the ratio of the transformed em axes. Rotation,
reflection and shear stay in the transform, so text remains editable.
Groups' text counts, keyboard increments use the same units, Shrink
Text to Fit works in document tenths, and a collapsed transform rejects
dimensional edits without touching the document.
text.getRange takes effective: true for the document-space values.
A folder search reads at most 8 MB of each font file. The suitcase
reader reads a suitcase font's resource fork whole, up to 256 MB.
Before this change, the search applied its limit to each font only
after that read. One search thread could read 256 MB from a single
suitcase and hold a copy of its fonts as well. The search now reads a
suitcase font only when its resource fork is 8 MB or less. The font
scan still reads each suitcase font's resource fork whole, up to
256 MB.
Docs: docs/mcp.md.
When a document opens with fonts that aren't available, fonts used
only in its symbols included, VectorCraft lists them in a Missing
Fonts dialog after the missing linked file questions, one document at
a time, with Find Fonts and Close. Find Fonts opens Type › Find Font
on the first missing font of the layers' type, to replace it. After
Find Fonts or Close the dialog doesn't open again for that open; when
another dialog replaces it, it opens again once that dialog closes.
Search Fonts Folder (the Fonts folder File › Package writes next to
the document) and Find in Folder… search a folder and its subfolders
for the fonts' files, and Add Fonts copies the files the user chooses
into a Fonts folder next to the preferences. The desktop app and
vectorcraft-cli read that folder with the system's font folders. Type
› Find Font has a Find in Folder… button too when fonts the document
uses are missing. The web app shows no dialog; a status message says
how many fonts aren't available.
- text.missingFonts lists each missing family and style once,
whatever version the document's type names, and leaves out CSS
generic families such as sans-serif. text.findFontFiles searches a
folder on up to 8 threads and returns at once; text.findFontFiles {}
reports the search, and {stop: true} stops it. A search reads only
what the font scan reads of font files (their table directories and
name, fvar and OS/2 tables) and, on macOS, of suitcase fonts, and
lists a file for a font only when the file makes the font resolve
exactly. It does not enter app packages, the Program Files and
ProgramData folders, the system's folders at the root of a volume,
the Shared and Public folders in Users, a home folder's Library,
AppData, Applications, snap and dot folders, a Library folder's app
data folders or the folders the environment names for apps, and it
skips other hidden folders and does not follow links below the
picked folder. It ends after 60 s by default, after 10,000,000
entries listed, with more than 1,000,000 folders waiting, after
10,000 font files read or 1,000 kept, or once every font has a file;
Stop and Close end it too.
- text.addFontFiles copies only files the last search found. Of
those, it copies only regular .ttf, .otf, .ttc, .otc, .woff and
.woff2 files and suitcase fonts with their resource fork, at most
256 MB a file and 1 GB in all. It never replaces a file: a file with
the same contents is kept, and when another file has that name, the
copy gets a number (Name 2.otf). The fonts are then scanned again,
and type set in them redraws.
- ui.missingFontsDialog and ui.findFontsInFolder open the dialog for
agents; docs/control-protocol.md lists its fields.
- File › Package gives a font file whose name has no font extension
the extension that matches its data (.ttf, .otf or .ttc).
- vectorcraft-text adds set_app_font_dir, which system_font_dirs
reads, WantedFonts, which reads a font file's names as the font scan
does, is_font_file and is_suitcase. #762 split its face reader, which
now takes read budgets: file_face_names_within tries the suitcase
reader first, and reader_face_names_within applies the budgets to
each font. vectorcraft-format adds write_new_with.
Docs: docs/mcp.md, docs/control-protocol.md, ROADMAP.
The styles were stored on the text and written to SVG's text-decoration,
but the type was drawn without the lines. vectorcraft_text::decorations
now gives each underlined or struck-through stretch of a run on a line
its bar, placed and sized from the font's own underline and strikeout
metrics (with fallbacks for fonts that have none). The canvas, Create
Outlines, PDF export (outlined and real text) and SVG's outlined text
draw them in the run's paint.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
hyphenation_exceptions_override_the_pattern failed when the round-trip
test set the process-wide list at the same moment: words missing from the
thread's override fell through to the shared store.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A font library can hold two versions of one family and style (a new
Montserrat-Bold.ttf beside an old montserrat.ttf). The font database kept
only the first one loaded, so type set in the other couldn't be matched
when a PDF or .ai was opened: it came in as outlines. Now every version
loads (the first stays the one family and style resolve to), PDF import
tries the other versions when the glyphs differ, and type set in a version
the family and style alone wouldn't pick names it in CharStyle::font_version
(left out when None, so documents without it are unchanged). Layout,
shaping, the substituted-glyph highlight, Find Font, Package and Document
Info follow it; changing the font clears it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
File › Package and Document Info › Font Details looked fonts up with
FontDb::face and compared the face's family with the name the document
stores. A font a document named by another of its names (a PostScript
or localized name, as SVG and PDF files do) was reported as not
available and left out of the package, although the canvas drew it and
exports embedded it.
- Package and Font Details resolve fonts with FontDb::resolve, as
text.fonts and the renderer do. A style the family lacks is still
copied as its closest style; the report now names the style it
copies, and Font Details lists the font as substituted, with that
style's file and permission.
- FontFace::embeddable reads fsType as exports do (Embedding), so fonts
that allow bitmap embedding only (bit 9), or that set the restricted
bit together with reserved bit 0 (0x0003), are no longer copied.
Document Info's permission labels follow the same rule: when a font
sets more than one usage bit, the label names the most permissive
one.
- The Package dialog and its status line name the fonts it didn't copy.
The font scan read only the macOS font folders and the Additional
Fonts Folder, so fonts that apps and font managers register with
CoreText from their own folders were missing from the font menus, and
exports set such type in the fallback font. CoreText's font manager
lists them with the installed fonts.
- `mac_fonts.rs` lists the files of the fonts the font manager has
available (`CTFontManagerCopyAvailableFontURLs`, through
objc2-core-text), keeping only file URLs as `open_documents.rs` does
(#433). The desktop app and `vectorcraft-cli` install it as they
install the DirectWrite lister on Windows, and the scan reads the
files outside the font folders in place.
- The scan leaves out faces with no outlines the font engine draws (no
glyf, CFF, CFF2 or VARC table), on every platform. CoreText lists
Apple's PingFang UI interface font (macOS 27), whose outlines are in
an hvgl table; if the scan cataloged it, PingFang SC would count as
installed, and Chinese characters in fallback text, Japanese kanji
included, would draw blank.
- Tests: the list holds the installed fonts by full path, a font
registered from another folder is listed while it is registered, and
a face without outlines the engine draws isn't cataloged.
References can share data, so a damaged fork could make the scan copy one
large resource up to 256 times: cap the total copied like the fork itself.
Off macOS no file has a fork, so the scan doesn't look at each font file twice.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Replace the Trunk post_build hook packaging/web/copy-fonts.sh with `cargo xtask web-fonts` (xtask/src/web_fonts.rs), so the web build only needs trunk, the wasm32 target and cargo, with no POSIX shell, and works the same on Windows.
The new task reuses crates/text/build/web_fonts.rs for list parsing and URLs, so build.rs and the copier can't disagree on fonts/<sha16>/<file>. It still does nothing without CRAFT_FONTS_DIR, checks each file's SHA-256 against the manifest, and copies the licence next to each face. A listed face that an older craft-fonts checkout lacks is now skipped with a warning, as build.rs does, instead of failing the build. A relative CRAFT_FONTS_DIR is resolved from the workspace root.
Update the references in Trunk.toml, fonts.rs, build.rs, craft_fonts.rs and web-fonts.txt, and document the web build's requirements in docs/development.md. Add tests for the copy, a hash mismatch and the no-op case.
Signed-off-by: Ahmed Moussa <[email protected]>
macOS lists fonts such as the ones Microsoft Office installs in
/Library/Fonts/Microsoft (Bookman Old Style, Impact, Georgia, Tahoma, ...) but
their files have nothing in the data fork and no font extension: the fonts are
`sfnt` resources in the resource fork. The font scan only read data forks of
.ttf/.otf/.ttc/.otc files, so those families were never cataloged and a file
using one (an EPS naming BookmanOldStyle-Bold) showed the font as missing.
The scan now catalogs a file with an empty data fork and a resource fork by
the fonts in that fork, and loading reads them the same way. The fork reader
(crates/text/src/suitcase.rs) follows Apple's published resource file format,
caps what a damaged fork can make it read and never panics. macOS only.
Tests: the fork reader on a synthetic fork built around a bundled font,
including truncated and garbage forks; a macOS scan of a suitcase file.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Desktop builds made with CRAFT_FONTS_DIR now load craft-fonts' 12 Arabic families into the document font database. Noto Sans Arabic comes first and is the fallback for Arabic text. Cairo, Tajawal, Almarai, IBM Plex Sans Arabic, Noto Kufi Arabic, Amiri, Alexandria, Fustat, Reem Kufi, Aref Ruqaa and Gulzar can be picked by name from the font menus.
The web build doesn't embed these fonts because they would make the wasm too large. It fetches them from beside the wasm instead:
- crates/text/web-fonts.txt lists each face as startup or background.
- build.rs turns that list into vectorcraft_text::WEB_FONTS, with each face's URL (fonts/<sha16>/<file>) and SRI hash taken from the craft-fonts manifest.
- packaging/web/copy-fonts.sh, a Trunk post_build hook, copies the font files and their OFL licences there.
- apps/vectorcraft-web/src/fonts.rs fetches Noto Sans Arabic before the app starts, so Arabic text never shows as boxes. It loads the other faces in the background and logs `web font …` for any that fail.
Because fonts can now arrive after startup, the Type tool's layout cache also checks the font database's generation, so text lays out again when new fonts arrive.
_headers and .htaccess serve fonts/* as font/ttf and cache it as immutable. packaging/web/README.md and docs/development.md explain the fonts folder, and ROADMAP.md lists the Arabic typefaces under Type. Also adds Ahmed Moussa to contributors/people.toml.
Signed-off-by: Ahmed Moussa <[email protected]>
With Line-end Punctuation Half Width, an opening bracket that starts a paragraph now drops the
half-em space before it, as one starting a wrapped line already does (#417): its face starts at
the first-line indent, JLREQ 3.1.5's Figure 71 ① (the principle JIS X 4051 adopts), as decided in
#633. Paragraph-start brackets in documents set with this mojikumi move half an em toward the line
start.
Two mojikumi tests used a bracket at a paragraph's start to test something else (consecutive
brackets, the Japanese–Latin space); they now set it inside the line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Paragraph panel › Kinsoku Set (with the East Asian options) and `text.setFormat {kinsoku}`, as
decided in #633:
- Hard: today's set, so existing documents don't change; the default, and not saved.
- Soft: as Hard, except that 々, the prolonged sound mark ー (JLREQ cl-10) and small kana (cl-11)
may start a line: JLREQ's level 3 line-breaking rules (Appendix C.3). Half-width forms aren't in
those classes and stay off the line start.
- None: no kinsoku.
The setting reaches every break the composers consider (greedy, every-line and hyphenation
points). Reset Panel returns to Hard.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
VectorCraft found fonts only in the system's font folders (and, on Windows,
those font services activate). A new preference names one more folder, read
with its subfolders after the platform's: its fonts are listed in the font
menus and used as if installed, without installing them.
- vectorcraft_text::set_user_font_dirs / user_font_dirs: folders
system_font_dirs adds to every scan (none on wasm).
- prefs fontsFolder (Type > Options > Additional Fonts Folder), trimmed; when
it changes after the first scan, the fonts are scanned again at once, so the
folder's fonts appear (or go) and type in them lays out again.
- The label is translated where Additional Plug-ins Folder is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Layout (vectorcraft-text tests_combos): every pair of ligatures under tracking,
the paragraph composer, vertical alignment, Shrink Text to Fit, per-paragraph
attributes and inline graphics, and all six at once. Commands
(vectorcraft-engine tests_textcombos): Auto Size and Shrink Text with inline
art, paragraphs, composer and alignment; editing and undo across them; a save
round trip with everything on.
The fit scaled each run's size, leading and baseline shift but not an inline
graphic's own shift (points), so shrunk symbols sat off the cap-height centre.
align_vertically moved lines and glyphs but not TextLayout::inlines, so inline
symbols in a centred or bottom-aligned area stayed where top alignment put them.
The reflow path for non-rectangular frames also kept the previous pass's
inlines, drawing each symbol twice.