Merge pull request #4981 from storytold/feat/indic-digits

Fix #4942: native Indic digits in the Digits menu
This commit is contained in:
BFlat
2026-11-01 03:40:50 +07:00
committed by GitHub
37 changed files with 516 additions and 34 deletions
+71 -2
View File
@@ -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> {
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)
}
+33
View File
@@ -60,6 +60,39 @@ fn digits_map_between_the_numeral_systems() {
assert_eq!(serde_json::from_value::<CharStyle>(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::<Digits>(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::<Digits>("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() {
File diff suppressed because one or more lines are too long
+10 -9
View File
@@ -39,7 +39,7 @@ pub fn specs() -> Vec<CommandSpec> {
"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<Option<Option
}))
}
/// `digits: "default"|"arabic"|"hindi"|"farsi"` → the numeral system the digits are drawn in.
/// `digits: "default"|"arabic"|"hindi"|"farsi"|"devanagari"|…` ([`Digits::id`]) → the numeral
/// system the digits are drawn in.
///
/// [`Digits::id`]: vectorcraft_doc::Digits::id
pub(crate) fn digits_param(p: &Value, cmd: &str) -> Result<Option<vectorcraft_doc::Digits>> {
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<String> = 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
@@ -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::<String>(), "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]
+41
View File
@@ -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::<Vec<_>>();
let gids = |l: &TextLayout| l.glyphs.iter().map(|g| (g.gid, g.font_id)).collect::<Vec<_>>();
// 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]
+9
View File
@@ -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 Инструмент „Свиване“
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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…
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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ů.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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)
Can't render this file because it contains an unexpected character in line 164 and column 5.
+9
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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…
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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.
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
1359
1360
1361 digits
1362 digits
1363 digits
1364 digits
1365 digits
1366 digits
1367 digits
1368 digits
1369 digits
1370
1371
1372
1373
+9
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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 日本語
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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. სიმბოლოების ატრიბუტების რედაქტირებისთვის მონიშნეთ ტექსტური ობიექტი.
Can't render this file because it is too large.
+9
View File
@@ -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 일본어
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+9
View File
@@ -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 Инструмент «Сморщивание»
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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
Can't render this file because it contains an unexpected character in line 5 and column 25.
+16
View File
@@ -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"));
}
+9
View File
@@ -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ı
Can't render this file because it contains an unexpected character in line 5 and column 25.
+9
View File
@@ -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 Інструмент «Зморщування»
Can't render this file because it contains an unexpected character in line 178 and column 5.
+9
View File
@@ -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
Can't render this file because it contains an unexpected character in line 200 and column 5.
+9
View File
@@ -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 收缩工具
Can't render this file because it contains an unexpected character in line 2 and column 73.
+9
View File
@@ -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 縮攏工具
Can't render this file because it contains an unexpected character in line 6 and column 47.
+39 -15
View File
@@ -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<vectorcraft_doc::Digits>) {
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));
@@ -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::<Vec<_>>(), ["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.
+8 -5
View File
@@ -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.
+3 -2
View File
@@ -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 |