From 2bdaaa89812745276c15eafd0d8ba2534027a595 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sun, 1 Nov 2026 00:52:44 +0700 Subject: [PATCH] Fix #4946: a Language setting per text, with Indian languages (#4943) Character panel > Language sets the selected characters' language; text.setFormat and text.setRangeStyle take `language` by name or tag (unknown: an error), and character styles carry it. Document Setup's language list gains Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu, with catalog rows; the Digits menu's numeral names move to a digits context, as Hindi is now also a language name. --- crates/doc/src/setup.rs | 17 +++++-- crates/engine/src/cmd/panelcmds.rs | 7 ++- crates/engine/src/cmd/textedit.rs | 18 +++++++- crates/engine/src/cmd/textstyles.rs | 10 ++++- crates/engine/src/cmd/typecmd/area_tests.rs | 49 +++++++++++++++++++++ crates/ui-egui/src/i18n/bg.tsv | 10 +++++ crates/ui-egui/src/i18n/bs.tsv | 10 +++++ crates/ui-egui/src/i18n/ca.tsv | 10 +++++ crates/ui-egui/src/i18n/cs.tsv | 13 +++++- crates/ui-egui/src/i18n/da.tsv | 10 +++++ crates/ui-egui/src/i18n/de.tsv | 10 +++++ crates/ui-egui/src/i18n/el.tsv | 10 +++++ crates/ui-egui/src/i18n/es.tsv | 10 +++++ crates/ui-egui/src/i18n/fi.tsv | 10 +++++ crates/ui-egui/src/i18n/fr.tsv | 10 +++++ crates/ui-egui/src/i18n/hu.tsv | 10 +++++ crates/ui-egui/src/i18n/id.tsv | 10 +++++ crates/ui-egui/src/i18n/it.tsv | 10 +++++ crates/ui-egui/src/i18n/ja.tsv | 13 +++++- crates/ui-egui/src/i18n/ka.tsv | 10 +++++ crates/ui-egui/src/i18n/ko.tsv | 10 +++++ crates/ui-egui/src/i18n/nl.tsv | 10 +++++ crates/ui-egui/src/i18n/pl.tsv | 10 +++++ crates/ui-egui/src/i18n/pt-br.tsv | 10 +++++ crates/ui-egui/src/i18n/ru.tsv | 10 +++++ crates/ui-egui/src/i18n/sv.tsv | 10 +++++ crates/ui-egui/src/i18n/tr.tsv | 10 +++++ crates/ui-egui/src/i18n/uk.tsv | 10 +++++ crates/ui-egui/src/i18n/vi.tsv | 13 +++++- crates/ui-egui/src/i18n/zh-hans.tsv | 13 +++++- crates/ui-egui/src/i18n/zh-hant.tsv | 13 +++++- crates/ui-egui/src/panels/character.rs | 22 +++++++-- docs/mcp.md | 15 +++++++ docs/target-app-parity.md | 3 +- docs/type-parity.md | 4 +- docs/ui-parity.md | 3 +- 36 files changed, 405 insertions(+), 18 deletions(-) diff --git a/crates/doc/src/setup.rs b/crates/doc/src/setup.rs index bb7c497c9..62276ece9 100644 --- a/crates/doc/src/setup.rs +++ b/crates/doc/src/setup.rs @@ -167,6 +167,17 @@ pub const LANGUAGES: &[(&str, &str, Quotes)] = &[ ("Turkish", "tr", Quotes { double: ['“', '”'], single: ['‘', '’'] }), ("Japanese", "ja", Quotes { double: ['「', '」'], single: ['『', '』'] }), ("Chinese", "zh", Quotes { double: ['“', '”'], single: ['‘', '’'] }), + ("Hindi", "hi", Quotes::ENGLISH), + ("Marathi", "mr", Quotes::ENGLISH), + ("Gujarati", "gu", Quotes::ENGLISH), + ("Bengali", "bn", Quotes::ENGLISH), + ("Punjabi", "pa", Quotes::ENGLISH), + ("Tamil", "ta", Quotes::ENGLISH), + ("Telugu", "te", Quotes::ENGLISH), + ("Kannada", "kn", Quotes::ENGLISH), + ("Malayalam", "ml", Quotes::ENGLISH), + ("Odia", "or", Quotes::ENGLISH), + ("Urdu", "ur", Quotes::ENGLISH), ]; /// The quotes of a [`LANGUAGES`] entry (case-insensitive). @@ -354,13 +365,13 @@ mod tests { #[test] fn char_languages_are_stored_by_tag() { assert_eq!(char_language("Turkish"), Ok(Some("tr"))); - assert_eq!(char_language("TR"), Ok(Some("tr"))); + assert_eq!(char_language("MR"), Ok(Some("mr"))); assert_eq!(char_language("english: usa"), Ok(None)); assert_eq!(char_language("en-US"), Ok(None)); assert!(char_language("Klingon").is_err() && char_language("").is_err()); assert_eq!( - (char_language_name(None), char_language_name(Some("de")), char_language_name(Some("x-unknown"))), - (Some("English: USA"), Some("German"), None) + (char_language_name(None), char_language_name(Some("gu")), char_language_name(Some("x-unknown"))), + (Some("English: USA"), Some("Gujarati"), None) ); // Every entry has a tag of its own, and a name of its own. for (i, (n, t, _)) in LANGUAGES.iter().enumerate() { diff --git a/crates/engine/src/cmd/panelcmds.rs b/crates/engine/src/cmd/panelcmds.rs index 4c52da717..081678341 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), 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\" (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)", has_doc, set_format ), @@ -372,6 +372,7 @@ fn set_format(s: &mut Session, p: &Value) -> Result { "tateChuYoko", "noBreak", "digits", + "language", "fontAxes", "burasagari", "romanHangingPunctuation", @@ -393,6 +394,7 @@ fn set_format(s: &mut Session, p: &Value) -> Result { let char_align = super::textedit::char_align_param(p, C)?; let char_direction = super::textedit::char_direction_param(p, C)?; let digits = super::textedit::digits_param(p, C)?; + let language = super::textedit::language_param(p, C)?; let font_axes = super::textedit::font_axes_param(p, C)?; let mojikumi = match p.get("mojikumi") { None => None, @@ -524,6 +526,9 @@ fn set_format(s: &mut Session, p: &Value) -> Result { if let Some(v) = digits { st.digits = v; } + if let Some(v) = &language { + st.language.clone_from(v); + } if let Some(v) = flag("proportionalMetrics") { st.proportional_metrics = v; } diff --git a/crates/engine/src/cmd/textedit.rs b/crates/engine/src/cmd/textedit.rs index 8d393a58e..958c5f10d 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), 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\" (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)}", has_doc, set_range_style ), @@ -321,6 +321,8 @@ pub(crate) struct CharChange { tate_chu_yoko: Option, no_break: Option, digits: Option, + /// The language to set: a tag, or `None` for the default. + language: Option>, font_axes: Option, } @@ -350,6 +352,15 @@ pub(crate) fn digits_param(p: &Value, cmd: &str) -> Result Result>> { + let Some(v) = p.get("language").filter(|v| !v.is_null()) else { return Ok(None) }; + let l = v.as_str().ok_or_else(|| bad(cmd, "`language` must be a language name or tag, such as \"Turkish\" or \"tr\""))?; + let tag = vectorcraft_doc::setup::char_language(l).map_err(|e| bad(cmd, e))?; + Ok(Some(tag.map(str::to_string))) +} + /// Variable Font settings to change: by axis tag, a value in the axis's units, or `None` to go /// back to the style's own setting of that axis. pub(crate) type AxisChange = std::collections::BTreeMap>; @@ -507,6 +518,7 @@ impl CharChange { tate_chu_yoko: flag("tateChuYoko"), no_break: flag("noBreak"), digits: digits_param(p, cmd)?, + language: language_param(p, cmd)?, font_axes: font_axes_param(p, cmd)?, }; if c.size.is_some_and(|v| v <= 0.0) { @@ -552,6 +564,7 @@ impl CharChange { && self.tate_chu_yoko.is_none() && self.no_break.is_none() && self.digits.is_none() + && self.language.is_none() && self.font_axes.is_none() } @@ -655,6 +668,9 @@ impl CharChange { if let Some(v) = self.digits { st.digits = v; } + if let Some(v) = &self.language { + st.language.clone_from(v); + } } } diff --git a/crates/engine/src/cmd/textstyles.rs b/crates/engine/src/cmd/textstyles.rs index 30ccbfc7b..e70af82a0 100644 --- a/crates/engine/src/cmd/textstyles.rs +++ b/crates/engine/src/cmd/textstyles.rs @@ -177,6 +177,7 @@ pub fn char_attrs(v: &CharStyle) -> Map { ("tateChuYoko", json!(v.tate_chu_yoko)), ("noBreak", json!(v.no_break)), ("digits", json!(v.digits)), + ("language", json!(v.language)), ] { m.insert(k.into(), x); } @@ -480,7 +481,14 @@ fn attrs_param(p: &Value, kind: Kind, verb: &str) -> Result char_attrs(&with_attrs(&CharStyle::default(), m, &c)?), + Kind::Char => { + let mut st = with_attrs(&CharStyle::default(), m, &c)?; + // A language of the list, stored as its tag (as text.setFormat takes it). + if let Some(l) = &st.language { + st.language = vectorcraft_doc::setup::char_language(l).map_err(|e| bad(&c, e))?.map(str::to_string); + } + char_attrs(&st) + } Kind::Para => para_attrs(&with_attrs(&ParaStyle::default(), m, &c)?), }; Ok(Some(m.keys().filter_map(|k| full.get(k).map(|v| (k.clone(), v.clone()))).collect())) diff --git a/crates/engine/src/cmd/typecmd/area_tests.rs b/crates/engine/src/cmd/typecmd/area_tests.rs index 4bde140a7..1f93079ac 100644 --- a/crates/engine/src/cmd/typecmd/area_tests.rs +++ b/crates/engine/src/cmd/typecmd/area_tests.rs @@ -1268,6 +1268,55 @@ fn digits_are_a_character_attribute_saved_only_when_set() { assert_eq!(set, [("صفحة ".to_string(), Digits::Default), ("12".to_string(), Digits::Farsi)]); } +/// 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] +fn language_is_a_character_attribute_saved_only_when_set() { + 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": "fi i fikir"})).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"), + }; + let set = |s: &Session| runs(s).iter().map(|r| (r.text.clone(), r.style.language.clone())).collect::>(); + assert_eq!(runs(&s)[0].style.language, None); + assert!(serde_json::to_value(&runs(&s)[0].style).unwrap().get("language").is_none()); + s.execute("select.set", &json!({"ids": [id]})).unwrap(); + s.execute("text.setFormat", &json!({"language": "Turkish"})).unwrap(); + assert_eq!(set(&s), [("fi i fikir".to_string(), Some("tr".to_string()))]); + assert_eq!(serde_json::to_value(&runs(&s)[0].style).unwrap()["language"], "tr"); + for wrong in [json!("Klingon"), json!(5)] { + let err = s.execute("text.setFormat", &json!({ "language": wrong })).unwrap_err().to_string(); + assert!(err.contains("language"), "{err}"); + } + // Characters selected with the Type tool: only the range takes it; the default is none. + s.execute("text.setRangeStyle", &json!({"id": id, "start": 0, "end": 2, "language": "en-US"})).unwrap(); + assert_eq!(set(&s), [("fi".to_string(), None), (" i fikir".to_string(), Some("tr".to_string()))]); + s.execute("edit.undo", &json!({})).unwrap(); + assert_eq!(set(&s), [("fi i fikir".to_string(), Some("tr".to_string()))]); + s.execute("edit.redo", &json!({})).unwrap(); + // Saved and reopened as it was. + 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") }; + let reopened: Vec<_> = t.runs.iter().map(|r| (r.text.clone(), r.style.language.clone())).collect(); + assert_eq!(reopened, set(&s)); + // A character style captured from the characters carries it, the default included; one given + // a language by name stores its tag. + assert_eq!(super::super::textstyles::char_attrs(&runs(&s)[1].style)["language"], "tr"); + assert_eq!(super::super::textstyles::char_attrs(&runs(&s)[0].style).get("language"), Some(&serde_json::Value::Null)); + s.execute("charStyle.new", &json!({"name": "Marathi", "attrs": {"language": "Marathi"}})).unwrap(); + let listed = s.execute("charStyle.list", &json!({})).unwrap(); + let marathi = listed["styles"].as_array().unwrap().iter().find(|st| st["name"] == "Marathi").unwrap(); + assert_eq!(marathi["attrs"]["language"], "mr"); + assert!(s.execute("charStyle.new", &json!({"name": "Bad", "attrs": {"language": "Klingon"}})).is_err()); + // New type in a Turkish document is in Turkish. + s.execute("document.setup", &json!({"language": "Turkish"})).unwrap(); + let id2 = s.execute("text.create", &json!({"x": 10, "y": 150, "text": "fikir"})).unwrap()["id"].as_u64().unwrap(); + let NodeKind::Text(t) = &s.doc().unwrap().doc.node(NodeId(id2)).unwrap().kind else { panic!("text") }; + assert_eq!(t.runs[0].style.language.as_deref(), Some("tr")); +} + /// Variable Font settings (Character panel › Variable Font): a character attribute, saved only /// when set, each value clamped to its axis's range, and cleared by another font or style. #[test] diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index e09a59232..d33addad4 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -5926,4 +5926,14 @@ breakText Glyphs Глифи Set Size of Each Задаване на размер на всеки @msg select exactly two objects изберете точно два обекта @msg give `width` or `height` задайте `width` или `height` + Marathi Маратхи + Gujarati Гуджарати + Bengali Бенгалски + Punjabi Пенджабски + Tamil Тамилски + Telugu Телугу + Kannada Каннада + Malayalam Малаялам + Odia Ория + Urdu Урду VectorCraft {release} and later (version {n}) VectorCraft {release} и по-нови (версия {n}) diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index f213baaab..f527ca255 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -5960,4 +5960,14 @@ shape Open Otvoren Set Size of Each Postavi veličinu svakog @msg select exactly two objects odaberite tačno dva objekta @msg give `width` or `height` zadajte `width` ili `height` + Marathi Marathi + Gujarati Gudžarati + Bengali Bengalski + Punjabi Pandžapski + Tamil Tamilski + Telugu Telugu + Kannada Kanada + Malayalam Malajalam + Odia Odija + Urdu Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} i novije (verzija {n}) diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 8d8054c25..5873fbada 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -6033,4 +6033,14 @@ breakText Glyphs Glifs Set Size of Each Defineix la mida de cadascun @msg select exactly two objects selecciona exactament dos objectes @msg give `width` or `height` indica `width` o `height` + Marathi Marathi + Gujarati Gujarati + Bengali Bengalí + Punjabi Panjabi + Tamil Tàmil + Telugu Telugu + Kannada Kannada + Malayalam Malaiàlam + Odia Oriya + Urdu Urdú VectorCraft {release} and later (version {n}) VectorCraft {release} i posteriors (versió {n}) diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 0caa797bd..8f7a60710 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1442,7 +1442,8 @@ lighting Light Světlo Character Direction Směr znaků Digits Číslice Arabic Arabské - Hindi Hindské + Hindi Hindština +digits Hindi Hindské Farsi Perské Reset Panel Obnovit panel Roman Baseline Latinková účaří @@ -6042,4 +6043,14 @@ breakText Glyphs Glyfy Set Size of Each Nastavit velikost jednotlivě @msg select exactly two objects vyberte právě dva objekty @msg give `width` or `height` zadejte `width` nebo `height` + Marathi Maráthština + Gujarati Gudžarátština + Bengali Bengálština + Punjabi Paňdžábština + Tamil Tamilština + Telugu Telugština + Kannada Kannadština + Malayalam Malajálamština + Odia Urijština + Urdu Urdština VectorCraft {release} and later (version {n}) VectorCraft {release} a novější (verze {n}) diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 1507f30ef..8b67b907a 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -5940,4 +5940,14 @@ breakText Glyphs Glyffer Set Size of Each Angiv størrelse for hver @msg select exactly two objects markér præcis to objekter @msg give `width` or `height` angiv `width` eller `height` + Marathi Marathi + Gujarati Gujarati + Bengali Bengali + Punjabi Punjabi + Tamil Tamil + Telugu Telugu + Kannada Kannada + Malayalam Malayalam + Odia Oriya + Urdu Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} og nyere (version {n}) diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 22d228097..7400e6f73 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -5628,4 +5628,14 @@ breakText Glyphs Glyphen Set Size of Each Größe einzeln festlegen @msg select exactly two objects wählen Sie genau zwei Objekte aus @msg give `width` or `height` geben Sie `width` oder `height` an + Marathi Marathi + Gujarati Gujarati + Bengali Bengalisch + Punjabi Punjabi + Tamil Tamil + Telugu Telugu + Kannada Kannada + Malayalam Malayalam + Odia Oriya + Urdu Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} und neuer (Version {n}) diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index a993b9840..19ba17dd7 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -5995,4 +5995,14 @@ breakText Glyphs Γλύφοι Set Size of Each Ορισμός μεγέθους καθενός @msg select exactly two objects επιλέξτε ακριβώς δύο αντικείμενα @msg give `width` or `height` δώστε `width` ή `height` + Marathi Μαραθικά + Gujarati Γκουγιαράτι + Bengali Βεγγαλικά + Punjabi Παντζαπικά + Tamil Ταμιλικά + Telugu Τελούγκου + Kannada Κανάντα + Malayalam Μαλαγιάλαμ + Odia Όντια + Urdu Ούρντου VectorCraft {release} and later (version {n}) VectorCraft {release} και νεότερες (έκδοση {n}) diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 19e524cf2..884ee8e7e 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -5985,4 +5985,14 @@ breakText Glyphs Glifos Set Size of Each Definir el tamaño de cada uno @msg select exactly two objects selecciona exactamente dos objetos @msg give `width` or `height` indica `width` o `height` + Marathi Maratí + Gujarati Guyaratí + Bengali Bengalí + Punjabi Punyabí + Tamil Tamil + Telugu Telugu + Kannada Canarés + Malayalam Malayálam + Odia Oriya + Urdu Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} y posteriores (versión {n}) diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index eb9c0e525..1b3528602 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -6073,4 +6073,14 @@ breakText Glyphs Merkit Set Size of Each Aseta kunkin koko @msg select exactly two objects valitse täsmälleen kaksi objektia @msg give `width` or `height` anna `width` tai `height` + Marathi Marathi + Gujarati Gudžarati + Bengali Bengali + Punjabi Pandžabi + Tamil Tamili + Telugu Telugu + Kannada Kannada + Malayalam Malajalam + Odia Orija + Urdu Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} ja uudemmat (versio {n}) diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index d2ecba42d..46f1c08db 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -6040,4 +6040,14 @@ breakText Glyphs Glyphes Set Size of Each Définir la taille de chaque objet @msg select exactly two objects sélectionnez exactement deux objets @msg give `width` or `height` indiquez `width` ou `height` + Marathi Marathi + Gujarati Goudjarati + Bengali Bengali + Punjabi Pendjabi + Tamil Tamoul + Telugu Télougou + Kannada Kannada + Malayalam Malayalam + Odia Odia + Urdu Ourdou VectorCraft {release} and later (version {n}) VectorCraft {release} et ultérieures (version {n}) diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index b29f8ef71..bf9ec10b2 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -6029,4 +6029,14 @@ breakText Glyphs Glifák Set Size of Each Egyenkénti méret beállítása @msg select exactly two objects jelölj ki pontosan két objektumot @msg give `width` or `height` add meg a `width` vagy a `height` értéket + Marathi Maráthi + Gujarati Gudzsaráti + Bengali Bengáli + Punjabi Pandzsábi + Tamil Tamil + Telugu Telugu + Kannada Kannada + Malayalam Malajálam + Odia Odia + Urdu Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} és újabb (verzió: {n}) diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 705f57370..99b747111 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -5940,4 +5940,14 @@ breakText Glyphs Glif Set Size of Each Atur ukuran masing-masing @msg select exactly two objects pilih tepat dua objek @msg give `width` or `height` berikan `width` atau `height` + Marathi Marathi + Gujarati Gujarati + Bengali Bengali + Punjabi Punjabi + Tamil Tamil + Telugu Telugu + Kannada Kannada + Malayalam Malayalam + Odia Oriya + Urdu Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} dan yang lebih baru (versi {n}) diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 9d6a31cca..f3cbd7558 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5662,4 +5662,14 @@ breakText Glyphs Glifi Set Size of Each Imposta dimensione di ciascuno @msg select exactly two objects seleziona esattamente due oggetti @msg give `width` or `height` indica `width` o `height` + Marathi Marathi + Gujarati Gujarati + Bengali Bengalese + Punjabi Punjabi + Tamil Tamil + Telugu Telugu + Kannada Kannada + Malayalam Malayalam + Odia Odia + Urdu Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} e successive (versione {n}) diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index abe51ff0a..5363c5d06 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -46,7 +46,8 @@ font-technology Variable バリアブル Character Direction 文字の方向 Digits 数字 Arabic アラビア数字 - Hindi ヒンディー数字 + Hindi ヒンディー語 +digits Hindi ヒンディー数字 Farsi ペルシャ数字 Favorites お気に入り Flip 反転 @@ -5728,4 +5729,14 @@ breakText Glyphs 字形 Set Size of Each 個別にサイズを設定 @msg select exactly two objects オブジェクトをちょうど 2 つ選択してください @msg give `width` or `height` `width` または `height` を指定してください + Marathi マラーティー語 + Gujarati グジャラート語 + Bengali ベンガル語 + Punjabi パンジャブ語 + Tamil タミル語 + Telugu テルグ語 + Kannada カンナダ語 + Malayalam マラヤーラム語 + Odia オディア語 + Urdu ウルドゥー語 VectorCraft {release} and later (version {n}) VectorCraft {release} 以降 (バージョン {n}) diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index adcb85d67..8f2c407c4 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -6032,4 +6032,14 @@ breakText Glyphs გლიფები Set Size of Each თითოეულის ზომის დაყენება @msg select exactly two objects მონიშნეთ ზუსტად ორი ობიექტი @msg give `width` or `height` მიუთითეთ `width` ან `height` + Marathi მარათჰი + Gujarati გუჯარათი + Bengali ბენგალური + Punjabi პენჯაბური + Tamil ტამილური + Telugu ტელუგუ + Kannada კანადა + Malayalam მალაიალამური + Odia ორია + Urdu ურდუ VectorCraft {release} and later (version {n}) VectorCraft {release} და უფრო ახალი (ვერსია {n}) diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index b3bf3cd62..acf8135c3 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5625,4 +5625,14 @@ breakText Glyphs 글리프 Set Size of Each 각각 크기 설정 @msg select exactly two objects 개체를 정확히 두 개 선택하세요 @msg give `width` or `height` `width` 또는 `height`를 지정하세요 + Marathi 마라티어 + Gujarati 구자라트어 + Bengali 벵골어 + Punjabi 펀잡어 + Tamil 타밀어 + Telugu 텔루구어 + Kannada 칸나다어 + Malayalam 말라얄람어 + Odia 오리야어 + Urdu 우르두어 VectorCraft {release} and later (version {n}) VectorCraft {release} 이상 (버전 {n}) diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index c3bf46c3e..51dd8b37e 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -5984,4 +5984,14 @@ breakText Glyphs Glyphs Set Size of Each Formaat van elk instellen @msg select exactly two objects selecteer precies twee objecten @msg give `width` or `height` geef `width` of `height` op + Marathi Marathi + Gujarati Gujarati + Bengali Bengaals + Punjabi Punjabi + Tamil Tamil + Telugu Telugu + Kannada Kannada + Malayalam Malayalam + Odia Odia + Urdu Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} en nieuwer (versie {n}) diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 519595ffc..30203b1a1 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -5898,4 +5898,14 @@ breakText Glyphs Glify Set Size of Each Ustaw rozmiar każdego @msg select exactly two objects zaznacz dokładnie dwa obiekty @msg give `width` or `height` podaj `width` lub `height` + Marathi Marathi + Gujarati Gudżarati + Bengali Bengalski + Punjabi Pendżabski + Tamil Tamilski + Telugu Telugu + Kannada Kannada + Malayalam Malajalam + Odia Orija + Urdu Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} i nowsze (wersja {n}) diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index b07ec8f66..6ede5c351 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -5962,4 +5962,14 @@ breakText Glyphs Glifos Set Size of Each Definir Tamanho de Cada Um @msg select exactly two objects selecione exatamente dois objetos @msg give `width` or `height` informe `width` ou `height` + Marathi Marati + Gujarati Guzerate + Bengali Bengali + Punjabi Panjabi + Tamil Tâmil + Telugu Télugo + Kannada Canarim + Malayalam Malaiala + Odia Oriá + Urdu Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} e posteriores (versão {n}) diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 685d6862b..2bf406c74 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -6020,4 +6020,14 @@ breakText Glyphs Глифы Set Size of Each Задать размер каждого @msg select exactly two objects выделите ровно два объекта @msg give `width` or `height` укажите `width` или `height` + Marathi Маратхи + Gujarati Гуджарати + Bengali Бенгальский + Punjabi Панджаби + Tamil Тамильский + Telugu Телугу + Kannada Каннада + Malayalam Малаялам + Odia Ория + Urdu Урду VectorCraft {release} and later (version {n}) VectorCraft {release} и новее (версия {n}) diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index b5ff04674..788d45c8a 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -6095,4 +6095,14 @@ breakText Glyphs Glyfer Set Size of Each Ange storlek för vart och ett @msg select exactly two objects markera exakt två objekt @msg give `width` or `height` ange `width` eller `height` + Marathi Marathi + Gujarati Gujarati + Bengali Bengali + Punjabi Punjabi + Tamil Tamil + Telugu Telugu + Kannada Kannada + Malayalam Malayalam + Odia Oriya + Urdu Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} och senare (version {n}) diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 4961fcd15..50b7f213b 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -6000,4 +6000,14 @@ breakText Glyphs Glifler Set Size of Each Her Birinin Boyutunu Ayarla @msg select exactly two objects tam olarak iki nesne seç @msg give `width` or `height` `width` veya `height` belirt + Marathi Marathi dili + Gujarati Gücerat dili + Bengali Bengalce + Punjabi Pencapça + Tamil Tamilce + Telugu Telugu dili + Kannada Kannada dili + Malayalam Malayalam dili + Odia Oriya dili + Urdu Urduca VectorCraft {release} and later (version {n}) VectorCraft {release} ve sonrası (sürüm {n}) diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 886675579..03d912f32 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5642,4 +5642,14 @@ breakText Glyphs Гліфи Set Size of Each Задати розмір кожного @msg select exactly two objects виділіть рівно два об’єкти @msg give `width` or `height` укажіть `width` або `height` + Marathi Маратхі + Gujarati Гуджараті + Bengali Бенгальська + Punjabi Панджабі + Tamil Тамільська + Telugu Телугу + Kannada Каннада + Malayalam Малаялам + Odia Одія + Urdu Урду VectorCraft {release} and later (version {n}) VectorCraft {release} і новіші (версія {n}) diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index f0ed3842e..cdea7542b 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1624,7 +1624,8 @@ file-filter Templates Mẫu Highlight Substituted Fonts Đánh dấu phông thay thế Highlight Substituted Glyphs Đánh dấu Glyph bị thay thế Highlight Variable Đánh dấu biến - Hindi Hindi + Hindi Tiếng Hindi +digits Hindi Hindi History Lịch sử History Options… Tùy chọn lịch sử… Step Backward Lùi một bước @@ -5644,4 +5645,14 @@ breakText Glyphs Glyph Set Size of Each Đặt kích thước từng cái @msg select exactly two objects hãy chọn đúng hai đối tượng @msg give `width` or `height` hãy cho `width` hoặc `height` + Marathi Tiếng Marathi + Gujarati Tiếng Gujarati + Bengali Tiếng Bengali + Punjabi Tiếng Punjab + Tamil Tiếng Tamil + Telugu Tiếng Telugu + Kannada Tiếng Kannada + Malayalam Tiếng Malayalam + Odia Tiếng Odia + Urdu Tiếng Urdu VectorCraft {release} and later (version {n}) VectorCraft {release} trở lên (phiên bản {n}) diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index b18470640..c485086b1 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2358,7 +2358,8 @@ font-script Japanese 汉字 Character Direction 字符方向 Digits 数字 Arabic 阿拉伯数字 - Hindi 印地数字 + Hindi 印地语 +digits Hindi 印地数字 Farsi 波斯数字 Proportional Oldstyle 比例旧式数字 Pucker & Bloat… 缩拢与膨胀… @@ -5884,4 +5885,14 @@ breakText Glyphs 字形 Set Size of Each 分别设置大小 @msg select exactly two objects 请恰好选择两个对象 @msg give `width` or `height` 请提供 `width` 或 `height` + Marathi 马拉地语 + Gujarati 古吉拉特语 + Bengali 孟加拉语 + Punjabi 旁遮普语 + Tamil 泰米尔语 + Telugu 泰卢固语 + Kannada 卡纳达语 + Malayalam 马拉雅拉姆语 + Odia 奥里亚语 + Urdu 乌尔都语 VectorCraft {release} and later (version {n}) VectorCraft {release} 及更高版本 (版本 {n}) diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 2b7e38a40..06ea60498 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -3299,7 +3299,8 @@ font-script Japanese 漢字 Character Direction 字元方向 Digits 數字 Arabic 阿拉伯數字 - Hindi 印地數字 + Hindi 印地文 +digits Hindi 印地數字 Farsi 波斯數字 Proportional Oldstyle 比例舊式數字 Pucker & Bloat… 縮攏與膨脹… @@ -5681,4 +5682,14 @@ breakText Glyphs 字符 Set Size of Each 個別設定大小 @msg select exactly two objects 請選取正好兩個物件 @msg give `width` or `height` 請提供 `width` 或 `height` + Marathi 馬拉地文 + Gujarati 古吉拉特文 + Bengali 孟加拉文 + Punjabi 旁遮普文 + Tamil 坦米爾文 + Telugu 泰盧固文 + Kannada 坎那達文 + Malayalam 馬來亞拉姆文 + Odia 歐利亞文 + Urdu 烏都文 VectorCraft {release} and later (version {n}) VectorCraft {release} 及更新版本 (版本 {n}) diff --git a/crates/ui-egui/src/panels/character.rs b/crates/ui-egui/src/panels/character.rs index 82445374b..ebd80f1ed 100644 --- a/crates/ui-egui/src/panels/character.rs +++ b/crates/ui-egui/src/panels/character.rs @@ -1,11 +1,12 @@ //! Character panel: font family / style, size, leading, kerning, tracking, vertical and horizontal //! scale, baseline shift, character rotation and the All Caps, Small Caps, Superscript, Subscript, -//! Underline and Strikethrough toggles. +//! Underline and Strikethrough toggles and the Language menu. use std::sync::{Mutex, OnceLock}; use egui::{Key, Ui, vec2}; use serde_json::{Value, json}; +use vectorcraft_doc::setup::{LANGUAGES, char_language_name}; use vectorcraft_doc::{CharStyle, NodeId, NodeKind}; use vectorcraft_tools::{Mods, ToolKey}; @@ -922,6 +923,17 @@ fn attributes(app: &mut VectorcraftApp, ui: &mut Ui) { } } }); + // Language: the characters are shaped in it, so that the font uses that language's forms. + ui.add_space(4.0); + let names: Vec<&str> = LANGUAGES.iter().map(|(n, _, _)| *n).collect(); + // Mixed: blank; a tag from a file that is not in the list: as it is. + let current = shared(app, |s| s.language.clone()) + .map_or_else(String::new, |l| char_language_name(l.as_deref()).map_or_else(|| l.unwrap_or_default(), str::to_string)); + widgets::label_row(ui, tl!("Language:"), 64.0, |ui| { + if let Some(name) = widgets::dropdown(ui, "ch-language", ¤t, &names, w - 72.0).and_then(|i| names.get(i)) { + format(app, json!({"language": name})); + } + }); } /// The `text.setFormat` params of the Superscript or Subscript toggle `p` for text whose @@ -1079,11 +1091,13 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { 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"), - (tl!("Arabic"), Digits::Arabic, "arabic"), - (tl!("Hindi"), Digits::Hindi, "hindi"), - (tl!("Farsi"), Digits::Farsi, "farsi"), + (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})); diff --git a/docs/mcp.md b/docs/mcp.md index a03d5db9f..baf64c4e2 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -5034,6 +5034,21 @@ A digit the font lacks comes from a fallback font. Default and saved only when s {"name":"run_command","arguments":{"command":"text.setFormat","params":{"digits":"hindi"}}} ``` +## Language + +`text.setFormat {language}` (the selected type, or `ids`) and `text.setRangeStyle {id, start, end, language}` (a range) +set the language of the characters, as the Character panel's Language menu does: one of Document Setup's languages +(`document.setup {language}`), by name or BCP 47 tag, case-insensitive (`"Turkish"` or `"tr"`, `"Marathi"` or `"mr"`). +An unknown language is an error. The characters are shaped in it, so the font uses that language's forms (OpenType +`locl`): in Turkish, Source Serif 4 keeps `fi` as two letters and the dot on the i, where English makes the fi ligature. +Runs are saved with the tag, and English: USA, the default, as no language, so files without languages load and save +as before. New type takes the document's language. Character styles captured from type carry it, and `charStyle.new` / +`charStyle.setAttrs` take `attrs: {language}` the same way. One undo step. + +```json +{"name":"run_command","arguments":{"command":"text.setFormat","params":{"language":"Turkish"}}} +``` + ## Touch Type The Touch Type tool (`touchType`, Shift+T) changes one character at a time. A click on a character of a type object diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 2adeb29a5..6dd6db0b2 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # Parity with Adobe Illustrator -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (New Document: Save Preset in More Settings, #4893; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (Type core: text shaped in its language, a Language per character, Indian languages, #4940 #4946 #4943; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -1403,6 +1403,7 @@ dialogs, Preview and Cancel, Zoom to Selection) and `command_sweep`. Commands: ` | Date | Change | Summary | |---|---|---| +| 2026-11-01 | minor | Type core: a Language per character (Character panel › Language, `text.setFormat` / `text.setRangeStyle {language}`, character styles), new type in the document's language; text is shaped in its language so fonts use their language forms (OpenType `locl`: Turkish `fi`, #4940, #4946); Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu in the language list (#4943); scores unchanged | | 2026-10-31 | minor | Selection tools: with Alt held at the press, a Direct Selection marquee selects every path it touches whole and keeps the Group Selection pointer (#4890); a press on a selected object's centre point selects the object whole and a drag from it moves it (#4891); regression tests in `direct/tests.rs`; scores and estimates unchanged. | | 2026-10-31 | minor | Arrange Tools add-on (a VectorCraft addition): evidence added; scores unchanged. | | 2026-10-31 | minor | Guides and snapping: Snap to Perpendicular and Snap to Tangent apply to an existing line's end dragged with Direct Selection, as the public pages describe for editing existing lines (the end anchor of a straight segment, not mid-line anchors or curved segments); a regression test covers both snaps, both toggles and non-snapping controls (#2530); scores and estimates unchanged. | diff --git a/docs/type-parity.md b/docs/type-parity.md index 4de41b37c..3d76581f9 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-10-31 · **Change:** minor (PDF import finds installed fonts whose PDF names hold hyphens or a composite font's CMap; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **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) 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 @@ -60,6 +60,7 @@ against Illustrator's public documentation; **estimated** unless noted. | CJK: Proportional Metrics (`palt` in horizontal type, `vpal` in vertical type) | done | #966 | | CJK: manual tate-chu-yoko | done | Character panel menu › Tate-chu-yoko (`text.setFormat` / `text.setRangeStyle {tateChuYoko}`), with Show East Asian Options: marked characters stand side by side across one em of a vertical column, squeezed to the em when wider; two or three half-width digits still do so without the mark; horizontal type keeps the mark and does not move them. Up/Down and Left/Right settings remain. Evidence: `tests_vertical::manual_tate_chu_yoko_sets_marked_characters_across_one_em`, `typecmd::tate_chu_yoko_is_a_character_attribute_saved_only_when_on` | | CJK: ruby, manual tate-chu-yoko | partial | ruby (ふりがな) for live text (`type.ruby`): a reading over a byte range, above (right of the column in vertical type) or below, start/centre/end alignment, size, overhang or squeeze; saved natively, laid out with its line growing, drawn on the canvas, in SVG and PDF (as outlines where ruby is present), in EPS, EMF/WMF and legacy `.ai` (legacy `.ai` writes type with ruby as outlines), and kept by Create Outlines, Expand Appearance and flattening, each glyph where the layout puts it (`typecmd::ruby_tests::create_outlines_keeps_the_ruby_where_it_is_laid_out`); manual tate-chu-yoko missing | #633 | +| Language per character (Character panel › Language, character styles), text shaped in it | done | `text.setFormat` / `text.setRangeStyle {language}` by name or BCP 47 tag, one of Document Setup's languages (Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu added, #4943); the shaper gets the tag, so the font's language forms apply (OpenType `locl`: Turkish keeps `fi` apart and the dot on its i, #4940); new type takes the document's language; saved as the tag only when set, carried by character styles (#4946). Not yet: hyphenation and spelling by it, `.ai`/PDF import of a text's language, paragraph styles. Evidence: `features::tests::language_picks_the_fonts_forms`, `typecmd::area_tests::language_is_a_character_attribute_saved_only_when_set` | | No Break | done | Character panel menu (`text.setFormat` / `text.setRangeStyle {noBreak}`): the marked characters stay on one line, including across a space; a run that does not fit after earlier text moves together, and a run longer than the line stays together. Evidence: `layout::wrapping_tests::no_break_keeps_marked_characters_on_one_line`, `tests::no_break_keeps_marked_words_on_one_line`, `typecmd::no_break_is_a_character_attribute_saved_only_when_on` | | CJK: ruby | missing | #633 | | Inline symbols in text | beyond Illustrator | not scored | @@ -71,6 +72,7 @@ Character Styles menu New/Redefine and footer New capture the first selected cha | Date | Change | Summary | |---|---|---| +| 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 | | 2026-10-30 | minor | Highlight Alternate Glyphs leaves the required lam-alef ligature unhighlighted also in older Arabic fonts (such as legacy Arial) that put it in `liga` rather than `rlig` (#394); scores unchanged | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index fee3db42d..f7a7c1e16 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI and interaction parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (native Save As Version menu, refused-save alert, fixed Contributors table header, freeform point swatch opens its popover: #4931, #4933, #4944, #4953) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (Character panel › Language, Indian languages in Document Setup › Language, #4946 #4943) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -381,6 +381,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| +| 2026-11-01 | minor | Character panel › Language: the selected characters' language, from Document Setup's list, which gains Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu (#4946, #4943); the Digits menu's numeral names keep their own translations; scores unchanged | | 2026-11-01 | minor | VectorCraft Options' Version is a menu of the versions VectorCraft writes, each named by the releases that read it, and an uncompressible version turns Use Compression off (#4931); a save refused before writing shows the Can't save alert with the reason (#4933); About › Contributors' table keeps its header row above the scrolling rows (#4944); the Gradient panel's freeform point swatch opens the point's popover (#4953); scores unchanged | | 2026-10-31 | minor | A file opened from the file manager while VectorCraft runs opens as a new tab in that window, which comes to the front, on Windows and Linux (#4892); `--new-instance` opts out; scores unchanged | | 2026-10-31 | minor | Object › Path › Smooth: Auto-Smooth takes a shaky line to one smooth curve, and direct-selected anchors smooth only their section; scores unchanged |