The font database made one style per font file, so a variable font
such as Figtree[wght].ttf listed only its default instance (Light).
Faces now also yield their fvar named instances, each a style of the
family named by its subfamily name (the default instance is the face
itself; unnamed or duplicate instances are left out; axes and
instances read are capped). The system font scan reads the fvar table
beside the name table, so installed instances are listed before the
file is loaded and found by name and by PostScript name.
An instance face carries its axis settings: skrifa draws its outlines
and reads its metrics and advances at that location, harfrust shapes
with a ShaperInstance, so layout, the canvas, Create Outlines and hit
testing all use the instance. PDF export embeds it through krilla's
Font::new_variable, SVG @font-face through subsetter's
subset_with_variations: both write a static font of that instance.
Tests build a small variable font in code (test_fonts, via
write-fonts; no font file is committed) and check listing, resolving,
glyphs and advances, PDF and SVG export and damaged fvar/gvar tables;
Bahnschrift and Segoe UI Variable are checked where installed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In vertical type every character now sits in its own cell down the column:
"10" and "22" in 「10月22日」 stand side by side in one em, centred on the
column, and 月/日 start right after them. Justified (Justify All) columns
no longer split a tate-chu-yoko block, and tracked or justified upright
characters stay on the column's centre line instead of drifting across it.
Root cause: in 0.3.1 (the reporter's build) upright glyphs were placed by
the font's ascent rather than the ideographic em box, so a font with a tall
ascent (Noto Sans CJK JP, 1.16 em) drew each upright character ~0.28 em
down into the next cell, onto the sideways digits; #208 (after 0.3.1)
replaced that with em-box placement and tate-chu-yoko, which this commit
locks in with regression tests. Two defects of the same kind remained:
Justify All added its inter-character gap inside a tate-chu-yoko block
(the block's second digit slid down the column, away from the first), and
upright glyphs turned about the middle of their advance, which tracking and
justification lengthen, instead of the middle of their own cell.
Tests: the issue's layout with bundled glyphs (cells one after the other,
every ink inside its cell and no overlaps, 2-3 digit blocks one em and
centred, single digits and 2026 on their side advancing by their length,
spaces), the issue's string with craft-fonts' Japanese face (skips without
craft-fonts), Justify All with a block, tracking.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Builds on the vertical type from #145 (same data, commands and tools).
- Kinsoku (the strict set, horizontal Japanese too): no line starts with a
closing bracket, 、。, a middle dot, colon, !?, the long vowel mark,
iteration marks or small kana; no line ends with an opening bracket.
Hyphenation breaks obey it too.
- Tate-chu-yoko: a run of two or three half-width digits (10月, 第100回)
stands upright across the column as one block one em long, squeezed to
the em when wider; longer numbers stay on their side.
- Upright or on its side by Unicode's Vertical_Orientation: CJK and
symbols upright, marks that need a vertical form on their side when the
font has none; upright glyphs advance at least one em, centred, and turn
about the ideographic em box centre so they sit on the column's centre
line. Point type keeps its anchor on the first column's centre line.
- `vrt2` is no longer requested: its glyphs are already on their side, so
with the layout turning Latin and digits too they came out upside down
(and tate-chu-yoko blocks broke) in fonts that have it, e.g. Hiragino.
- The line→text mapping is one affine (`TextLayout::line_xf`, an exact
quarter turn plus the point-type offset); `logical_point` and
`physical_point` use it. Pixel snapping skips vertical type.
- Tests: kinsoku in both directions, tate-chu-yoko blocks, upright vs
sideways, anchor on the centre line, arrows along columns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File > Document Setup is a tabbed dialog over one engine command,
document.setup (no params: a query; any keys: one undo step;
document.setUnits stays as the units alias).
- General: units, bleed (four sides with a link toggle), Edit Artboards,
Show Images in Outline Mode (greyscale pixels under the frame),
Highlight Substituted Fonts/Glyphs (pink boxes on screen only).
- Transparency: grid size and colour presets or custom colours, Simulate
Colored Paper, the flattener preset (built-in or saved) and Discard
White Overprint (Overprint Preview keeps white overprints visible).
- Type: language with its quotes, Use Typographer's Quotes for the Type
tool and Smart Punctuation, superscript/subscript size and position and
small caps size (Character panel position/smallCaps; changing the
setup restyles the text in the same undo step), text export (SVG text
as text or outlines by default).
- The canvas draws a red bleed outline and the grid in the document's
colours and cell size as one textured quad, also in rotated views; a
white Background Contents exports raster formats opaque.
Model: Document.setup (DocSetup, defaulted, skipped when default) and
CharStyle position/small_caps; old files load unchanged.
tests_transparencygrid's helper now finds the grid by its texture (the
grid is one textured mesh instead of per-cell rectangles).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shipped code no longer unwraps, expects or panics: every one of the 124
unwrap()/expect()/panic!/unreachable! sites outside tests now handles the
failure (Result/Option, ?, let-else, defaults) and reports errors to the user.
- Lints: workspace clippy denies unwrap_used, expect_used, panic, todo,
unimplemented and unreachable; clippy.toml allows them in tests, and
integration tests, examples and testkit opt out explicitly.
- Safety net (vectorcraft_engine::guard): a panic in a top-level command rolls
the document back and returns EngineError::Internal; a panicking tool is
reset; the MCP server answers with an internal error and keeps serving; the
UI frame and each control request are guarded.
- APIs that hid failures now return them: Rendered::to_png/to_jpeg/to_webp
return Result (JPEG/WebP errors used to write empty files),
FontDb::face returns Option, Region::top returns Option.
- Fuzzing: engine/tests/import_fuzz.rs feeds garbage, hostile and mutated SVG
and PDF through import → render → export (30k cases locally, no panics).
- Docs: AGENTS.md/CLAUDE.md rule, docs/development.md "Robustness" section,
README pointer and a PR template checklist.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>