From 10a87311d38ba8a8e399bda4435f08025305e106 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Thu, 8 Oct 2026 17:15:21 +0700 Subject: [PATCH] Type: burasagari labels Regular/Force, full-width commas and full stops hang, MCP docs (review of #463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The Paragraph panel menu's Burasagari submenu reads None / Regular / Force, as the reference app's help names them (`text.setFormat` keeps "none"/"standard"/"forced", its scripting names); Reset Panel goes back to new type's Regular. - The full-width ,.and half-width 、。 hang too; Latin commas and full stops never do, so Latin text keeps its breaks and composer with new type's default (tested). The spaces after a hanging mark stay on its line. - docs/mcp.md: a Burasagari section. ROADMAP: wording, Last updated. Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 4 ++-- crates/doc/src/text.rs | 5 +++-- crates/engine/src/cmd/panelcmds.rs | 2 +- crates/text/src/layout.rs | 14 ++++++++++---- crates/text/src/tests.rs | 20 ++++++++++++++++++++ crates/text/src/tests_vertical.rs | 5 +++++ crates/ui-egui/src/i18n/es.tsv | 3 +-- crates/ui-egui/src/i18n/ja.tsv | 3 +-- crates/ui-egui/src/i18n/zh-hans.tsv | 3 +-- crates/ui-egui/src/i18n/zh-hant.tsv | 3 +-- crates/ui-egui/src/panels/paragraph.rs | 8 ++++---- docs/mcp.md | 14 ++++++++++++++ 12 files changed, 63 insertions(+), 21 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 6080ca34..af8be0d7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,7 +4,7 @@ VectorCraft is a clean-room, open-source, pure-Rust reimplementation of the Adob This file tracks **how far we are and what's left**. Time estimates are wall-clock hours of continuous Claude Opus 5.5 agent work (including builds and the CI gate), given both for **one agent** and for **4–6 parallel agents** on disjoint crates. They are counted from the remaining work (see [Parity estimate](#parity-estimate)), calibrated against measured throughput, and updated as work lands. -_Last updated: 2026-10-08 (after M4.14–M4.98, M8.1–M8.20, M14.4–M14.7, the MCP prompts, resource templates, completions and logging, the 2026-10-07 issue fixes, the Layers panel rework and the 2026-10-08 community contributions: Pen and shape-tool modifiers, path editing, Layers, Artboards, Swatches/Brushes and dialogs, Japanese composition and PDF text, Hebrew/Arabic type, interface languages (Spanish among them), the font menu, saved selections, MCP and EPS, the missing-font export warning (#319), open paths in the Shape Builder (#374), PDF/`.ai` import keeping strokes live and hidden and nested layers (#372), per-corner Live Corners (#381), SVG type opening at the size it draws at (#396), the Wayland clipboard with copied-file paste (#398), ruler guides that can be selected, moved and deleted (#414), two batches of Preferences options that were stored but unused (#394), skeleton reads with `document.find` for large documents over MCP (#416) and anchors dragged with Direct Selection snapping to points, segments and smart guides, clipped SVG layers opening as clipping layers (#420), PDF/`.ai` layers read on pages of tens of millions of operators (#419), Document Color Mode putting RGB greys on the black plate on request and converting pattern tiles (#421) and movable dialogs with Offset Path's Joins dropdown (#423) and numeric fields scrubbed by dragging their label (#400), stroke weight presets in the stroke unit (#426), the Appearance panel's Opacity popup (#430), type on a path brackets, flip and Type on a Path Options that keep the type in place (#429), Layers rows dragged with a ghost and dropped on the trash or New Layer (#435), tool flyouts opening on a right or corner press (#436), tool flyouts torn off into floating toolbars (#437), the Shape Builder's erase mode deleting edges too (#439), installed fonts registered outside the font folders and fonts installed while the app runs listed in the font menus (#443), alike circular corners on rectangles that aren't square (#442), tool cursors as OS cursors so the pointer no longer trails over the canvas (#444), artboards copied and pasted with their art (#446), guides dragged out of the rulers snapping to the art's side midpoints and the artboards, artboard guides (#451), the toolbar's double arrow and Window › Toolbars › Double Column switching between one and two columns in any window height (#455), the Smart Guides display options and Snapping Tolerance (#394), Font Size and Leading combo boxes with preset lists in the Character and Properties panels and the Control bar (#458), screenshots pasted from the clipboard on macOS, Windows and the web (#457), the Properties panel's Fill and Stroke matching the Control bar's, Illustrator's Window menu panel keys (Shift+F7 for Align…) and Alt-wheel zoom whatever the wheel preference (Discord feedback), the point/area type widget (Discord feedback), groups in legacy Illustrator .ai files (Discord feedback), a rotating desktop log file (#465), image controls in the Control bar and Properties panel: Image Trace with its presets, Mask, Crop Image, Embed/Unembed, and an Image Trace object's preset, Expand and Release (Discord feedback), the Pen working as the Anchor Point tool with Alt held over a selected path's handles and anchors, and anchor buttons that convert, connect and cut paths at the selected anchors (Discord feedback); see [Honest assessment](#honest-assessment-2026-10-05))._ +_Last updated: 2026-10-08 (after M4.14–M4.98, M8.1–M8.20, M14.4–M14.7, the MCP prompts, resource templates, completions and logging, the 2026-10-07 issue fixes, the Layers panel rework and the 2026-10-08 community contributions: Pen and shape-tool modifiers, path editing, Layers, Artboards, Swatches/Brushes and dialogs, Japanese composition and PDF text, Hebrew/Arabic type, interface languages (Spanish among them), the font menu, saved selections, MCP and EPS, the missing-font export warning (#319), open paths in the Shape Builder (#374), PDF/`.ai` import keeping strokes live and hidden and nested layers (#372), per-corner Live Corners (#381), SVG type opening at the size it draws at (#396), the Wayland clipboard with copied-file paste (#398), ruler guides that can be selected, moved and deleted (#414), two batches of Preferences options that were stored but unused (#394), skeleton reads with `document.find` for large documents over MCP (#416) and anchors dragged with Direct Selection snapping to points, segments and smart guides, clipped SVG layers opening as clipping layers (#420), PDF/`.ai` layers read on pages of tens of millions of operators (#419), Document Color Mode putting RGB greys on the black plate on request and converting pattern tiles (#421) and movable dialogs with Offset Path's Joins dropdown (#423) and numeric fields scrubbed by dragging their label (#400), stroke weight presets in the stroke unit (#426), the Appearance panel's Opacity popup (#430), type on a path brackets, flip and Type on a Path Options that keep the type in place (#429), Layers rows dragged with a ghost and dropped on the trash or New Layer (#435), tool flyouts opening on a right or corner press (#436), tool flyouts torn off into floating toolbars (#437), the Shape Builder's erase mode deleting edges too (#439), installed fonts registered outside the font folders and fonts installed while the app runs listed in the font menus (#443), alike circular corners on rectangles that aren't square (#442), tool cursors as OS cursors so the pointer no longer trails over the canvas (#444), artboards copied and pasted with their art (#446), guides dragged out of the rulers snapping to the art's side midpoints and the artboards, artboard guides (#451), the toolbar's double arrow and Window › Toolbars › Double Column switching between one and two columns in any window height (#455), the Smart Guides display options and Snapping Tolerance (#394), Font Size and Leading combo boxes with preset lists in the Character and Properties panels and the Control bar (#458), screenshots pasted from the clipboard on macOS, Windows and the web (#457), the Properties panel's Fill and Stroke matching the Control bar's, Illustrator's Window menu panel keys (Shift+F7 for Align…) and Alt-wheel zoom whatever the wheel preference (Discord feedback), the point/area type widget (Discord feedback), groups in legacy Illustrator .ai files (Discord feedback), a rotating desktop log file (#465), image controls in the Control bar and Properties panel: Image Trace with its presets, Mask, Crop Image, Embed/Unembed, and an Image Trace object's preset, Expand and Release (Discord feedback), the Pen working as the Anchor Point tool with Alt held over a selected path's handles and anchors, and anchor buttons that convert, connect and cut paths at the selected anchors (Discord feedback), Japanese hanging punctuation (burasagari, #463); see [Honest assessment](#honest-assessment-2026-10-05))._ ## Where we are @@ -134,7 +134,7 @@ grade by behaviour against `plan/illustrator/`, not by whether a menu item exist - Pathfinder (10 exact curve booleans), Offset, Outline Stroke, Simplify, Clean Up, Split Into Grid, Divide Objects Below. Object › Path › Remove Anchor Points (`path.removeAnchors`) removes the direct-selected anchors without opening their paths (the Delete key still deletes them with their segments): the neighbours keep their handle directions and their facing handles are refitted so one cubic follows the two old segments, and straight sides stay straight. The Delete Anchor Point tool uses the same refit. The Control bar, the contextual task bar, the Properties quick actions and the canvas context menu offer it when anchors are selected. With anchors selected, the Control bar and the Properties panel also convert them to corner or smooth (`path.convertAnchors`), connect end points (Join) and cut the path at them (`path.cutAtAnchors`, also on the task bar and the context menu): a closed path opens there and an open one becomes one path per piece, each cut leaving one of its two coincident anchors selected so a drag pulls the path apart. - Live effects with previewing dialogs: Distort & Transform, Path, Convert to Shape, 15 Warp styles, Round Corners, Scribble, Effect → Pathfinder (all 10 operations, live on groups; several loose objects are grouped first, in one undo step), Color Adjustments (Brightness/Contrast, Curves, Levels, Hue/Saturation, Shift to Color, Temperature/Tint, on vectors, live type and embedded images), and raster drop shadow, glows and feather. SVG and PDF export keep live effects (geometry baked; SVG raster effects as filters). - **Type:** Text Wrap, Type on a Path effects (Rainbow/Skew/3D Ribbon/Stair Step/Gravity) and Options (Flip, Align to Path, Spacing; OK keeps the type where it is), type on a path's start, centre and end brackets (Selection and Direct Selection drag them to set where the type runs, slide it along its path and, across the path, flip it: #429), Character and Paragraph Styles (override-preserving redefine), Area Type Options (rows/columns/inset/first baseline), threaded text across any closed shapes, Fit Headline. Area type resizes like Illustrator's type areas: a bounding-box handle drag (Selection tool) or Area Type Options Width/Height size the frame and the text reflows at its size (threads too); Direct Selection drags a frame corner or edge (`text.reshapeArea`). Point type still scales. The Selection tool shows the type widget beside selected type's bounding box (hollow on point type, filled on area type); double-clicking it converts between them (Type › Convert To Area Type / Convert To Point Type), keeping the text and its styles in place. Variable fonts list every named instance as a style of their family (Figtree Light to Black and their italics, Bahnschrift, Segoe UI Variable), and the instance chosen is shaped, drawn, outlined and measured at its axis settings; PDF and SVG embed it as that instance. Variation axis sliders are not done. Hebrew and Arabic are laid out right to left with the Unicode bidirectional algorithm (embedded Latin and numbers keep their direction, lines put in visual order after wrapping) in point, area and path type, with caret, selection and arrow keys in visual order; each paragraph's direction comes from its first strong character unless Paragraph Direction sets it (Paragraph panel with Show Indic Options, `text.setFormat {direction}`), and new type aligns to the start of its direction. SVG export outlines right-to-left text. Font Size and Leading are combo boxes in the Character and Properties panels (Font Size in the Control bar too, beside the Character link, font family and style): type, step with ↑/↓ or the spinner, scrub the label, or pick a preset (6–72 pt, shown in the type unit; Leading also Auto), each pick one undo step; Kerning and Tracking list -100…200; a size, leading, kerning or tracking the selected text doesn't share shows blank (#458). -- **Interface languages, vertical type:** VectorCraft ▸ Language (or Preferences ▸ User Interface) picks Automatic (the system locale) or a registered language, persisted as `interfaceLanguage`. Translations come from per-language catalogs (`i18n/*.tsv`): Traditional and Simplified Chinese, Japanese and Spanish (neutral international Spanish, for every `es-*` locale) cover the whole interface, Czech and Brazilian Portuguese every menu label, Brazilian Portuguese the shell's `tl!` literals too (tested); Spanish also translates status-bar and error messages as they are shown (`@msg` templates, every message literal tested; the messages themselves stay English for agents); untranslated text stays English. Japanese and Chinese glyphs come from the craft-fonts build input (release builds) or the installed fonts, Czech and Spanish ones from the bundled UI fonts. The web app follows the browser's languages (or `?lang=`). The Basic toolbar's Type button holds Vertical Type in its flyout. Vertical Type, Vertical Area Type and Vertical Type on a Path create vertical text (columns right to left, upright CJK glyphs and marks with `vert` alternates, Latin and longer numbers on their side, two- and three-digit numbers set as tate-chu-yoko in one em of the column (kept whole when justified), upright glyphs centred on the column (tracked or justified too), point type's anchor on the first column's centre line); kinsoku (the strict set) applies to horizontal and vertical Japanese, and justified Japanese lines spread their room between characters (not inside Latin words); Paragraph panel › Mojikumi Set (shown with Preferences › Type › Show East Asian Options, `text.setFormat {mojikumi}`): None or Line-end Punctuation Half Width (new type's default, as in Illustrator; older documents and imported text keep None) shares one half-em space between consecutive punctuation, sets a closing mark ending a line half width and an opening bracket starting a wrapped line flush with its start, and puts a quarter em between Japanese and Latin letters or digits; Paragraph panel menu › Top-to-Top / Bottom-to-Bottom Leading measures leading from em box top to top (area type's first line touches the frame) or from baseline to baseline; Paragraph panel menu › Burasagari (`text.setFormat {burasagari}`) hangs a Japanese comma or full stop ending an area type line outside it, Standard (new type's default, as in Illustrator) when it doesn't fit and Forced always (the rest of the line then fills the measure; closing brackets don't hang); Character panel menu › Character Alignment (with the East Asian options; `text.setFormat` or `text.setRangeStyle {charAlign}`) lines characters smaller than the largest on their line up on the Roman baseline or the em box top/right, centre or bottom/left (ICF top and bottom not yet); Type ▸ Type Orientation switches existing text; caret, selection, hit testing and arrow keys follow the writing direction. Vertical text exports to PDF as real text and to SVG as outlines. +- **Interface languages, vertical type:** VectorCraft ▸ Language (or Preferences ▸ User Interface) picks Automatic (the system locale) or a registered language, persisted as `interfaceLanguage`. Translations come from per-language catalogs (`i18n/*.tsv`): Traditional and Simplified Chinese, Japanese and Spanish (neutral international Spanish, for every `es-*` locale) cover the whole interface, Czech and Brazilian Portuguese every menu label, Brazilian Portuguese the shell's `tl!` literals too (tested); Spanish also translates status-bar and error messages as they are shown (`@msg` templates, every message literal tested; the messages themselves stay English for agents); untranslated text stays English. Japanese and Chinese glyphs come from the craft-fonts build input (release builds) or the installed fonts, Czech and Spanish ones from the bundled UI fonts. The web app follows the browser's languages (or `?lang=`). The Basic toolbar's Type button holds Vertical Type in its flyout. Vertical Type, Vertical Area Type and Vertical Type on a Path create vertical text (columns right to left, upright CJK glyphs and marks with `vert` alternates, Latin and longer numbers on their side, two- and three-digit numbers set as tate-chu-yoko in one em of the column (kept whole when justified), upright glyphs centred on the column (tracked or justified too), point type's anchor on the first column's centre line); kinsoku (the strict set) applies to horizontal and vertical Japanese, and justified Japanese lines spread their room between characters (not inside Latin words); Paragraph panel › Mojikumi Set (shown with Preferences › Type › Show East Asian Options, `text.setFormat {mojikumi}`): None or Line-end Punctuation Half Width (new type's default, as in Illustrator; older documents and imported text keep None) shares one half-em space between consecutive punctuation, sets a closing mark ending a line half width and an opening bracket starting a wrapped line flush with its start, and puts a quarter em between Japanese and Latin letters or digits; Paragraph panel menu › Top-to-Top / Bottom-to-Bottom Leading measures leading from em box top to top (area type's first line touches the frame) or from baseline to baseline; Paragraph panel menu › Burasagari None / Regular / Force (`text.setFormat {burasagari: "none"|"standard"|"forced"}`) hangs an East Asian comma or full stop (、。,.、。) ending an area type line outside it, Regular (new type's default) when it doesn't fit and Force always (the rest of the line then fills the measure; closing brackets and Latin punctuation don't hang); Character panel menu › Character Alignment (with the East Asian options; `text.setFormat` or `text.setRangeStyle {charAlign}`) lines characters smaller than the largest on their line up on the Roman baseline or the em box top/right, centre or bottom/left (ICF top and bottom not yet); Type ▸ Type Orientation switches existing text; caret, selection, hit testing and arrow keys follow the writing direction. Vertical text exports to PDF as real text and to SVG as outlines. - **Transparency:** opacity masks (clip/invert/disable/link), exported as SVG `` and PDF soft masks. - **Advanced art:** live Blends (steps/distance/smooth colour, editable spine, anchor-targeted Blend tool, faithful interpolation, knockout), Envelope Distort (warp/mesh/top object, Reset, full Envelope Options, mesh handles, Edit Contents; type, images, symbols, gradients, patterns and appearance distorted everywhere), Liquify tools with options and pen pressure, Puppet Warp with rotating pins, Perspective Grid with presets, movable planes and projected type (choosing a perspective tool shows the grid; Hide Grid hides it with the tool still chosen), Gradient Mesh, Shape Builder (stroke-only open paths and lines cut the regions they cross and enclose regions of their own; filled open paths count as closed; erase mode while Alt/Option is held: a minus pointer, red highlights of what will go, a click or drag deletes the regions touched, the pieces of open lines and the edges (outline pieces between the points where paths meet, opening their paths there) it passes over, one undo step, `shapeBuilder.merge {erase}` and `shapeBuilder.regions` edges over MCP), Live Paint (every path an edge, open lines included), Image Trace (12 presets; with an image selected the Control bar and the Properties panel show Image Trace, whose arrow traces with any preset, Mask (`object.maskImage`: a clipping path on the image's outline, selected so its handles crop), Crop Image and Embed or Unembed in place of Fill and Stroke; with an Image Trace object selected, its Preset (choosing another traces again), the Image Trace panel and Expand, plus Release in Properties; the View modes are not done), pattern swatches with pattern editing mode, live Repeat (radial/grid/mirror). - **Paint and appearance (M3):** diff --git a/crates/doc/src/text.rs b/crates/doc/src/text.rs index d27d65bd..913fc7fd 100644 --- a/crates/doc/src/text.rs +++ b/crates/doc/src/text.rs @@ -475,9 +475,10 @@ impl Mojikumi { } } -/// Hanging punctuation (burasagari): a Japanese comma or full stop ending a line +/// Hanging punctuation (burasagari): an East Asian comma or full stop ending a line (、。,.、。) /// stands outside the line's measure, in the space its punctuation spacing gives it (half width -/// with Line-end Punctuation Half Width). Closing brackets don't hang. +/// with Line-end Punctuation Half Width). Closing brackets and Latin punctuation don't hang. Shown +/// as None / Regular / Force. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum Burasagari { diff --git a/crates/engine/src/cmd/panelcmds.rs b/crates/engine/src/cmd/panelcmds.rs index 0f059755..3cee0f4a 100644 --- a/crates/engine/src/cmd/panelcmds.rs +++ b/crates/engine/src/cmd/panelcmds.rs @@ -43,7 +43,7 @@ pub fn specs() -> Vec { "Character / Paragraph", [], None, - "{ids?|id?, kerning?: 1/1000 em|\"auto\", baselineShift?: pt, hScale?: %, vScale?: %, rotation?: deg, underline?, strikethrough?, allCaps?, smallCaps?: bool, position?: \"normal\"|\"superscript\"|\"subscript\" (sizes from Document Setup), leftIndent?, rightIndent?, firstLineIndent?, spaceBefore?, spaceAfter?: pt, hyphenate?: bool, mojikumi?: \"none\"|\"lineEndHalf\" (Japanese punctuation spacing), direction?: \"auto\"|\"leftToRight\"|\"rightToLeft\" (paragraph direction; auto: from each paragraph's first strong character), leadingModel?: \"romanBaseline\"|\"emBoxTop\" (leading measured baseline to baseline, or em box top to top), charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\" (where characters smaller than the largest on their line line up with it), burasagari?: \"none\"|\"standard\"|\"forced\" (a Japanese comma or full stop ending a line hangs outside it: when it doesn't fit, or always)}", + "{ids?|id?, kerning?: 1/1000 em|\"auto\", baselineShift?: pt, hScale?: %, vScale?: %, rotation?: deg, underline?, strikethrough?, allCaps?, smallCaps?: bool, position?: \"normal\"|\"superscript\"|\"subscript\" (sizes from Document Setup), leftIndent?, rightIndent?, firstLineIndent?, spaceBefore?, spaceAfter?: pt, hyphenate?: bool, mojikumi?: \"none\"|\"lineEndHalf\" (Japanese punctuation spacing), direction?: \"auto\"|\"leftToRight\"|\"rightToLeft\" (paragraph direction; auto: from each paragraph's first strong character), leadingModel?: \"romanBaseline\"|\"emBoxTop\" (leading measured baseline to baseline, or em box top to top), charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\" (where characters smaller than the largest on their line line up with it), burasagari?: \"none\"|\"standard\"|\"forced\" (Paragraph panel menu › Burasagari None/Regular/Force: an East Asian comma or full stop ending an area type line hangs outside it: when it doesn't fit, or always; new type: \"standard\")}", has_doc, set_format ), diff --git a/crates/text/src/layout.rs b/crates/text/src/layout.rs index 45cda408..cbd3e49b 100644 --- a/crates/text/src/layout.rs +++ b/crates/text/src/layout.rs @@ -762,8 +762,13 @@ fn break_line(text: &str, g: &[SGlyph], i: usize, width: f64, hyphenate: bool, b while j < g.len() { let gl = &g[j]; if j > i && !gl.is_space() && x + gl.adv > width + EPS { + // It ends the line with the spaces after it. if burasagari != Burasagari::None && hangs(gl) && kinsoku_allows(g, j) && g.get(j + 1).is_none_or(|n| n.byte != gl.byte) { - return (j + 1, false); + let mut end = j + 1; + while g.get(end).is_some_and(SGlyph::is_space) { + end += 1; + } + return (end, false); } break; } @@ -811,10 +816,11 @@ fn break_line(text: &str, g: &[SGlyph], i: usize, width: f64, hyphenate: bool, b (end, hy) } -/// Can glyph `g` hang outside the line (burasagari)? A Japanese comma or full stop, not a closing -/// bracket. +/// Can glyph `g` hang outside the line (burasagari)? An East Asian comma or full stop, full width +/// (、。,.) or half width (、。); not a closing bracket, nor Latin punctuation (Latin text keeps +/// its line breaks and composer). fn hangs(g: &SGlyph) -> bool { - matches!(g.ch, '、' | '。') && g.tcy.is_none() + matches!(g.ch, '、' | '。' | ',' | '.' | '、' | '。') && g.tcy.is_none() } /// Does kinsoku allow a line break after glyph `j`? Not after an opening bracket, nor before a diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index 303f2c7d..b670fcb9 100644 --- a/crates/text/src/tests.rs +++ b/crates/text/src/tests.rs @@ -585,3 +585,23 @@ fn character_alignment_lines_small_characters_up_with_the_largest_em_box() { assert!(near(place(CharAlign::EmBoxBottom), -0.12 * 20.0), "bottom: {}", place(CharAlign::EmBoxBottom)); } } + +/// Burasagari leaves Latin punctuation alone: in a measure of exactly "abcd", the full stop of +/// "abcd. ef" doesn't hang with Standard or Forced (the line breaks as it does with None), and a +/// Latin line ending in a full stop isn't shortened by Forced. +#[test] +fn burasagari_leaves_latin_commas_and_full_stops_inside_the_line() { + use vectorcraft_doc::Burasagari; + let measure = width(&point("abcd", style(20.0))); + let lay = |text: &str, b: Burasagari| { + let mut t = area(text, style(20.0), Rect::new(0.0, 0.0, measure + 0.01, 400.0), Justify::JustifyLeft); + t.para.burasagari = b; + layout(db(), &t).glyphs.iter().map(|g| (g.line, (g.origin.x * 100.0).round())).collect::>() + }; + for text in ["abcd. ef", "a b. efgh", "ab, cd, efgh"] { + let none = lay(text, Burasagari::None); + for b in [Burasagari::Standard, Burasagari::Forced] { + assert_eq!(lay(text, b), none, "{text} {b:?}"); + } + } +} diff --git a/crates/text/src/tests_vertical.rs b/crates/text/src/tests_vertical.rs index b2657045..cc72bbd3 100644 --- a/crates/text/src/tests_vertical.rs +++ b/crates/text/src/tests_vertical.rs @@ -459,6 +459,11 @@ fn burasagari_hangs_a_comma_or_full_stop_outside_the_line() { let l = lay("一二三四五」六七", b, vertical_type); assert_ne!(l.glyphs[4].line, l.glyphs[0].line, "vertical {vertical_type} {b:?}"); } + // The full-width comma and full stop hang too. + for mark in [',', '.'] { + let l = lay(&format!("一二三四五{mark}六七"), Burasagari::Standard, vertical_type); + assert_eq!(l.glyphs[5].line, l.glyphs[0].line, "vertical {vertical_type}: {mark} hangs"); + } } // Horizontal: the hanging mark starts at the frame's edge. let l = lay("一二三四五、六七", Burasagari::Standard, false); diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index d5fc3eaf..6f26fc02 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -3006,8 +3006,7 @@ Top-to-Top Leading Interlineado de superior a superior Bottom-to-Bottom Leading Interlineado de inferior a inferior Burasagari Burasagari - Forced Forzado - Standard Estándar + Force Forzar # About › Contributors / Models (credits.rs) About Acerca de diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 71f3c23f..8d583678 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -16,8 +16,7 @@ Baseline ベースライン Bottom-to-Bottom Leading 欧文ベースライン基準の行送り Burasagari ぶら下がり - Forced 強制 - Standard 標準 + Force 強制 Center 中央 Character Alignment 文字揃え Connect Selected End Points 選択した端点を連結 diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 8219334b..393ac1f4 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -250,8 +250,7 @@ Bottom in Front 下方置前 Bottom-to-Bottom Leading 基线至基线行距 Burasagari 标点悬挂 - Forced 强制 - Standard 标准 + Force 强制 Bottom: 下: Bounds 边界 Bounds (Fill Proportionally) 边界 (按比例填满) diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 56dc5fb4..bd6d1310 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -261,8 +261,7 @@ Bottom in Front 下方置前 Bottom-to-Bottom Leading 基線至基線行距 Burasagari 標點懸掛 - Forced 強制 - Standard 標準 + Force 強制 Bottom: 下: Bounds 邊界 Bounds (Fill Proportionally) 邊界 (按比例填滿) diff --git a/crates/ui-egui/src/panels/paragraph.rs b/crates/ui-egui/src/panels/paragraph.rs index e758ebb3..25ea8dbb 100644 --- a/crates/ui-egui/src/panels/paragraph.rs +++ b/crates/ui-egui/src/panels/paragraph.rs @@ -162,7 +162,7 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { if menu_item(ui, tl!("Bottom-to-Bottom Leading"), has, has && !top_to_top) { format(app, json!({"leadingModel": "romanBaseline"})); } - // Hanging punctuation: a Japanese comma or full stop ending a line stands outside it. + // Hanging punctuation: a comma or full stop ending a line stands outside it. let hang = style.as_ref().map(|(_, p)| p.burasagari); ui.add_enabled_ui(has, |ui| { // Indented like the items beside it (their check column). @@ -170,8 +170,8 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { use vectorcraft_doc::Burasagari; for (label, b, key) in [ (tl!("None"), Burasagari::None, "none"), - (tl!("Standard"), Burasagari::Standard, "standard"), - (tl!("Forced"), Burasagari::Forced, "forced"), + (tl!("Regular"), Burasagari::Standard, "standard"), + (tl!("Force"), Burasagari::Forced, "forced"), ] { if menu_item(ui, label, true, hang == Some(b)) { format(app, json!({"burasagari": key})); @@ -188,7 +188,7 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { para_cmd(app, "text.setStyle", json!({"justify": "auto"})); format( app, - json!({"leftIndent": 0, "rightIndent": 0, "firstLineIndent": 0, "spaceBefore": 0, "spaceAfter": 0, "hyphenate": false, "direction": "auto", "leadingModel": "romanBaseline", "burasagari": "none"}), + json!({"leftIndent": 0, "rightIndent": 0, "firstLineIndent": 0, "spaceBefore": 0, "spaceAfter": 0, "hyphenate": false, "direction": "auto", "leadingModel": "romanBaseline", "burasagari": "standard"}), ); } } diff --git a/docs/mcp.md b/docs/mcp.md index 55d4c008..e1e981fd 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -2112,6 +2112,20 @@ Type and Convert To Point Type. Either keeps the text and its styles and is one {"name":"run_command","arguments":{"command":"type.convertToAreaType","params":{"ids":[42]}}} ``` +## Hanging punctuation (burasagari) + +`text.setFormat {burasagari: "none"|"standard"|"forced"}` sets the Paragraph panel menu's Burasagari (None / Regular / +Force) of the selected type (or `ids`): an East Asian comma or full stop ending an area type line (、。,., half-width +、。) hangs outside the frame, Regular only when it doesn't fit, Force always (the rest of the line then fills the +measure). Closing brackets and Latin punctuation don't hang; point type, right-to-left and hyphenated lines are left as +they are. The hanging mark is as wide as its punctuation spacing makes it (`mojikumi`). New type (the Type tools, +`text.create`) takes `standard`; documents from before it and imported text keep `none`, and `none` isn't saved. One +undo step. + +```json +{"name":"run_command","arguments":{"command":"text.setFormat","params":{"burasagari":"forced"}}} +``` + ## Moving and flipping type on a path Type on a path flows between a start and an end bracket, stored as fractions of its path's length.