From b8365d6197cf6b67f36ce2cfb0af20ea86705152 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sun, 1 Nov 2026 03:09:17 +0700 Subject: [PATCH] Fix #4942: native Indic digits in the Digits menu --- crates/doc/src/text/char_style.rs | 73 +++++++++++++++++++- crates/doc/src/text/tests.rs | 33 +++++++++ crates/engine/src/cmd/panelcmds.rs | 2 +- crates/engine/src/cmd/textedit.rs | 19 ++--- crates/engine/src/cmd/typecmd/area_tests.rs | 47 +++++++++++++ crates/text/src/tests/bidi.rs | 41 +++++++++++ crates/ui-egui/src/i18n/bg.tsv | 9 +++ crates/ui-egui/src/i18n/bs.tsv | 9 +++ crates/ui-egui/src/i18n/ca.tsv | 9 +++ crates/ui-egui/src/i18n/cs.tsv | 9 +++ crates/ui-egui/src/i18n/da.tsv | 9 +++ crates/ui-egui/src/i18n/de.tsv | 9 +++ crates/ui-egui/src/i18n/el.tsv | 9 +++ crates/ui-egui/src/i18n/es.tsv | 9 +++ crates/ui-egui/src/i18n/fi.tsv | 9 +++ crates/ui-egui/src/i18n/fr.tsv | 9 +++ crates/ui-egui/src/i18n/hu.tsv | 9 +++ crates/ui-egui/src/i18n/id.tsv | 9 +++ crates/ui-egui/src/i18n/it.tsv | 9 +++ crates/ui-egui/src/i18n/ja.tsv | 9 +++ crates/ui-egui/src/i18n/ka.tsv | 9 +++ crates/ui-egui/src/i18n/ko.tsv | 9 +++ crates/ui-egui/src/i18n/nl.tsv | 9 +++ crates/ui-egui/src/i18n/pl.tsv | 9 +++ crates/ui-egui/src/i18n/pt-br.tsv | 9 +++ crates/ui-egui/src/i18n/ru.tsv | 9 +++ crates/ui-egui/src/i18n/sv.tsv | 9 +++ crates/ui-egui/src/i18n/tests.rs | 16 +++++ crates/ui-egui/src/i18n/tr.tsv | 9 +++ crates/ui-egui/src/i18n/uk.tsv | 9 +++ crates/ui-egui/src/i18n/vi.tsv | 9 +++ crates/ui-egui/src/i18n/zh-hans.tsv | 9 +++ crates/ui-egui/src/i18n/zh-hant.tsv | 9 +++ crates/ui-egui/src/panels/character.rs | 54 +++++++++++---- crates/ui-egui/src/panels/character/tests.rs | 13 ++++ docs/mcp.md | 13 ++-- docs/type-parity.md | 5 +- 37 files changed, 516 insertions(+), 34 deletions(-) diff --git a/crates/doc/src/text/char_style.rs b/crates/doc/src/text/char_style.rs index 26c8b3c4f..d53c99e6c 100644 --- a/crates/doc/src/text/char_style.rs +++ b/crates/doc/src/text/char_style.rs @@ -123,8 +123,9 @@ pub struct CharStyle { } /// The numeral system digits are drawn in ([`CharStyle::digits`]): as typed, or every digit of -/// the Western, Arabic-Indic and Eastern Arabic-Indic sets drawn in one of them. The text keeps -/// the digits that were typed; only the glyphs change (like All Caps). +/// the Western, Arabic-Indic and Eastern Arabic-Indic sets drawn in one of them or in the digits +/// of an Indic script. The text keeps the digits that were typed; only the glyphs change (like +/// All Caps). #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum Digits { @@ -137,9 +138,68 @@ pub enum Digits { Hindi, /// Eastern Arabic-Indic digits ۰–۹ (U+06F0–U+06F9), used in Persian and Urdu. Farsi, + /// Devanagari digits ०–९ (U+0966–U+096F), used in Hindi, Marathi, Nepali and Sanskrit. + Devanagari, + /// Bengali digits ০–৯ (U+09E6–U+09EF), used in Bengali and Assamese. + Bengali, + /// Gurmukhi digits ੦–੯ (U+0A66–U+0A6F), used in Punjabi. + Gurmukhi, + /// Gujarati digits ૦–૯ (U+0AE6–U+0AEF). + Gujarati, + /// Odia digits ୦–୯ (U+0B66–U+0B6F). + Odia, + /// Tamil digits ௦–௯ (U+0BE6–U+0BEF). + Tamil, + /// Telugu digits ౦–౯ (U+0C66–U+0C6F). + Telugu, + /// Kannada digits ೦–೯ (U+0CE6–U+0CEF). + Kannada, + /// Malayalam digits ൦–൯ (U+0D66–U+0D6F). + Malayalam, } impl Digits { + /// Every numeral system, in menu order. + pub const ALL: [Digits; 13] = [ + Digits::Default, + Digits::Arabic, + Digits::Hindi, + Digits::Farsi, + Digits::Devanagari, + Digits::Bengali, + Digits::Gurmukhi, + Digits::Gujarati, + Digits::Odia, + Digits::Tamil, + Digits::Telugu, + Digits::Kannada, + Digits::Malayalam, + ]; + + /// The name files and commands give this numeral system (as it is saved). + pub fn id(self) -> &'static str { + match self { + Digits::Default => "default", + Digits::Arabic => "arabic", + Digits::Hindi => "hindi", + Digits::Farsi => "farsi", + Digits::Devanagari => "devanagari", + Digits::Bengali => "bengali", + Digits::Gurmukhi => "gurmukhi", + Digits::Gujarati => "gujarati", + Digits::Odia => "odia", + Digits::Tamil => "tamil", + Digits::Telugu => "telugu", + Digits::Kannada => "kannada", + Digits::Malayalam => "malayalam", + } + } + + /// The numeral system [`Digits::id`] names; `None` for any other name. + pub fn from_id(id: &str) -> Option { + Digits::ALL.into_iter().find(|d| d.id() == id) + } + /// The digit `c` is drawn as in this numeral system: the same digit in the chosen set, or `c` /// itself when `c` isn't a digit of the Western, Arabic-Indic or Eastern Arabic-Indic set. pub fn map(self, c: char) -> char { @@ -154,6 +214,15 @@ impl Digits { Digits::Arabic => '0' as u32, Digits::Hindi => 0x660, Digits::Farsi => 0x6F0, + Digits::Devanagari => 0x966, + Digits::Bengali => 0x9E6, + Digits::Gurmukhi => 0xA66, + Digits::Gujarati => 0xAE6, + Digits::Odia => 0xB66, + Digits::Tamil => 0xBE6, + Digits::Telugu => 0xC66, + Digits::Kannada => 0xCE6, + Digits::Malayalam => 0xD66, }; char::from_u32(base + value).unwrap_or(c) } diff --git a/crates/doc/src/text/tests.rs b/crates/doc/src/text/tests.rs index 25e46a049..89a2591a9 100644 --- a/crates/doc/src/text/tests.rs +++ b/crates/doc/src/text/tests.rs @@ -60,6 +60,39 @@ fn digits_map_between_the_numeral_systems() { assert_eq!(serde_json::from_value::(serde_json::to_value(&farsi).unwrap()).unwrap(), farsi); } +/// Each Indic numeral system draws 0–9 (and the Arabic-Indic sets) as the ten digits of its +/// script's block; every system saves under its id, which reads back, and other names are refused. +#[test] +fn indic_digits_map_to_their_scripts_block() { + let map = |d: Digits, s: &str| -> String { s.chars().map(|c| d.map(c)).collect() }; + for (d, zero) in [ + (Digits::Devanagari, 0x966), + (Digits::Bengali, 0x9E6), + (Digits::Gurmukhi, 0xA66), + (Digits::Gujarati, 0xAE6), + (Digits::Odia, 0xB66), + (Digits::Tamil, 0xBE6), + (Digits::Telugu, 0xC66), + (Digits::Kannada, 0xCE6), + (Digits::Malayalam, 0xD66), + ] { + let block: String = (zero..zero + 10).map(|u| char::from_u32(u).unwrap()).collect(); + assert!(block.chars().all(char::is_numeric), "{d:?}: {block}"); + for s in ["0123456789", "٠١٢٣٤٥٦٧٨٩", "۰۱۲۳۴۵۶۷۸۹"] { + assert_eq!(map(d, s), block, "{d:?}"); + } + assert_eq!(map(d, "a-Z ٪"), "a-Z ٪"); + } + assert_eq!(map(Digits::Devanagari, "page 12"), "page १२"); + for d in Digits::ALL { + assert_eq!(serde_json::to_value(d).unwrap(), d.id()); + assert_eq!(serde_json::from_value::(d.id().into()).unwrap(), d); + assert_eq!(Digits::from_id(d.id()), Some(d)); + } + assert_eq!(Digits::from_id("roman"), None); + assert!(serde_json::from_value::("roman".into()).is_err()); +} + /// Language is saved only when set; styles written before it existed read as the default. #[test] fn language_is_saved_only_when_set() { diff --git a/crates/engine/src/cmd/panelcmds.rs b/crates/engine/src/cmd/panelcmds.rs index 081678341..163d54377 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\"|\"optical\", 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 (±1296), list?: \"none\"|\"bullet\"|\"number\", bullet?: \"none\"|\"dot\"|\"dash\"|\"circle\"|\"square\", number?: \"none\"|\"arabic\"|\"upperLetters\"|\"lowerLetters\"|\"upperRoman\"|\"lowerRoman\"|\"twoDigits\"|\"threeDigits\" (Paragraph panel › Bullets and Numbered List: each paragraph of a list starts with its marker, drawn in the font, size and color of the paragraph's first character, and its text starts at the next tab stop after the marker, the left indent counting as a stop; a bulleted list's marker is its bullet, and a numbered list's is the paragraph's number and a period: 1, 2, 3; A to Z, then AA, BB; a to z, then aa, bb; I, II, III; i, ii, iii; 01 to 99, then 100; 001 to 999, then 1000; letters and roman numerals past 3999 in Arabic digits; the numbers count the story's numbered paragraphs, its threaded frames included, from 1 after a paragraph in no list, and an empty or bulleted paragraph is not counted; list \"bullet\" keeps a list's bullet, else a dot, and list \"number\" keeps a numbered list's style, else arabic; without list, a number style makes a numbered list and a bullet makes a bulleted one; \"none\" in any of the three turns the list off; a paragraph that joins a list with left and first-line indents of 0 gets 18 and -18 unless the call sets an indent; a horizontal right-to-left list paragraph has its indents mirrored, measured from the right), hyphenate?: bool, wordsLongerThan?: letters 1..25 (5), afterFirst?, beforeLast?: letters 1..15 (3), hyphenLimit?: lines 0..25 (0: no limit), hyphenationZone?: pt 0..1296 (0; Single-line Composer only), hyphenateCapitalized?: bool (true) (Paragraph panel menu › Hyphenation…: only words longer than wordsLongerThan break, keeping afterFirst letters before the hyphen and beforeLast after it; at most hyphenLimit lines in a row end with a hyphen; a line whose text already ends within hyphenationZone of its right edge breaks before the next word instead of hyphenating it; the counts are rounded, and every value is clamped to its range), wordSpacing?: % 0..1000 (80, 100, 133), letterSpacing?: % of a word space -100..500 (0, 0, 0), glyphScaling?: % 50..200 (100, 100, 100): each [min, desired, max] or {min?, desired?, max?} (Paragraph panel menu › Justification…: Desired applies to every line, Minimum and Maximum bound what justified lines may use; values are clamped to their range, and Minimum ≤ Desired ≤ Maximum must hold afterwards or nothing changes), autoLeading?: % of the size 0..500 (120; the leading of characters whose leading is Auto), singleWordJustification?: \"fullJustify\"|\"alignLeft\"|\"alignCenter\"|\"alignRight\" (a justified line holding one word), mojikumi?: \"none\"|\"lineEndHalf\" (Japanese punctuation spacing), kinsoku?: \"none\"|\"hard\"|\"soft\" (Kinsoku Set: Soft lets 々, ー and small kana start a line), 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\"|\"icfTop\"|\"icfBottom\" (where characters smaller than the largest on their line line up with it), proportionalMetrics?: bool (full-width glyphs on the font's proportional widths: `palt` in horizontal type, `vpal` in vertical type), tateChuYoko?: bool (Character panel menu › Tate-chu-yoko, with Show East Asian Options: in vertical type those characters stand side by side across one em of the column; kept on the characters and ignored in horizontal type), noBreak?: bool (Character panel menu › No Break: these characters stay on one line, including across a space inside the run), charDirection?: \"default\"|\"leftToRight\"|\"rightToLeft\" (Character panel menu › Character Direction: the characters are set left to right or right to left inside their paragraph, as a Unicode bidi embedding; default: as the bidi algorithm sets them), digits?: \"default\"|\"arabic\"|\"hindi\"|\"farsi\" (Character panel menu › Digits: the numeral system every digit is drawn in, whatever was typed), language?: name|tag (Character panel › Language, as text.setRangeStyle), fontAxes?: {\"wght\": 650, …} (Character panel › Variable Font, as text.setRangeStyle: each axis named set in its units and clamped to its range, null back to the style's own setting), 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\"), romanHangingPunctuation?: bool (Paragraph panel menu › Roman Hanging Punctuation: quotation marks, hyphens, full stops and commas starting or ending a line hang outside the margin, asterisks, tildes, ellipses, dashes, colons and semicolons by half; off by default), kashida?: \"none\"|\"short\"|\"medium\"|\"long\"|\"stylistic\" (Paragraph panel › Insert Kashidas: justified lines of horizontal area type lengthen each Arabic word with a kashida between its last pair of joined letters, at most a quarter, half or whole em long, or whole tatweels up to an em for stylistic, before they widen their word spaces; none by default), composer?: \"singleLine\"|\"everyLine\" (line breaking, Every-line by default), start?: byte, end?: byte} (baselineShift is in document points and hScale includes the object transform; groups include their text descendants; with a range: the character attributes style that range and the paragraph attributes apply to the paragraphs it touches; without: all the text)", + "{ids?|id?, kerning?: 1/1000 em|\"auto\"|\"optical\", 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 (±1296), list?: \"none\"|\"bullet\"|\"number\", bullet?: \"none\"|\"dot\"|\"dash\"|\"circle\"|\"square\", number?: \"none\"|\"arabic\"|\"upperLetters\"|\"lowerLetters\"|\"upperRoman\"|\"lowerRoman\"|\"twoDigits\"|\"threeDigits\" (Paragraph panel › Bullets and Numbered List: each paragraph of a list starts with its marker, drawn in the font, size and color of the paragraph's first character, and its text starts at the next tab stop after the marker, the left indent counting as a stop; a bulleted list's marker is its bullet, and a numbered list's is the paragraph's number and a period: 1, 2, 3; A to Z, then AA, BB; a to z, then aa, bb; I, II, III; i, ii, iii; 01 to 99, then 100; 001 to 999, then 1000; letters and roman numerals past 3999 in Arabic digits; the numbers count the story's numbered paragraphs, its threaded frames included, from 1 after a paragraph in no list, and an empty or bulleted paragraph is not counted; list \"bullet\" keeps a list's bullet, else a dot, and list \"number\" keeps a numbered list's style, else arabic; without list, a number style makes a numbered list and a bullet makes a bulleted one; \"none\" in any of the three turns the list off; a paragraph that joins a list with left and first-line indents of 0 gets 18 and -18 unless the call sets an indent; a horizontal right-to-left list paragraph has its indents mirrored, measured from the right), hyphenate?: bool, wordsLongerThan?: letters 1..25 (5), afterFirst?, beforeLast?: letters 1..15 (3), hyphenLimit?: lines 0..25 (0: no limit), hyphenationZone?: pt 0..1296 (0; Single-line Composer only), hyphenateCapitalized?: bool (true) (Paragraph panel menu › Hyphenation…: only words longer than wordsLongerThan break, keeping afterFirst letters before the hyphen and beforeLast after it; at most hyphenLimit lines in a row end with a hyphen; a line whose text already ends within hyphenationZone of its right edge breaks before the next word instead of hyphenating it; the counts are rounded, and every value is clamped to its range), wordSpacing?: % 0..1000 (80, 100, 133), letterSpacing?: % of a word space -100..500 (0, 0, 0), glyphScaling?: % 50..200 (100, 100, 100): each [min, desired, max] or {min?, desired?, max?} (Paragraph panel menu › Justification…: Desired applies to every line, Minimum and Maximum bound what justified lines may use; values are clamped to their range, and Minimum ≤ Desired ≤ Maximum must hold afterwards or nothing changes), autoLeading?: % of the size 0..500 (120; the leading of characters whose leading is Auto), singleWordJustification?: \"fullJustify\"|\"alignLeft\"|\"alignCenter\"|\"alignRight\" (a justified line holding one word), mojikumi?: \"none\"|\"lineEndHalf\" (Japanese punctuation spacing), kinsoku?: \"none\"|\"hard\"|\"soft\" (Kinsoku Set: Soft lets 々, ー and small kana start a line), 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\"|\"icfTop\"|\"icfBottom\" (where characters smaller than the largest on their line line up with it), proportionalMetrics?: bool (full-width glyphs on the font's proportional widths: `palt` in horizontal type, `vpal` in vertical type), tateChuYoko?: bool (Character panel menu › Tate-chu-yoko, with Show East Asian Options: in vertical type those characters stand side by side across one em of the column; kept on the characters and ignored in horizontal type), noBreak?: bool (Character panel menu › No Break: these characters stay on one line, including across a space inside the run), charDirection?: \"default\"|\"leftToRight\"|\"rightToLeft\" (Character panel menu › Character Direction: the characters are set left to right or right to left inside their paragraph, as a Unicode bidi embedding; default: as the bidi algorithm sets them), digits?: \"default\"|\"arabic\"|\"hindi\"|\"farsi\"|\"devanagari\"|\"bengali\"|\"gurmukhi\"|\"gujarati\"|\"odia\"|\"tamil\"|\"telugu\"|\"kannada\"|\"malayalam\" (Character panel menu › Digits: the numeral system every digit is drawn in, whatever was typed: Western, Arabic-Indic (hindi), Eastern Arabic-Indic (farsi) or an Indic script's own digits), language?: name|tag (Character panel › Language, as text.setRangeStyle), fontAxes?: {\"wght\": 650, …} (Character panel › Variable Font, as text.setRangeStyle: each axis named set in its units and clamped to its range, null back to the style's own setting), 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\"), romanHangingPunctuation?: bool (Paragraph panel menu › Roman Hanging Punctuation: quotation marks, hyphens, full stops and commas starting or ending a line hang outside the margin, asterisks, tildes, ellipses, dashes, colons and semicolons by half; off by default), kashida?: \"none\"|\"short\"|\"medium\"|\"long\"|\"stylistic\" (Paragraph panel › Insert Kashidas: justified lines of horizontal area type lengthen each Arabic word with a kashida between its last pair of joined letters, at most a quarter, half or whole em long, or whole tatweels up to an em for stylistic, before they widen their word spaces; none by default), composer?: \"singleLine\"|\"everyLine\" (line breaking, Every-line by default), start?: byte, end?: byte} (baselineShift is in document points and hScale includes the object transform; groups include their text descendants; with a range: the character attributes style that range and the paragraph attributes apply to the paragraphs it touches; without: all the text)", has_doc, set_format ), diff --git a/crates/engine/src/cmd/textedit.rs b/crates/engine/src/cmd/textedit.rs index 0efc70bc6..1797873ae 100644 --- a/crates/engine/src/cmd/textedit.rs +++ b/crates/engine/src/cmd/textedit.rs @@ -39,7 +39,7 @@ pub fn specs() -> Vec { "Character", [], None, - "{id? (default: the one selected type object), start?: byte, end?: byte (default: all text), font?, style?, size?: pt, leading?: pt|\"auto\", tracking?, kerning?: 1/1000 em|\"auto\"|\"optical\", baselineShift?: pt, hScale?: %, vScale?: %, rotation?: deg, fill?: colour|\"none\", stroke?: colour|\"none\", keepModel?: bool (fill and stroke, as paint.setFill), strokeWidth?: pt, strokeOptions?: {weight?, cap?, join?, miterLimit?, align?, dash?, dashOffset?, alignDashes?} (as stroke.set: the character stroke), underline?, strikethrough?, allCaps?: bool, smallCaps?: bool, position?: \"normal\"|\"superscript\"|\"subscript\" (sizes from Document Setup), features?: [\"dlig\", \"-liga\", …], charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\"|\"icfTop\"|\"icfBottom\", proportionalMetrics?: bool (full-width glyphs on the font's proportional widths: `palt` in horizontal type, `vpal` in vertical type), tateChuYoko?: bool (Character panel menu › Tate-chu-yoko, with Show East Asian Options: in vertical type those characters stand side by side across one em of the column; kept on the characters and ignored in horizontal type), noBreak?: bool (Character panel menu › No Break: these characters stay on one line, including across a space inside the run), charDirection?: \"default\"|\"leftToRight\"|\"rightToLeft\" (Character panel menu › Character Direction: the characters are set left to right or right to left inside their paragraph, as a Unicode bidi embedding; default: as the bidi algorithm sets them), digits?: \"default\"|\"arabic\"|\"hindi\"|\"farsi\" (Character panel menu › Digits: the numeral system every digit is drawn in, whatever was typed), language?: name|tag (Character panel › Language: one of Document Setup's languages by name or BCP 47 tag, case-insensitive, such as \"Turkish\" or \"tr\"; the characters are shaped in it, so the font uses that language's forms (OpenType `locl`); saved as the tag, English: USA, the default, as none; new type takes the document's language), fontAxes?: {\"wght\": 650, …} (Character panel › Variable Font: the setting of each variation axis named, in the axis's units and clamped to its range; null: back to the style's own setting; other axes keep theirs; tags the font has no axis for are left out; a font or style change clears them; text.fontList {family, style} lists the axes)} style a character range (size/leading/baselineShift are document points and hScale includes the object transform; runs are split at the range ends) → {id, runs, warning?: text (the font or style set isn't available: its type is drawn in the fallback font or the family's closest style)}", + "{id? (default: the one selected type object), start?: byte, end?: byte (default: all text), font?, style?, size?: pt, leading?: pt|\"auto\", tracking?, kerning?: 1/1000 em|\"auto\"|\"optical\", baselineShift?: pt, hScale?: %, vScale?: %, rotation?: deg, fill?: colour|\"none\", stroke?: colour|\"none\", keepModel?: bool (fill and stroke, as paint.setFill), strokeWidth?: pt, strokeOptions?: {weight?, cap?, join?, miterLimit?, align?, dash?, dashOffset?, alignDashes?} (as stroke.set: the character stroke), underline?, strikethrough?, allCaps?: bool, smallCaps?: bool, position?: \"normal\"|\"superscript\"|\"subscript\" (sizes from Document Setup), features?: [\"dlig\", \"-liga\", …], charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\"|\"icfTop\"|\"icfBottom\", proportionalMetrics?: bool (full-width glyphs on the font's proportional widths: `palt` in horizontal type, `vpal` in vertical type), tateChuYoko?: bool (Character panel menu › Tate-chu-yoko, with Show East Asian Options: in vertical type those characters stand side by side across one em of the column; kept on the characters and ignored in horizontal type), noBreak?: bool (Character panel menu › No Break: these characters stay on one line, including across a space inside the run), charDirection?: \"default\"|\"leftToRight\"|\"rightToLeft\" (Character panel menu › Character Direction: the characters are set left to right or right to left inside their paragraph, as a Unicode bidi embedding; default: as the bidi algorithm sets them), digits?: \"default\"|\"arabic\"|\"hindi\"|\"farsi\"|\"devanagari\"|\"bengali\"|\"gurmukhi\"|\"gujarati\"|\"odia\"|\"tamil\"|\"telugu\"|\"kannada\"|\"malayalam\" (Character panel menu › Digits: the numeral system every digit is drawn in, whatever was typed: Western, Arabic-Indic (hindi), Eastern Arabic-Indic (farsi) or an Indic script's own digits), language?: name|tag (Character panel › Language: one of Document Setup's languages by name or BCP 47 tag, case-insensitive, such as \"Turkish\" or \"tr\"; the characters are shaped in it, so the font uses that language's forms (OpenType `locl`); saved as the tag, English: USA, the default, as none; new type takes the document's language), fontAxes?: {\"wght\": 650, …} (Character panel › Variable Font: the setting of each variation axis named, in the axis's units and clamped to its range; null: back to the style's own setting; other axes keep theirs; tags the font has no axis for are left out; a font or style change clears them; text.fontList {family, style} lists the axes)} style a character range (size/leading/baselineShift are document points and hScale includes the object transform; runs are split at the range ends) → {id, runs, warning?: text (the font or style set isn't available: its type is drawn in the fallback font or the family's closest style)}", has_doc, set_range_style ), @@ -339,17 +339,18 @@ pub(crate) fn char_direction_param(p: &Value, cmd: &str) -> Result Result> { use vectorcraft_doc::Digits; let Some(v) = p.get("digits").filter(|v| !v.is_null()) else { return Ok(None) }; - Ok(Some(match v.as_str() { - Some("default") => Digits::Default, - Some("arabic") => Digits::Arabic, - Some("hindi") => Digits::Hindi, - Some("farsi") => Digits::Farsi, - _ => return Err(bad(cmd, "`digits` must be \"default\", \"arabic\", \"hindi\" or \"farsi\"")), - })) + let Some(d) = v.as_str().and_then(Digits::from_id) else { + let ids: Vec = Digits::ALL.iter().map(|d| format!("\"{}\"", d.id())).collect(); + return Err(bad(cmd, format!("`digits` must be one of {}", ids.join(", ")))); + }; + Ok(Some(d)) } /// `language: name|tag` (one of Document Setup's languages, case-insensitive) → the language to diff --git a/crates/engine/src/cmd/typecmd/area_tests.rs b/crates/engine/src/cmd/typecmd/area_tests.rs index 1f93079ac..1dae041d9 100644 --- a/crates/engine/src/cmd/typecmd/area_tests.rs +++ b/crates/engine/src/cmd/typecmd/area_tests.rs @@ -1268,6 +1268,53 @@ fn digits_are_a_character_attribute_saved_only_when_set() { assert_eq!(set, [("صفحة ".to_string(), Digits::Default), ("12".to_string(), Digits::Farsi)]); } +/// The Indic digits (#4942): each set by its id through text.setFormat and text.setRangeStyle, +/// saved under it, kept by save and reopen and by character styles; the text stays as typed. +#[test] +fn indic_digits_round_trip_through_the_commands_and_the_file() { + use vectorcraft_doc::Digits; + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap(); + let id = s.execute("text.create", &json!({"x": 10, "y": 50, "text": "page 12"})).unwrap()["id"].as_u64().unwrap(); + let runs = |s: &Session| match &s.doc().unwrap().doc.node(NodeId(id)).unwrap().kind { + NodeKind::Text(t) => t.runs.clone(), + _ => panic!("text"), + }; + s.execute("select.set", &json!({"ids": [id]})).unwrap(); + let indic = [ + Digits::Devanagari, + Digits::Bengali, + Digits::Gurmukhi, + Digits::Gujarati, + Digits::Odia, + Digits::Tamil, + Digits::Telugu, + Digits::Kannada, + Digits::Malayalam, + ]; + for d in indic { + s.execute("text.setFormat", &json!({"digits": d.id()})).unwrap(); + assert!(runs(&s).iter().all(|r| r.style.digits == d), "{d:?}"); + assert_eq!(serde_json::to_value(&runs(&s)[0].style).unwrap()["digits"], d.id()); + assert_eq!(runs(&s).iter().map(|r| r.text.as_str()).collect::(), "page 12"); + let back = vectorcraft_format::load(&vectorcraft_format::save(&s.doc().unwrap().doc, false)).unwrap(); + let NodeKind::Text(t) = &back.node(NodeId(id)).unwrap().kind else { panic!("text") }; + assert!(t.runs.iter().all(|r| r.style.digits == d), "{d:?} after reopening"); + } + s.execute("text.setRangeStyle", &json!({"id": id, "start": 5, "end": 7, "digits": "gujarati"})).unwrap(); + let set: Vec<_> = runs(&s).iter().map(|r| (r.text.clone(), r.style.digits)).collect(); + assert_eq!(set, [("page ".to_string(), Digits::Malayalam), ("12".to_string(), Digits::Gujarati)]); + assert_eq!(super::super::textstyles::char_attrs(&runs(&s)[1].style)["digits"], "gujarati"); + s.execute("charStyle.new", &json!({"name": "Tamil digits", "attrs": {"digits": "tamil"}})).unwrap(); + let listed = s.execute("charStyle.list", &json!({})).unwrap(); + let tamil = listed["styles"].as_array().unwrap().iter().find(|st| st["name"] == "Tamil digits").unwrap(); + assert_eq!(tamil["attrs"]["digits"], "tamil"); + for wrong in [json!("oriya"), json!("Devanagari"), json!(3)] { + let err = s.execute("text.setFormat", &json!({ "digits": wrong })).unwrap_err().to_string(); + assert!(err.contains("`digits` must be one of") && err.contains("\"malayalam\""), "{err}"); + } +} + /// Language (Character panel, #4946): a character attribute set by name or tag, saved as the tag /// only when set, kept by save and reopen and by character styles; new type takes the document's. #[test] diff --git a/crates/text/src/tests/bidi.rs b/crates/text/src/tests/bidi.rs index 1ec51d15a..a48d2c020 100644 --- a/crates/text/src/tests/bidi.rs +++ b/crates/text/src/tests/bidi.rs @@ -591,6 +591,47 @@ fn digits_draw_every_digit_in_the_styles_numeral_system() { assert_ne!(gids(&lay("3 ٣ ۳", Digits::Default)), gids(&lay("3 ٣ ۳", Digits::Hindi))); } +/// The Indic digits (#4942): typed Western digits are drawn with the glyphs of the script's own +/// digits, on the typed text's clusters. A font lacking them still lays the text out, in the +/// fallback face that has them or as missing glyphs, with the clusters as typed. Each script +/// needs an installed font with its digits; one without is skipped. +#[test] +fn indic_digits_draw_the_scripts_own_digits() { + use vectorcraft_doc::Digits; + let indic = [ + (Digits::Devanagari, '३'), + (Digits::Bengali, '৩'), + (Digits::Gurmukhi, '੩'), + (Digits::Gujarati, '૩'), + (Digits::Odia, '୩'), + (Digits::Tamil, '௩'), + (Digits::Telugu, '౩'), + (Digits::Kannada, '೩'), + (Digits::Malayalam, '൩'), + ]; + let clusters = |l: &TextLayout| l.glyphs.iter().map(|g| (g.byte, g.len)).collect::>(); + let gids = |l: &TextLayout| l.glyphs.iter().map(|g| (g.gid, g.font_id)).collect::>(); + // Bundled fonts only: the digits may have no face at all, and nothing panics. + let bundled = FontDb::with_font_dirs(vec![]); + for (d, _) in indic { + let lay = |s: &str, digits: Digits| layout(&bundled, &TextObject::point(Point::ZERO, s, CharStyle { digits, ..CharStyle::default() })); + assert_eq!(clusters(&lay("page 30", d)), clusters(&lay("page 30", Digits::Default)), "{d:?}"); + } + let db = FontDb::global(); + for (d, three) in indic { + let Some(face) = db.face_covering(three) else { + eprintln!("SKIPPED: indic_digits_draw_the_scripts_own_digits for {d:?}; no installed font covers {three}"); + continue; + }; + eprintln!("ASSERTIONS RUN: indic_digits_draw_the_scripts_own_digits for {d:?}; {} {}", face.family, face.style); + let st = |digits: Digits| CharStyle { digits, ..arabic_style(&face) }; + let lay = |s: &str, digits: Digits| layout(db, &TextObject::point(Point::ZERO, s, st(digits))); + let drawn = lay("3", d); + assert_eq!(gids(&drawn), gids(&lay(&three.to_string(), Digits::Default)), "{d:?}"); + assert_eq!(clusters(&drawn), clusters(&lay("3", Digits::Default)), "{d:?}"); + } +} + /// Right-to-left type on a path that overflows shows the start of its text, as a line of area /// type does, not whatever comes first in visual order. #[test] diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 177f87058..f47d5b7b6 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2401,6 +2401,15 @@ font-technology Variable Променливи Arabic Арабски Hindi Хинди Farsi Фарси +digits Devanagari Деванагари +digits Bengali Бенгалски +digits Gurmukhi Гурмукхи +digits Gujarati Гуджарати +digits Odia Ория +digits Tamil Тамилски +digits Telugu Телугу +digits Kannada Каннада +digits Malayalam Малаялам Proportional Oldstyle Пропорционални стари цифри Pucker & Bloat… Свиване и раздуване… Pucker Tool Инструмент „Свиване“ diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 1cdf807f0..584d4cf40 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -1421,6 +1421,15 @@ lighting Light Svjetlo Arabic Arapski Hindi Hindi Farsi Farsi +digits Devanagari Devanagari +digits Bengali Bengalski +digits Gurmukhi Gurmukhi +digits Gujarati Gudžarati +digits Odia Odija +digits Tamil Tamilski +digits Telugu Telugu +digits Kannada Kanada +digits Malayalam Malajalam Reset Panel Vrati panel na početno Roman Baseline Latinična osnovna linija Select a text object to edit its character attributes. Odaberite tekstualni objekat da uredite njegove atribute znakova. diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index c2c363ebd..02c98c1b5 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -1488,6 +1488,15 @@ Eyedropper: click the art to sample the key color Comptagotes: fes clic a l’art per mostrejar el color clau Eyedropper: click the art to sample the stop's colour Comptagotes: fes clic a l’art per mostrejar el color de la parada Farsi Persa +digits Devanagari Devanagari +digits Bengali Bengalí +digits Gurmukhi Gurmukhi +digits Gujarati Gujarati +digits Odia Oriya +digits Tamil Tàmil +digits Telugu Telugu +digits Kannada Kannada +digits Malayalam Malaiàlam Favorites Preferits Feather Ploma Feather… Ploma… diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 42f1c3977..2f21d51d8 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1445,6 +1445,15 @@ lighting Light Světlo Hindi Hindština digits Hindi Hindské Farsi Perské +digits Devanagari Dévanágarské +digits Bengali Bengálské +digits Gurmukhi Gurmukhské +digits Gujarati Gudžarátské +digits Odia Urijské +digits Tamil Tamilské +digits Telugu Telugské +digits Kannada Kannadské +digits Malayalam Malajálamské Reset Panel Obnovit panel Roman Baseline Latinková účaří Select a text object to edit its character attributes. Vyberte textový objekt, abyste mohli upravit atributy jeho znaků. diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 5b64d2758..df8c467e1 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1359,6 +1359,15 @@ lighting Light Lys Arabic Arabisk Hindi Hindi Farsi Farsi +digits Devanagari Devanagari +digits Bengali Bengali +digits Gurmukhi Gurmukhi +digits Gujarati Gujarati +digits Odia Oriya +digits Tamil Tamil +digits Telugu Telugu +digits Kannada Kannada +digits Malayalam Malayalam Reset Panel Nulstil panel Roman Baseline Romersk grundlinje Select a text object to edit its character attributes. Markér et tekstobjekt for at redigere dets tegnattributter. diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 32f8ee3f8..95439eea6 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2420,6 +2420,15 @@ mark-style Japanese Japanisch Arabic Arabisch Hindi Hindi Farsi Farsi +digits Devanagari Devanagari +digits Bengali Bengalisch +digits Gurmukhi Gurmukhi +digits Gujarati Gujarati +digits Odia Oriya +digits Tamil Tamil +digits Telugu Telugu +digits Kannada Kannada +digits Malayalam Malayalam Proportional Oldstyle Proportionale Mediävalziffern Pucker & Bloat… Zusammenziehen und aufblasen… Pucker Tool Zusammenziehen-Werkzeug diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 2ce1d6b85..91f441ada 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -3059,6 +3059,15 @@ theme Appearance Εμφάνιση Arabic Αραβικά Hindi Χίντι Farsi Περσικά +digits Devanagari Ντεβαναγκάρι +digits Bengali Βεγγαλικά +digits Gurmukhi Γκουρμούκι +digits Gujarati Γκουγιαράτι +digits Odia Όντια +digits Tamil Ταμιλικά +digits Telugu Τελούγκου +digits Kannada Κανάντα +digits Malayalam Μαλαγιάλαμ Proportional Oldstyle Αναλογικοί παλαιοτυπικοί αριθμοί Pucker & Bloat… Συρρίκνωση και διόγκωση… Pucker Tool Συρρίκνωση (Pucker Tool) diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 277af453f..8fe2d8401 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2458,6 +2458,15 @@ font-technology Variable Variables Arabic Árabes Hindi Hindi Farsi Farsi +digits Devanagari Devanagari +digits Bengali Bengalí +digits Gurmukhi Gurmukhi +digits Gujarati Guyaratí +digits Odia Oriya +digits Tamil Tamil +digits Telugu Telugu +digits Kannada Canarés +digits Malayalam Malayálam Proportional Oldstyle Estilo antiguo proporcional Pucker & Bloat… Fruncir y engordar… Pucker Tool Herramienta Fruncir diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 5ff14b7d9..5fbec069a 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1419,6 +1419,15 @@ lighting Light Valo Arabic Arabia Hindi Hindi Farsi Farsi +digits Devanagari Devanagari +digits Bengali Bengali +digits Gurmukhi Gurmukhi +digits Gujarati Gudžarati +digits Odia Orija +digits Tamil Tamili +digits Telugu Telugu +digits Kannada Kannada +digits Malayalam Malajalam Reset Panel Palauta paneeli Roman Baseline Latinalainen perusviiva Select a text object to edit its character attributes. Valitse tekstiobjekti, jonka merkkimääritteitä haluat muokata. diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 76717dd00..d6ec59b6c 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2456,6 +2456,15 @@ font-technology Variable Variables Arabic Arabes Hindi Hindi Farsi Farsi +digits Devanagari Dévanagari +digits Bengali Bengali +digits Gurmukhi Gurmukhi +digits Gujarati Goudjarati +digits Odia Odia +digits Tamil Tamoul +digits Telugu Télougou +digits Kannada Kannada +digits Malayalam Malayalam Proportional Oldstyle Elzévirien proportionnel Pucker & Bloat… Contraction et dilatation… Pucker Tool Outil Contraction diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index c391405c4..fe56efa82 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1375,6 +1375,15 @@ font-technology Variable Változó Eyedropper: click the art to sample the stop's colour Pipetta: kattints a grafikára a pont színének mintavételéhez Eyedropper: click the art to sample the key color Pipetta: kattints a grafikára a kulcsszín mintavételéhez Farsi Perzsa +digits Devanagari Dévanágari +digits Bengali Bengáli +digits Gurmukhi Gurmukhi +digits Gujarati Gudzsaráti +digits Odia Odia +digits Tamil Tamil +digits Telugu Telugu +digits Kannada Kannada +digits Malayalam Malajálam Favorites Kedvencek Feather Tollazat Feather… Elmosott szél… diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index a49fcb3e3..a37491b99 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1359,6 +1359,15 @@ lighting Light Cahaya Arabic Arab Hindi Hindi Farsi Farsi +digits Devanagari Dewanagari +digits Bengali Bengali +digits Gurmukhi Gurmukhi +digits Gujarati Gujarati +digits Odia Oriya +digits Tamil Tamil +digits Telugu Telugu +digits Kannada Kannada +digits Malayalam Malayalam Reset Panel Setel ulang panel Roman Baseline Baseline Romawi Select a text object to edit its character attributes. Pilih objek teks untuk mengedit atribut karakternya. diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 0816ef0c7..dbf755d31 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2489,6 +2489,15 @@ mark-style Japanese Giapponese Arabic Arabe Hindi Hindi Farsi Farsi +digits Devanagari Devanagari +digits Bengali Bengalese +digits Gurmukhi Gurmukhi +digits Gujarati Gujarati +digits Odia Odia +digits Tamil Tamil +digits Telugu Telugu +digits Kannada Kannada +digits Malayalam Malayalam Proportional Oldstyle Proporzionali stile antico Pucker & Bloat… Contrai e gonfia… Pucker Tool Strumento Contrai diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 795b48730..54e53749b 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -49,6 +49,15 @@ font-technology Variable バリアブル Hindi ヒンディー語 digits Hindi ヒンディー数字 Farsi ペルシャ数字 +digits Devanagari デーヴァナーガリー数字 +digits Bengali ベンガル数字 +digits Gurmukhi グルムキー数字 +digits Gujarati グジャラート数字 +digits Odia オディア数字 +digits Tamil タミル数字 +digits Telugu テルグ数字 +digits Kannada カンナダ数字 +digits Malayalam マラヤーラム数字 Favorites お気に入り Flip 反転 Japanese 日本語 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 2eec1b66f..a5f844aba 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -1276,6 +1276,15 @@ lighting Light სინათლე Arabic არაბული Hindi ჰინდი Farsi სპარსული +digits Devanagari დევანაგარი +digits Bengali ბენგალური +digits Gurmukhi გურმუხი +digits Gujarati გუჯარათი +digits Odia ორია +digits Tamil ტამილური +digits Telugu ტელუგუ +digits Kannada კანადა +digits Malayalam მალაიალამური Reset Panel პანელის ჩამოყრა Roman Baseline ლათინური საბაზისო ხაზი Select a text object to edit its character attributes. სიმბოლოების ატრიბუტების რედაქტირებისთვის მონიშნეთ ტექსტური ობიექტი. diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 05f3a02a1..5dd77eaba 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -42,6 +42,15 @@ font-technology Variable 가변 Arabic 아라비아어 Hindi 힌디어 Farsi 페르시아어 +digits Devanagari 데바나가리 +digits Bengali 벵골어 +digits Gurmukhi 구르무키 +digits Gujarati 구자라트어 +digits Odia 오리야어 +digits Tamil 타밀어 +digits Telugu 텔루구어 +digits Kannada 칸나다어 +digits Malayalam 말라얄람어 Favorites 즐겨찾기 Flip 뒤집기 Japanese 일본어 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 082877247..4c60e7358 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1373,6 +1373,15 @@ lighting Light Licht Arabic Arabisch Hindi Hindi Farsi Farsi +digits Devanagari Devanagari +digits Bengali Bengaals +digits Gurmukhi Gurmukhi +digits Gujarati Gujarati +digits Odia Odia +digits Tamil Tamil +digits Telugu Telugu +digits Kannada Kannada +digits Malayalam Malayalam Reset Panel Paneel herstellen Roman Baseline Romeinse basislijn Select a text object to edit its character attributes. Selecteer een tekstobject om de tekenkenmerken ervan te bewerken. diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 38f394971..593f7d2ab 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1358,6 +1358,15 @@ lighting Light Światło Arabic Arabskie Hindi Hindi Farsi Perskie +digits Devanagari Dewanagari +digits Bengali Bengalskie +digits Gurmukhi Gurmukhi +digits Gujarati Gudżarati +digits Odia Orija +digits Tamil Tamilskie +digits Telugu Telugu +digits Kannada Kannada +digits Malayalam Malajalam Reset Panel Resetuj panel Roman Baseline Linia bazowa łacińska Select a text object to edit its character attributes. Zaznacz obiekt tekstowy, aby edytować atrybuty znaków. diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index ed4568edc..e6e4599be 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2027,6 +2027,15 @@ Arabic Arábicos Hindi Hindi Farsi Farsi +digits Devanagari Devanágari +digits Bengali Bengali +digits Gurmukhi Gurmukhi +digits Gujarati Guzerate +digits Odia Oriá +digits Tamil Tâmil +digits Telugu Télugo +digits Kannada Canarim +digits Malayalam Malaiala Pucker & Bloat… Encolher e Inchar… Pucker Tool Ferramenta Encolher Punctuation Space Espaço de Pontuação diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index a829cbf0d..fa9e68b5d 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2485,6 +2485,15 @@ mark-style Japanese Японский Arabic Арабские Hindi Хинди Farsi Фарси +digits Devanagari Деванагари +digits Bengali Бенгальские +digits Gurmukhi Гурмукхи +digits Gujarati Гуджарати +digits Odia Ория +digits Tamil Тамильские +digits Telugu Телугу +digits Kannada Каннада +digits Malayalam Малаялам Proportional Oldstyle Пропорциональные старого стиля Pucker & Bloat… Сморщивание и раздувание… Pucker Tool Инструмент «Сморщивание» diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 182dee7b5..23aa4028b 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2401,6 +2401,15 @@ font-technology Variable Variabla Arabic Arabiska Hindi Hindi Farsi Farsi +digits Devanagari Devanagari +digits Bengali Bengali +digits Gurmukhi Gurmukhi +digits Gujarati Gujarati +digits Odia Oriya +digits Tamil Tamil +digits Telugu Telugu +digits Kannada Kannada +digits Malayalam Malayalam Proportional Oldstyle Proportionella medievalsiffror Pucker & Bloat… Dra ihop och svullna… Pucker Tool Verktyget Dra ihop diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index f1d4bc04f..c3e3345a0 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -1985,3 +1985,19 @@ fn polish_plurals_have_three_forms() { } assert!(count >= 13, "Polish plural rows missing"); } + +/// Every catalog names each numeral system of Character panel menu › Digits, in the `digits` +/// context (where the language's name would read wrong) or with its plain row (#4942). +#[test] +fn every_catalog_names_the_digits_menus_numeral_systems() { + let mut missing = Vec::new(); + for l in LANGUAGES.iter().filter(|l| !l.source.is_empty()) { + let cat = l.catalog(); + for (label, _) in crate::panels::character::DIGITS { + if cat.contextual("digits", label).is_none() && cat.plain(label).is_none() { + missing.push(format!("{}: {label}", l.code)); + } + } + } + assert!(missing.is_empty(), "Digits menu labels without a row:\n{}", missing.join("\n")); +} diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index ae2a8b933..c9a3175cb 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2280,6 +2280,15 @@ shape Open Açık Arabic Arapça Hindi Hintçe Farsi Farsça +digits Devanagari Devanagari +digits Bengali Bengalce +digits Gurmukhi Gurmukhi +digits Gujarati Gücerat +digits Odia Oriya +digits Tamil Tamilce +digits Telugu Telugu +digits Kannada Kannada +digits Malayalam Malayalam Proportional Oldstyle Orantılı Eski Stil Pucker & Bloat… Büzüşme ve Şişkinlik… Pucker Tool Büzme Aracı diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index f96a15dc6..39303f0a2 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2481,6 +2481,15 @@ mark-style Japanese Японський Arabic Арабські Hindi Гінді Farsi Фарсі +digits Devanagari Деванагарі +digits Bengali Бенгальські +digits Gurmukhi Гурмукхі +digits Gujarati Гуджараті +digits Odia Одія +digits Tamil Тамільські +digits Telugu Телугу +digits Kannada Каннада +digits Malayalam Малаялам Proportional Oldstyle Пропорційні цифри старого стилю Pucker & Bloat… Зморщування та роздування … Pucker Tool Інструмент «Зморщування» diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 559dbe099..bc4d856aa 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1252,6 +1252,15 @@ font-technology Variable Biến thiên Eyedropper: click the art to sample the stop's colour Ống hút màu: bấm vào tác phẩm để lấy mẫu màu điểm dừng Eyedropper: click the art to sample the key color Ống hút màu: bấm vào tác phẩm để lấy mẫu màu chủ Farsi Farsi +digits Devanagari Devanagari +digits Bengali Bengali +digits Gurmukhi Gurmukhi +digits Gujarati Gujarati +digits Odia Odia +digits Tamil Tamil +digits Telugu Telugu +digits Kannada Kannada +digits Malayalam Malayalam Favorites Yêu thích Feather… Làm mờ dần… Feet Feet diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index cc619b472..00839e4ef 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2361,6 +2361,15 @@ font-script Japanese 汉字 Hindi 印地语 digits Hindi 印地数字 Farsi 波斯数字 +digits Devanagari 天城数字 +digits Bengali 孟加拉数字 +digits Gurmukhi 古木基数字 +digits Gujarati 古吉拉特数字 +digits Odia 奥里亚数字 +digits Tamil 泰米尔数字 +digits Telugu 泰卢固数字 +digits Kannada 卡纳达数字 +digits Malayalam 马拉雅拉姆数字 Proportional Oldstyle 比例旧式数字 Pucker & Bloat… 缩拢与膨胀… Pucker Tool 收缩工具 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index d2134afdc..1385a52c7 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -3302,6 +3302,15 @@ font-script Japanese 漢字 Hindi 印地文 digits Hindi 印地數字 Farsi 波斯數字 +digits Devanagari 天城數字 +digits Bengali 孟加拉數字 +digits Gurmukhi 古木基數字 +digits Gujarati 古吉拉特數字 +digits Odia 歐利亞數字 +digits Tamil 坦米爾數字 +digits Telugu 泰盧固數字 +digits Kannada 坎那達數字 +digits Malayalam 馬來亞拉姆數字 Proportional Oldstyle 比例舊式數字 Pucker & Bloat… 縮攏與膨脹… Pucker Tool 縮攏工具 diff --git a/crates/ui-egui/src/panels/character.rs b/crates/ui-egui/src/panels/character.rs index ebd80f1ed..bf5788d98 100644 --- a/crates/ui-egui/src/panels/character.rs +++ b/crates/ui-egui/src/panels/character.rs @@ -1016,6 +1016,44 @@ fn char_direction_items(app: &mut VectorcraftApp, ui: &mut Ui) { } } +/// The numeral systems Character panel menu › Digits lists after Default, with their English +/// labels, translated in the `digits` context (Hindi, Bengali, Tamil… are also languages): the +/// Arabic-script ones, then the Indic scripts' own digits from Devanagari on. +pub(crate) const DIGITS: [(&str, vectorcraft_doc::Digits); 12] = { + use vectorcraft_doc::Digits; + [ + ("Arabic", Digits::Arabic), + ("Hindi", Digits::Hindi), + ("Farsi", Digits::Farsi), + ("Devanagari", Digits::Devanagari), + ("Bengali", Digits::Bengali), + ("Gurmukhi", Digits::Gurmukhi), + ("Gujarati", Digits::Gujarati), + ("Odia", Digits::Odia), + ("Tamil", Digits::Tamil), + ("Telugu", Digits::Telugu), + ("Kannada", Digits::Kannada), + ("Malayalam", Digits::Malayalam), + ] +}; + +/// Character panel menu › Digits; `digits`: the selected characters' (`None` when they differ). +fn digits_items(app: &mut VectorcraftApp, ui: &mut Ui, digits: Option) { + use vectorcraft_doc::Digits; + let mut item = |ui: &mut Ui, label: &str, d: Digits| { + if menu_item(ui, label, true, digits == Some(d)) { + format(app, json!({"digits": d.id()})); + } + }; + item(ui, tl!("Default"), Digits::Default); + for (label, d) in DIGITS { + if d == Digits::Devanagari { + ui.separator(); + } + item(ui, crate::i18n::tr_ctx(crate::i18n::current(), "digits", label), d); + } +} + pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { let st = text_style(app); let has = st.is_some(); @@ -1089,21 +1127,7 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { if app.session.prefs.show_indic_options { let digits = s.as_ref().map(|s| s.digits); ui.add_enabled_ui(has, |ui| { - ui.menu_button(format!(" {}", tl!("Digits")), |ui| { - use vectorcraft_doc::Digits; - // The numeral systems, in the digits context: Hindi is also a language. - let digits_label = |s| crate::i18n::tr_ctx(crate::i18n::current(), "digits", s); - for (label, d, key) in [ - (tl!("Default"), Digits::Default, "default"), - (digits_label("Arabic"), Digits::Arabic, "arabic"), - (digits_label("Hindi"), Digits::Hindi, "hindi"), - (digits_label("Farsi"), Digits::Farsi, "farsi"), - ] { - if menu_item(ui, label, true, digits == Some(d)) { - format(app, json!({"digits": key})); - } - } - }); + ui.menu_button(format!(" {}", tl!("Digits")), |ui| digits_items(app, ui, digits)); }); ui.add_enabled_ui(has, |ui| { ui.menu_button(format!(" {}", tl!("Character Direction")), |ui| char_direction_items(app, ui)); diff --git a/crates/ui-egui/src/panels/character/tests.rs b/crates/ui-egui/src/panels/character/tests.rs index 61ec42a21..8f02228b5 100644 --- a/crates/ui-egui/src/panels/character/tests.rs +++ b/crates/ui-egui/src/panels/character/tests.rs @@ -540,6 +540,19 @@ fn digits_show_with_the_indic_options() { assert!(shown(&mut app)); } +/// The Digits submenu lists Default, the Arabic-script numeral systems and the Indic scripts' +/// digits (#4942), and checks the selected characters' system. +#[test] +fn digits_submenu_lists_the_indic_scripts_and_checks_the_selection() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + let text = crate::tests_labels::painted_text(&mut app, |app, ui| digits_items(app, ui, Some(vectorcraft_doc::Digits::Tamil))); + let items: Vec<_> = text.lines().map(|l| l.trim().trim_start_matches("✓ ")).collect(); + let want = + ["Default", "Arabic", "Hindi", "Farsi", "Devanagari", "Bengali", "Gurmukhi", "Gujarati", "Odia", "Tamil", "Telugu", "Kannada", "Malayalam"]; + assert!(want.iter().all(|w| items.contains(w)), "{items:?}"); + assert_eq!(text.lines().filter_map(|l| l.strip_prefix("✓ ")).collect::>(), ["Tamil"]); +} + /// Character Direction is in the panel menu with the Middle Eastern (Indic) options, beside /// Digits. Its submenu checks the direction the characters the panel acts on share: the /// selected type's, or the range the Type tool has selected. It checks no item when they differ. diff --git a/docs/mcp.md b/docs/mcp.md index ee1f9a923..4c893cff8 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -5022,11 +5022,14 @@ command is one undo step. ## Digits -`text.setFormat {digits: "default"|"arabic"|"hindi"|"farsi"}` (the selected type, or `ids`) and `text.setRangeStyle {id, -start, end, digits}` (a range) set the numeral system the characters' digits are drawn in, as the Character panel menu's -Digits does with the Middle Eastern (Indic) options shown: `arabic` draws every digit as the Western 0–9, `hindi` as the -Arabic-Indic ٠–٩ (U+0660–U+0669) and `farsi` as the Eastern Arabic-Indic ۰–۹ (U+06F0–U+06F9), whichever of the three sets -was typed; `default` leaves each digit as typed. The text keeps the digits that were typed (`text.getRange`, search, +`text.setFormat {digits: "default"|"arabic"|"hindi"|"farsi"|"devanagari"|"bengali"|"gurmukhi"|"gujarati"|"odia"|"tamil"|"telugu"|"kannada"|"malayalam"}` +(the selected type, or `ids`) and `text.setRangeStyle {id, start, end, digits}` (a range) set the numeral system the +characters' digits are drawn in, as the Character panel menu's Digits does with the Middle Eastern (Indic) options shown: +`arabic` draws every digit as the Western 0–9, `hindi` as the Arabic-Indic ٠–٩ (U+0660–U+0669) and `farsi` as the Eastern +Arabic-Indic ۰–۹ (U+06F0–U+06F9), whichever of the three sets was typed; the Indic ones draw them as their script's own +digits (#4942): `devanagari` ०–९ (U+0966–U+096F), `bengali` ০–৯ (U+09E6), `gurmukhi` ੦–੯ (U+0A66), `gujarati` ૦–૯ +(U+0AE6), `odia` ୦–୯ (U+0B66), `tamil` ௦–௯ (U+0BE6), `telugu` ౦–౯ (U+0C66), `kannada` ೦–೯ (U+0CE6) and `malayalam` ൦–൯ +(U+0D66); `default` leaves each digit as typed. Any other value is refused. The text keeps the digits that were typed (`text.getRange`, search, the caret and selection are unchanged); only the glyphs change, as with All Caps, and SVG export writes the digits as drawn. A digit the font lacks comes from a fallback font. Default and saved only when set. One undo step. diff --git a/docs/type-parity.md b/docs/type-parity.md index 3d76581f9..f6c4a2c7b 100644 --- a/docs/type-parity.md +++ b/docs/type-parity.md @@ -1,6 +1,6 @@ # Type parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (Language per character: text is shaped in its language, Character panel › Language, Indian languages; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (Digits: the native digits of the Indic scripts, #4942; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x) Type is Illustrator's largest single area by use (weight 13 of 105 across "Type core" and "Type advanced" in [target-app-parity.md](target-app-parity.md)). This checklist lists the features one by one. Status is from the @@ -53,7 +53,7 @@ against Illustrator's public documentation; **estimated** unless noted. | Snap to Glyph | done | View › Snap to Glyph (`view.snapToGlyph`): with Smart Guides on, new point type and drawing tools snap to live-text baselines and glyph bounds (`Targets::for_drawing`, `tools::text_tests::snap_to_glyph_lines_up_new_point_type_with_a_baseline`). Character panel menu › Show Snap to Glyph Options: Baseline, x-height, Glyph Bounds, Proximity Guides, Angular Guides and Anchor Point each turn their guides on or off (`view.snapToGlyph.options`, all on by default; #4232) | | Thai, Lao, Khmer, Myanmar: shaping (marks, reordering), lines broken between words, word-by-word caret and double-click | done | words found by ICU4X's line segmenter: dictionaries on the desktop, LSTM models on the web | | Middle Eastern: bidirectional layout, Paragraph Direction | done | | -| Middle Eastern: digit types (Character panel menu › Digits: Default, Arabic, Hindi, Farsi) | done | `text.setFormat` / `text.setRangeStyle {digits}`; the text keeps the typed digits, the glyphs and SVG export follow the style | +| Middle Eastern and Indic: digit types (Character panel menu › Digits: Default, Arabic, Hindi, Farsi; Devanagari, Bengali, Gurmukhi, Gujarati, Odia, Tamil, Telugu, Kannada, Malayalam) | done | `text.setFormat` / `text.setRangeStyle {digits}` (`"devanagari"`, `"bengali"`, `"gurmukhi"`, `"gujarati"`, `"odia"`, `"tamil"`, `"telugu"`, `"kannada"`, `"malayalam"` added, #4942); the text keeps the typed digits, the glyphs and SVG export follow the style; carried by character styles. Not yet: `.ai` export of the setting; Indic digits typed as such stay as typed under another system. Evidence: `text::tests::indic_digits_map_to_their_scripts_block`, `tests::bidi::indic_digits_draw_the_scripts_own_digits`, `typecmd::area_tests::indic_digits_round_trip_through_the_commands_and_the_file` | | Middle Eastern: Character Direction, kashidas, Middle Eastern composers, split caret | missing | | | CJK: input methods (marked text, candidate window) | done | | | CJK: kinsoku (Hard, Soft, None), Line-end Punctuation Half Width, tate-chu-yoko (auto) | done / partial | custom kinsoku sets, the Mojikumi Settings dialog and the other mojikumi sets missing | @@ -72,6 +72,7 @@ Character Styles menu New/Redefine and footer New capture the first selected cha | Date | Change | Summary | |---|---|---| +| 2026-11-01 | minor | Digits (#4942): the native digits of Devanagari, Bengali, Gurmukhi, Gujarati, Odia, Tamil, Telugu, Kannada and Malayalam in the Character panel menu › Digits and `{digits}`; scores unchanged | | 2026-11-01 | minor | Language per character (#4946): Character panel › Language, `text.setFormat` / `text.setRangeStyle {language}`, character styles; text is shaped in its language (`locl`, #4940); new type takes the document's; Indian languages in the list (#4943); scores unchanged | | 2026-10-31 | minor | PDF import finds an installed font whose PDF name holds hyphens (`Noto-Sans-Bold` is Noto Sans, Bold: the longest part before a hyphen that names an installed family) and a composite font by its CIDFont's name rather than its base name, which ends in the CMap's (`Name-Identity-H`, [ISO 32000-1 §9.7.6.1](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf)); a missing font is split only before a style part (`Helvetica-BoldOblique`), so it is reported as `MS PGothic`, not `MS`; scores unchanged | | 2026-10-31 | minor | Highlight Alternate Glyphs: an optional feature (such as `salt`) that changes a lam-alef ligature is highlighted again, only the required ligature staying clear, and a lam-alef with any combining mark (Arabic Extended-A/B/C included) counts as the ligature (#394); scores unchanged |