diff --git a/apps/vectorcraft-cli/src/main.rs b/apps/vectorcraft-cli/src/main.rs
index 906df8522..d3d94870a 100644
--- a/apps/vectorcraft-cli/src/main.rs
+++ b/apps/vectorcraft-cli/src/main.rs
@@ -96,7 +96,7 @@ USAGE:
SVG's data-* attributes; --outline-text writes SVG text as paths, --decimals sets SVG's coordinate
precision (1–7, default 3). OUT may not be IN when reading
IN left things out (hidden text, art or layers it could not read; the notes say which) unless
- --replace-lossy is given. --editing-data opens a .ai or Illustrator EPS from its editing copy
+ --replace-lossy is given. --editing-data opens a .ai or an EPS with editing data from its editing copy
even where that draws differently from its PDF part or page (which can be older), as long as
it can be read. The
result has the export's warnings, and importWarnings for what reading IN left out.
diff --git a/crates/engine/src/cmd/place/docx.rs b/crates/engine/src/cmd/place/docx.rs
index 400f4d168..2830fd437 100644
--- a/crates/engine/src/cmd/place/docx.rs
+++ b/crates/engine/src/cmd/place/docx.rs
@@ -19,7 +19,9 @@
//! the color.
//! - Paragraph formatting: alignment (`w:jc`), start and end indents (`w:start` or `w:left`,
//! `w:end` or `w:right`: the right and left indents of a right-to-left paragraph), first-line and
-//! hanging indents, space before and after, and direction (`w:bidi`).
+//! hanging indents, space before and after, and direction (`w:bidi`). A paragraph justified with
+//! a low, medium or high kashida length (`lowKashida`, `mediumKashida`, `highKashida`) is
+//! justified with Short, Medium or Long kashidas; `both` is justified without kashidas.
//! - Styles (§17.7.2): the document defaults, then the paragraph style (else the default paragraph
//! style) and the styles it is based on, then the run's character style (else the default
//! character style) and the styles it is based on, then direct formatting. Bold, italic, caps,
@@ -37,6 +39,7 @@
use std::collections::HashMap;
use roxmltree::Node;
+use vectorcraft_doc::text::Kashida;
use vectorcraft_doc::{CharPosition, CharStyle, Justify, ParaDirection, ParaStyle, ScriptMetrics, TextRun};
use super::text::{self, Story};
@@ -361,8 +364,9 @@ enum Align {
Left,
Center,
Right,
- /// Justified, the last line at the start (the right of a right-to-left paragraph).
- Both,
+ /// Justified, the last line at the start (the right of a right-to-left paragraph), with the
+ /// kashida length of `lowKashida`, `mediumKashida` and `highKashida` (§17.18.44).
+ Both(Kashida),
/// Justified, the last line too.
Distribute,
Start,
@@ -860,8 +864,8 @@ impl Reader {
Align::Left => Justify::Left,
Align::Center => Justify::Center,
Align::Right => Justify::Right,
- Align::Both if rtl => Justify::JustifyRight,
- Align::Both => Justify::JustifyLeft,
+ Align::Both(_) if rtl => Justify::JustifyRight,
+ Align::Both(_) => Justify::JustifyLeft,
Align::Distribute => Justify::JustifyAll,
Align::Start => Justify::Auto,
Align::End if rtl => Justify::Left,
@@ -873,6 +877,7 @@ impl Reader {
space_before: points(p.before),
space_after: points(p.after),
direction: p.rtl.map(|rtl| if rtl { ParaDirection::RightToLeft } else { ParaDirection::LeftToRight }),
+ kashida: if let Align::Both(k) = p.align { k } else { Kashida::None },
..ParaStyle::default()
}
})
@@ -898,7 +903,10 @@ fn para_props(ppr: Node) -> ParaProps {
Some("left") => Some(Align::Left),
Some("center") => Some(Align::Center),
Some("right") => Some(Align::Right),
- Some("both" | "lowKashida" | "mediumKashida" | "highKashida") => Some(Align::Both),
+ Some("both") => Some(Align::Both(Kashida::None)),
+ Some("lowKashida") => Some(Align::Both(Kashida::Short)),
+ Some("mediumKashida") => Some(Align::Both(Kashida::Medium)),
+ Some("highKashida") => Some(Align::Both(Kashida::Long)),
Some("distribute" | "thaiDistribute") => Some(Align::Distribute),
Some("start") => Some(Align::Start),
Some("end") => Some(Align::End),
diff --git a/crates/engine/src/tests_textimport.rs b/crates/engine/src/tests_textimport.rs
index 25c3a485b..202e6ffdb 100644
--- a/crates/engine/src/tests_textimport.rs
+++ b/crates/engine/src/tests_textimport.rs
@@ -366,6 +366,47 @@ fn docx_tables_breaks_and_fields_place_as_paragraphs_and_tabs() {
);
}
+#[test]
+fn docx_kashida_justification_places_with_insert_kashidas() {
+ use vectorcraft_doc::Justify;
+ use vectorcraft_doc::text::Kashida;
+ // The document holds right-to-left paragraphs justified with each kashida length and with `both`, a paragraph style
+ // that sets `mediumKashida`, that style under direct formatting, and a left-to-right paragraph.
+ let styles = r#""#;
+ let para = |ppr: &str| format!(r#"{ppr}كتاب جميل"#);
+ let body: String = [
+ r#""#,
+ r#""#,
+ r#""#,
+ r#""#,
+ r#""#,
+ r#""#,
+ r#""#,
+ r#""#,
+ ]
+ .map(para)
+ .concat();
+ let mut s = session();
+ let r = s.execute("file.place", &json!({"name": "kashida.docx", "dataBase64": vectorcraft_format::base64_encode(&docx(&body, styles))})).unwrap();
+ let n = s.doc().unwrap().doc.node(NodeId(r["ids"][0].as_u64().unwrap())).unwrap().clone();
+ let NodeKind::Text(t) = &n.kind else { panic!("not type") };
+ let paras: Vec<_> = t.paragraph_styles().iter().map(|p| (p.justify, p.kashida)).collect();
+ assert_eq!(
+ paras,
+ [
+ (Justify::JustifyRight, Kashida::Short),
+ (Justify::JustifyRight, Kashida::Medium),
+ (Justify::JustifyRight, Kashida::Long),
+ (Justify::JustifyRight, Kashida::None),
+ (Justify::JustifyRight, Kashida::Medium),
+ (Justify::JustifyRight, Kashida::None),
+ (Justify::JustifyLeft, Kashida::Long),
+ (Justify::Center, Kashida::None),
+ ],
+ "low, medium and high kashida lengths are Short, Medium and Long; other alignments have none"
+ );
+}
+
#[test]
fn malformed_docx_fails_cleanly() {
let mut s = session();
diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs
index 0063ef5db..bf592506a 100644
--- a/crates/text/src/tests.rs
+++ b/crates/text/src/tests.rs
@@ -92,7 +92,7 @@ fn fractional_widths_off_rounds_positive_advances_to_whole_points() {
tcy.vertical = true;
tcy.fractional_widths = false;
let adv: Vec = layout(db(), &tcy).glyphs.iter().map(|g| g.advance).collect();
- assert!(adv.iter().any(|a| *a == 0.0), "a tate-chu-yoko continuation stays at zero: {adv:?}");
+ assert!(adv.contains(&0.0), "a tate-chu-yoko continuation stays at zero: {adv:?}");
assert!(adv.iter().all(|a| *a == 0.0 || (a - a.round()).abs() < 1e-9), "{adv:?}");
}
diff --git a/crates/ui-egui/src/prefs_dialog.rs b/crates/ui-egui/src/prefs_dialog.rs
index da73fc832..7d48e8994 100644
--- a/crates/ui-egui/src/prefs_dialog.rs
+++ b/crates/ui-egui/src/prefs_dialog.rs
@@ -349,7 +349,8 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) {
labeled(ui, sp.label, |ui| {
let cur = v.as_str().unwrap_or("auto");
let codes: Vec<&str> = std::iter::once("auto").chain(crate::i18n::Lang::for_picker().into_iter().map(|l| l.code())).collect();
- let labels: Vec<&str> = std::iter::once(tl!("Auto")).chain(crate::i18n::Lang::for_picker().into_iter().map(|l| l.name())).collect();
+ let labels: Vec<&str> =
+ std::iter::once(tl!("Auto")).chain(crate::i18n::Lang::for_picker().into_iter().map(|l| l.name())).collect();
let cur_label = codes.iter().position(|c| c.eq_ignore_ascii_case(cur)).and_then(|i| labels.get(i).copied()).unwrap_or(cur);
if let Some(i) = widgets::dropdown(ui, sp.key, cur_label, &labels, 260.0)
&& let Some(code) = codes.get(i)
diff --git a/docs/localization-parity.md b/docs/localization-parity.md
index 394145ac6..47457e71a 100644
--- a/docs/localization-parity.md
+++ b/docs/localization-parity.md
@@ -30,10 +30,8 @@ Chinese by `imachus`). Only Georgian is recorded as reviewed by a native speaker
| Hindi | `hi` | 0 | none | Devanagari: the document's text shapes (HarfRust), with tests of conjuncts, reph, pre-base matras, nukta forms and caret stops that run when an OpenType Devanagari font is installed, but the egui interface does no complex shaping | no | none | 20–30 |
| Arabic | `ar` | 0 | none | document text: bidi layout and Paragraph Direction; interface: no right-to-left layout, no Arabic shaping in egui | no | none | 25–40 |
| French | `fr` | 4,020 / 4,020 (100%) | all | Latin | no | full | — (review) |
-| Portuguese | `pt-br` | 4,707 catalog entries: menus, `tl!` literals and messages complete (tests); one|other plurals | all | Latin | contributor is a Brazilian Portuguese speaker; further native review welcome | full | — (review) |
-| Indonesian | `id` | 5,279 rows, 750 of them `@msg` (counted 2026-10-21): menus and `tl!` literals complete (tests); the tests do not hold its messages complete; one plural form | dialogs and tooltips all; 750 message rows (French has 796) | Latin | no | full | — (review) |
| Portuguese | `pt-br` | 5,194 catalog entries (counted 2026-10-21): menus, `tl!` literals and messages complete (tests); one/other plurals | all | Latin | contributor is a Brazilian Portuguese speaker; further native review welcome | full | — (review) |
-| Indonesian | `id` | 0 | none | Latin | no | none | 5–8 |
+| Indonesian | `id` | 5,279 rows, 750 of them `@msg` (counted 2026-10-21): menus and `tl!` literals complete (tests); the tests do not hold its messages complete; one plural form | dialogs and tooltips all; 750 message rows (French has 796) | Latin | no | full | — (review) |
| Japanese | `ja` | 4,004 / 4,020 (99.6%) | all | CJK UI font as above; Japanese IME; vertical type partial; Japanese crop marks and composition settings | no | full | — (review) |
| German | `de` | 3,952 / 4,020 (98%; tests hold menus, literals and messages complete) | all (602 of 604 message rows; landed 2026-10-10) | Latin | contributor-written, not reviewed | full | — (review) |
| Korean | `ko` | 4,870 rows (counted 2026-10-21): menus, `tl!` literals and messages complete (tests); one plural form | all | Hangul from an installed system font on the desktop (Apple SD Gothic Neo, Malgun Gothic, Noto Sans CJK KR…); the web build has no Korean UI font yet (craft-fonts); IME path exists | no (AI-drafted) | full | — (review; web font) |
diff --git a/docs/mcp.md b/docs/mcp.md
index 8996f8a89..2dae918fd 100644
--- a/docs/mcp.md
+++ b/docs/mcp.md
@@ -2979,7 +2979,10 @@ left out. `file.place` answers `format: "rtf"`.
A Word document (a ZIP package named `.docx`, or one that holds `word/document.xml`, except a `.txt` file) is placed
the same way and keeps the same formatting, taken from the document defaults, the paragraph style and the character
-style (and the styles each is based on) and direct formatting; theme fonts are read from the theme part. Table cells
+style (and the styles each is based on) and direct formatting; theme fonts are read from the theme part. A paragraph
+justified with a low, medium or high kashida length (`w:jc` `lowKashida`, `mediumKashida` or `highKashida`) is
+justified with `kashida` `"short"`, `"medium"` or `"long"` (see [Insert Kashidas](#insert-kashidas)), and one
+justified with `both` has none. Table cells
are separated by tabs and each row is a paragraph. Hidden and deleted text, field instructions, pictures, text boxes,
headers, footers, notes and list numbers are left out. A package with more than 10,000 files, a part that unpacks to
more than 16 MiB or nests elements more than 256 deep, and a document with more than 4 MiB of text or 100,000
diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md
index 8a5d344a8..d7ec04ff7 100644
--- a/docs/target-app-parity.md
+++ b/docs/target-app-parity.md
@@ -80,7 +80,6 @@ dominate; 3D, graphs and automation are specialist). They sum to 105. Feature de
| 3D and Materials (and Turntable) | 4 | ~25% | live Revolve with automatic smooth vector shading and default-on continuous circular caps, Extrude & Bevel (twist, taper, caps, five bevel shapes), Inflate and Rotate with Position presets (colored Inflate uses smooth membrane lighting; white-light Inflate retains its existing shading; Extrude & Bevel and Rotate retain planar shading; one to ten colored lights, shared neutral ambient, painter visibility); materials, light presets, shadows, ray tracing, 3D Classic, Turntable views, 3D export | 30–55 | estimated (Extrude & Bevel, Inflate and Rotate added 2026-10-12) |
| Type core | 9 | 80% | Auto-leading style resets are implemented; remaining: hyphenation and justification options, Optical Margin Alignment, Middle Eastern features beyond bidi layout, optional group font-size preservation; transformed character controls fixed (#1034), see [type-parity.md](type-parity.md) | 14–19 | estimated; full-name font matches regression-tested (#1258); mixed paragraph alignment indication implemented; headless regressions, full integration CI and visual review pass; localized legacy family-name fallback with loaded/catalog regressions (#2208); Fractional Widths per type object regression-tested; scores and hours unchanged |
| Type advanced (CJK, spelling, Touch Type, Retype) | 4 | 50% | CJK vertical composition (ruby, mojikumi), variable font axes, tab leaders, spell check, Touch Type, Retype | 20–28 | estimated; No Break keeps marked characters on one line; upright fallback for fullwidth `!` / `?` regression-tested; score and hours unchanged |
-| Type core | 9 | 80% | Auto-leading style resets are implemented; remaining: hyphenation and justification options, Optical Margin Alignment, Middle Eastern features beyond bidi layout, optional group font-size preservation; transformed character controls fixed (#1034), see [type-parity.md](type-parity.md) | 14–19 | estimated; full-name font matches regression-tested (#1258); mixed paragraph alignment indication implemented; headless regressions, full integration CI and visual review pass; localized legacy family-name fallback with loaded/catalog regressions (#2208) |
| Symbols, blends, envelopes, Repeat, perspective | 5 | 87% | 30.7's Blend panel with ease in/out, dynamic symbols, symbol libraries, Start Global Edit, envelope Anti-Alias/Preserve Shape; Symbol Options › Registration, Export Type (Graphic / Movie Clip) and 9-slice guide settings (stored, not drawn) are done | 5–9 | estimated; interactive selected mirror axis implemented; bounding-box handles take priority over the axis line (focused regression and native checks passed; full CI blocked by an unrelated text test); #2544 spine Anchor Point conversion and picking regression-tested; scores and hours unchanged |
| Image Trace, graphs, image tools | 3 | 75% | graph Column designs, Image Trace Create Strokes depth (Create Object Mosaic is done; junction-returning piece survival has source-generated regressions in `image-trace-quality.md`; simpler contour walk, identical output; an edge's sub-pixel shift is the same from either side; notches read only their own piece; collapsed loops keep few segments, so Paths is monotone on the code-drawn sweep) | 5–8 | estimated (raised from 72%); quick presets reveal adjustment controls, Properties reopens them; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views (scores/hours unchanged) |
| Symbols, blends, envelopes, Repeat, perspective | 5 | 87% | 30.7's Blend panel with ease in/out, dynamic symbols, symbol libraries, Start Global Edit, envelope Anti-Alias/Preserve Shape; Symbol Options › Registration and Export Type (Graphic / Movie Clip) are done | 5–9 | estimated |
@@ -342,7 +341,6 @@ about 1–1.5 agent-hours each, so the other rows (estimated on the older scale)
| 3D and Materials | 4 | ~25% | Initial Revolve, Extrude & Bevel, Inflate and Rotate, with simple lighting; materials, light presets, shadows, ray tracing, 3D Classic and more complete visibility remain | 25–45 |
| Type core | 9 | 80% | Middle Eastern features beyond bidi layout and Paragraph Direction (Character Direction, kashidas, the Middle Eastern composers, editable bidirectional SVG text, a split caret where directions meet); Hyphenation… and Justification… options (Every-line Composer for justified and ragged text, per object, is done; it is tuned by eye, not yet compared side by side with Illustrator; mixed type sizes in a paragraph still break single-line in non-rectangular area type and with text wrap), Optical Margin Alignment, paragraph spacing limit for vertically justified area type, hidden characters, paragraph attributes in the Type tool's copy and paste (the characters keep their styles, the paragraphs take the destination's), mixed-value display in the Paragraph panel for a selection across differing paragraphs, partial selection of type-area anchors by marquee, optional font-size preservation during group scaling (scaled character controls fixed in #1034). Fractional Widths is done; System Layout remains a panel stub | 14–19 |
| Type advanced | 4 | 50% | CJK composition for vertical type (ruby, mojikumi beyond Line-end Punctuation Half Width — the other sets (and their paragraph-start bracket and indent rules), custom kinsoku sets (beyond Hard, Soft and None) and the Mojikumi Settings dialog —, manual tate-chu-yoko; No Break is done), variable font axis sliders (named instances are styles), tab leaders, spell check (open dictionary), Touch Type, Retype; vertical point/area/path type and Type Orientation have initial support (beyond parity, not scored: inline symbols in text) | 20–28 |
-| Type core | 9 | 80% | Middle Eastern features beyond bidi layout and Paragraph Direction (Character Direction, kashidas, the Middle Eastern composers, editable bidirectional SVG text, a split caret where directions meet); Hyphenation… and Justification… options (Every-line Composer for justified and ragged text, per object, is done; it is tuned by eye, not yet compared side by side with Illustrator; mixed type sizes in a paragraph still break single-line in non-rectangular area type and with text wrap), Optical Margin Alignment, paragraph spacing limit for vertically justified area type, hidden characters, paragraph attributes in the Type tool's copy and paste (the characters keep their styles, the paragraphs take the destination's), mixed-value display in the Paragraph panel for a selection across differing paragraphs, partial selection of type-area anchors by marquee, optional font-size preservation during group scaling (scaled character controls fixed in #1034) | 14–19 |
| Symbols, blends, envelopes, Repeat, perspective | 5 | 90% | symbol libraries (original), dynamic symbols, envelope Anti-Alias/Preserve Shape output and warp point editing, brush interpolation in blends (M8 fidelity pass done); Symbol Options › Registration and Export Type done (9-slice guides remain) | 4–7 |
| Image Trace, graphs, image tools | 3 | 72% | graph Column designs (Graph Design, Marker designs and series colours set with Group Selection are done), Create Object Mosaic, per-feature stroke/fill choice and varying widths in Image Trace's Create Strokes (Vector Halftone and the Image Trace View modes, Palette and Create Strokes are done) | 6–10 |
| Layers, artboards, document setup | 5 | 82% | Layers panel: a confirmation before deleting layers that hold art, Collect for Export, row context menus, the Layer Options dialog for several rows with mixed values, Paste Remembers Layers for sublayers, drag-scrolling a long panel while dragging rows; artboard preset polish (artboard escape routing and document setup modal handoff done; Move Artwork for artboard X/Y edits, Rearrange All Artboards, Document Setup, New Document and the rest of the Layers panel are done) | 4–7 |
@@ -811,6 +809,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- Place and the Links panel, Package (placed `.vectorcraft` documents packaged with their own linked files and fonts, relinked to the copies: #777), File Info, Print with print presets, PostScript output and print tiling, slices and Save for Web, Export for Screens and Asset Export, CSS Properties, PNG/PDF/SVG/text clipboard flavours; files copied in a file manager paste as art (SVG, PDF, EMF/WMF, bitmaps) on every desktop platform, and Linux/Wayland reads the compositor's clipboard (#398); on Linux the Paste menu checks the clipboard off the UI thread, so an X11 owner that never answers can't freeze the window (#510); screenshots and other copied pictures paste as embedded images at the view centre everywhere, the web included (#457).
- File › Place reads RTF files as area type with their formatting: each run's font, size, bold and italic, underline, strikethrough, caps, superscript or subscript, baseline shift and color, and each paragraph's alignment, indents, space before and after and direction. Table cells are separated by tabs; hidden text, pictures, headers, footers and notes are left out (`file.place`, `crates/engine/src/tests_textimport.rs`).
- File › Place reads Word `.docx` documents as area type with the same formatting, taken from the document defaults, the paragraph and character styles (their toggle properties reversing each other) and direct formatting, the theme's fonts included. Table rows are paragraphs with their cells separated by tabs; hidden and deleted text, field instructions, pictures, headers, footers and notes are left out (`file.place`, `crates/engine/src/tests_textimport.rs`).
+ - A Word paragraph justified with a low, medium or high kashida length places justified with Insert Kashidas set to Short, Medium or Long (`file.place`, `crates/engine/src/tests_textimport.rs`).
- On the desktop, Copy and Cut make the system clipboard's formats (text, SVG, PDF, PNG) from a copy of the clipboard on the thread that writes background saves and exports, and write them when they are ready; while that thread has a save, an export, a print or a recovery copy to finish, they are made and written at once. Until they are written, Paste in VectorCraft takes the internal clipboard, and while a text field or the Type tool has the keyboard the app waits for them. Another Copy or Cut, or a copy of text in VectorCraft, drops them.
- Missing linked files can be looked for in a folder and its subfolders: Find in Folder… in the Linked File Not Found dialog and in the Links panel menu lists the files with each one's name, those with the size and contents saved with the link first, and relinks the files chosen, copying an image's file into the document's folder when asked, without replacing a file there (`links.findFiles`, `links.relink {copy}`). In the Links panel it looks for the selected linked files when any are selected.
- On Windows, a document opened from a mapped network drive finds its links stored with the share's own path (`\\server\share\…`), as the same document opened from the share does; links on other computers stay missing.
diff --git a/docs/type-parity.md b/docs/type-parity.md
index 4dedca9f2..1fb1e3da4 100644
--- a/docs/type-parity.md
+++ b/docs/type-parity.md
@@ -35,7 +35,6 @@ against Illustrator's public documentation; **estimated** unless noted.
| Fallback glyphs keep the text's weight and slant | done | #2491: a character from a fallback face is set in that family's face nearest the run's weight and slant; Bold Italic of a missing family is drawn Bold (Source Sans 3 has no Bold Italic) rather than Italic |
| Colour fonts (OpenType SVG / COLR) | partial | |
| Every-line and Single-line composers | done | tuned by eye, never compared side by side |
-| Optical Margin Alignment | done | Type › Optical Margin Alignment, `type.opticalMarginAlignment`: per type object, with one setting for all the frames of a thread, in point and area type of left-to-right paragraphs, and at the brackets of type on a path whose first paragraph runs left to right; right-to-left paragraphs of point and area type, and type on a path whose first paragraph runs right to left, do not hang |
| Fractional Widths | done | Character panel menu › Fractional Widths, `type.fractionalWidths`: per type object, and every frame of its threads. On by default and for documents from before it; saved only when off. Off, each character that takes space advances by a whole number of points, at least one, so small type does not run together. System Layout (the document-wide whole-pixel setting) remains a panel stub. Evidence: `tests::fractional_widths_off_rounds_positive_advances_to_whole_points`, `typecmd::area_tests::fractional_widths_is_set_per_object_and_saved_only_when_off`, `panels::character::tests::fractional_widths_menu_item_toggles_the_type_object` |
| Optical Margin Alignment | done | Type › Optical Margin Alignment, `type.opticalMarginAlignment`: per type object, with one setting for all the frames of a thread, in point and area type of left-to-right and right-to-left paragraphs, and at the brackets of type on a path |
| Roman Hanging Punctuation | done | Paragraph panel menu › Roman Hanging Punctuation, `text.setFormat {romanHangingPunctuation}` (#2756): per paragraph, Optical Margin Alignment's punctuation and amounts without the letters; paragraph styles carry it. Evidence: `tests::roman_hanging_punctuation_hangs_punctuation_but_not_letters`, `typecmd::area_tests::roman_hanging_punctuation_is_set_per_paragraph_and_saved_only_when_on` |