The line's em box top is measured at the unshifted baseline; the shift
moves only the drawn glyphs. CJK character alignment still counts. The
Ascent, Cap Height and x Height offsets keep counting a raised baseline.
8806a223 (next, for #392) drew a slanted glyph with run while a
right-to-left line span was open, so its tagged span would not nest in
the line span. pdf-rtl-text (8f30ba1c) removes the line span and its
line_span flag, so the glyph always goes through tagged_run. The
regression test keeps its export of tagged and untagged PDFs with shatai
Hebrew and checks what the branch's tests check: reading-order text,
no marked content opened inside an ActualText span, and no ActualText
wider than a word.
Conflict: ROADMAP.md: status line lists both changes (the PR's review date); progress log both lines, the PR's first
Conflict: crates/ui-egui/src/i18n/ja.rs (auto-merged, edited): the PR's ("All", "すべて") row dropped, next already has it
Conflict: crates/doc/src/build.rs: next's parent_page_for (it calls Spread::parent_page_for_side); the PR's test kept
Conflict: crates/tools/src/snap.rs: shown_parent_page_index no longer exists (snap calls Spread::parent_page_for_side); the PR's test kept, calling parent_page_for_side on a two-page and a one-page parent
Conflict: crates/doc/src/page.rs (auto-merged, edited): parent_page_for_side picks the parent's first page for PageSide::Single, the PR's rule moved to where next makes the choice
Conflict: crates/doc/src/edit.rs: next's thread_frames (append_story); every rev += 1 is rev.wrapping_add(1)
Conflict: crates/engine/src/cmd/fonts.rs: next's replace (the local-formatting pass moved out of this hunk)
Conflict: crates/engine/src/cmd/text.rs: next's format_chars (apply_char_attrs, typing format); rev += 1 is rev.wrapping_add(1)
Conflict: Cargo.lock: next's (the PR's dependency is already on next)
Conflict: ROADMAP.md: next's (the PR's earlier head is already merged)
Conflict: docs/gaps.md: next's (the PR's earlier head is already merged)
Conflict: docs/typography-parity.md: next's, test count 170 -> 172 as in d0120bf6
Conflict: crates/compose/src/optical.rs: next's plus d0120bf6's kernable() dash and fullwidth exclusion and its unit test
Conflict: crates/compose/src/tests.rs: next's plus d0120bf6's optical_kerning_leaves_dash_pairs_alone
Changes from an independent review pass over the Czech catalog:
- meaning: "Fit Text" resizes the frame to the text, not the text; the nested
style "New Line Style" is a style for lines of text, not for strokes;
"Liquid Settings", "Nonbreaking Space/Hyphen" and "Discretionary Hyphen" use
the common Czech terms
- consistency: a story is "text" and an article is "clanek" (they were both
"clanek"); "Tags" matches "Novy tag"/"Odebrat tag"; "Float Panel" matches
"Float panel"; Image = "Obrazek" like the other image labels; Remove =
"Odebrat"; one name for the Utilities menu that no longer clashes with Tools
- grammar and shape: command-like labels (Essentials, Wavy, Metrics, Optical,
Both, Restart Numbering, Output All Blacks...) and a stray "(" fixed
Strings that need a code change to be right in every place (digit set names
that share keys with languages, nested-style units after a number, the
Crop/Trim/Media combo) are left as they are.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Every Czech row is looked up through the cached tr(), and an unknown
label passes through unchanged.
- No duplicate or empty rows; placeholders, trailing ellipses and colons
and padded captions match the English key. Only stale rows fail: keys
that no catalog, command label, menu name or UI command knows any
more. A new English string falls back to English, as in #294.
- count_label("cs", ..) gives the one / few / other form for 0, 1, 2,
4, 5, 11 and 22, and the Group selection title uses its context row.
- Czech is selectable from the menu, persisted in UiState and leaves
document data untouched; it joins the language switch geometry test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Register "Čeština" (cs) in LANGUAGES, the Interface Language menu and
the app.language parameter doc, and look it up in tr() like the other
single-language tables. tr_context() uses the Czech CONTEXT rows.
count_label() picks the Czech plural form for the number (1 chyba,
3 chyby, 5 chyb; 1 stránka, 3 stránky, 5 stránek), so the info panel,
the Preflight panel and the status bar read grammatically for every
count. Nouns without plural forms keep the existing "{count} {noun}"
caption; other languages are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add crates/ui-egui/src/i18n/cs.rs in the shape of ar.rs, it.rs, ja.rs and
uk.rs. It has a row for every key of the shared table and of the Arabic,
Brazilian Portuguese, Italian, Japanese and Ukrainian tables (1,697
strings), plus 25 labels the code passes to tr() that no catalog had yet
(data merge and composite font commands, panel docking, the unsaved
changes dialog, W:/H: in the control bar).
Translated from the meaning of the English strings with ordinary Czech
desktop-publishing vocabulary; no other product's localization was used.
Phrases that the MIT OR Apache-2.0 Czech catalogs of VectorCraft and
PhotoCraft already use are kept identical for consistency across
ArtCraft and credited in ASSETS.md.
The file also holds PLURALS (one / few / other forms for the count
captions) and CONTEXT (the Properties panel title "Group" is a noun,
the menu command is a verb). The next commit wires them in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wheel turned over a document view with Alt (Option) held scrolled
the view like the plain wheel. It now zooms at the pointer, exactly as
Ctrl/Cmd + wheel does. As with that zoom, a wheel motion keeps its key
to its end: letting Alt go while a trackpad swipe coasts on does not
turn the rest into scrolling.
Two dashes end to end have no ink gap to measure, so -- and the double
em dash opened up by the loosen cap; Chinese sets —— as one continuous
rule. Fullwidth Latin (U+FF21–FF5A) was kernable through its Latin
script and moved CJK text off its em grid. Both are now excluded from
kernable(), with a unit test and a compose test that failed before.
Text is composed in the box around a frame's path in the frame's own
coordinates. A sheared then rotated frame turns that box past its path's
bounds, which was all a move repainted, so its text left trails behind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Optical kerning was treated exactly like Metrics: the font's kern pairs
and nothing else, so text set with Optical in InDesign came out wider,
rewrapped and went overset in tightly fitted frames.
Now the font's pairs are turned off for Optical text and each pair is
spaced from its outlines (crates/compose/src/optical.rs): every glyph's
ink is sampled into 28 bands across the em, the pair's mean and smallest
ink gap over the bands both glyphs ink are compared with the font's
stem-to-stem gap (the pair nn, or HH then oo), and the pair moves 60% of
the way towards it, keeping 30% of that gap and moving at most -0.12 or
+0.06 em. Latin, Greek and Cyrillic letters, digits and Western
punctuation take part; spaces, marks, missing glyphs, right-to-left
runs, upright and tate-chu-yoko glyphs and pairs across a fallback font
keep their font spacing. Profiles and references are cached, bounded.
Tests: AV, To and r. close up and nn stays; HH closes up under Optical
but not Metrics (fails on main); every change on a pangram stays within
the bounds and spaces keep their width; profile, gap, adjustment and
character-class unit tests.
Docs: typography parity Kerning row and remaining effort, gap 4,
ROADMAP milestone 6 and progress log.
Fixes#181.
Command descriptions left the new-text hyphenation default and image fitting outcomes implicit. State the default and each fitting mode's semantics, and add an MCP example that disables hyphenation. No behavior changes.
Route visible labels through the existing translation and RTL helpers.
Keep document commands, stored options and user-defined style names intact.
Update the localization status and roadmap progress log.
atomic-say brings upstream's shared panel docking (#409), which sends
float_panel and dock_panel through the docking registry; next's Tabs panel
is float-only and not registered there, so Type > Tabs failed with "panel
must name a registered panel". Float-only panels keep the legacy floating
list: they float and close without the registry, a customized layout
keeps them in that list, and dock::floating still draws them then.
poppler positions an ActualText with the transform in force where its
span ends; a line span closes outside the runs' transforms, so pdftotext
placed the line text off the page and dropped it. An ActualText per run
fares no better: poppler reverses its characters in a right-to-left
line. Without them, poppler orders the glyphs of right-to-left lines
itself and extracts whole lines in reading order, and the content stream
keeps listing them in reading order. With no line span left, glyphs in
it no longer need to avoid krilla's cluster spans, so that workaround
goes too. The tests that read the line span's ActualText now check that
ActualText never spans more than a cluster.
Conflict: crates/compose/src/tests.rs: next's baseline-fit and keep tests, then the branch's OS/2 offset doc and code-page constant
Conflict: crates/fonts/src/tests_vertical.rs: testing imports from both (with_base, with_code_pages)
tool-cursors moves the cursor-to-system-icon table into cursors.rs; next's
text threading adds Cursor::ThreadLink and Cursor::Unthread. They keep the
icons next gave them (alias, no-drop) under the loaded-text badge.
The tool-cursors merge (ba6fcbe2) appended the branch's pen cursor tests
after next's live_corner_diamonds_follow_a_rotated_frame without that
test's closing brace.
Conflict: crates/tools/src/tests.rs: both sides' tests (thread ports, pen add/delete)
Conflict: crates/ui-egui/src/canvas.rs: the branch's cursors::show for the icon, then next's loaded-text/thread badge; next's cursor_icon dropped for cursors::system
Conflict: crates/ui-egui/src/lib.rs: DesignApp fields from both sides
Conflict: ROADMAP.md: both progress-log entries, next's newer ones first
Conflict: apps/designcraft/src/gpu.rs: next's side; the branch's test fix targets the startup record tests next replaced
Conflict: apps/designcraft/src/main.rs: next's gpu startup and macOS IME helper; config_dir() dropped for the branch's profile::config_dir(), recovery_dir from profile::recovery_dir()
Conflict: crates/ui-egui/src/canvas.rs: next's guard (menu_open, keyboard_pane already limits keys to the focused pane)
Conflict: crates/ui-egui/src/lib.rs: modules from both sides
Finding a placed PDF's Bounding Box crop rendered its page at 2048
pixels and took the bounds of the painted pixels. PDF export measures
every such crop that import left unmeasured, so exporting a 17-page
document that places one complex illustration took about 4 s longer
than before crops were supported. The box is now the union of the
page's painted paths (strokes included), glyphs and images, each cut
to its clip, collected by interpreting the page without rasterizing
it: the same box, exact instead of to the nearest pixel, in
milliseconds.
Test: content_crop_box_is_measured_without_rendering_the_page.
krilla wraps a mark cluster that maps to several glyphs, and a glyph drawn
with other text than its font already maps it to, in an ActualText span.
Inside a line's ActualText span that nested the spans, and poppler drops
the text before a nested span. Inside a line span each glyph now gets the
text its font maps it to (the exporter keeps a copy of krilla's mappings),
a cluster's text goes to its first glyph, and a run splits into separate
draw calls where two glyphs in a row would share an empty range. A run
drawn through tagged_run inside a line span takes the span as its tag.
Test: mark_clusters_in_right_to_left_lines_open_no_span_of_their_own
(5 nested spans before).
A font's em box is its BASE em box; else, for a CJK font (its font
menu group isn't Western: meta or name languages, CJK code pages, else
kana, Hangul or ideograph coverage), the OS/2 typo ascender when the
typo ascender and descender span exactly one em; else 0.5 em + cap
height / 2; else 0.88 em. Latin fonts with a one-em typo box, such as
DejaVu Serif, centre on the cap height.
Tool cursors are glyphs drawn in code (black ink, white halo): the filled
arrow for Selection, the hollow arrow for Direct Selection, the pen nib
with an add, delete, close or convert badge, and a curved double arrow
for rotating. Linux and Windows show them as OS cursor bitmaps,
rasterized per pixels-per-point and cached; macOS paints them at the
pointer, since it sizes cursor bitmaps in points; the web build keeps
the system cursors. Text, hand, zoom and resize cursors stay system
cursors.
The Pen tool reports what a click does: over the first anchor of the
path being drawn it closes the path, and between paths a click on a
selected path's anchor deletes it and a click on its outline adds one.
The Add, Delete and Convert Direction Point tools show the matching pen
badges.
The glyph geometry and the rasterizer are adapted from VectorCraft's
code-drawn tool cursors (same organisation, original work).
Fixes#438
Documents, books, libraries, snippets, swatches and every export are
written to a temporary file beside the target, flushed, renamed over it
and the folder flushed, so a crash, kill or full disk mid-write leaves
the old file intact. Replaced files keep their permissions, symbolic
links keep pointing at their file, and Windows retries a rename that a
scanner briefly blocks.
Fixes#436