diff --git a/fauntlet/src/compare_glyphs.rs b/fauntlet/src/compare_glyphs.rs index 9d8bc1bba..0eaa5248e 100644 --- a/fauntlet/src/compare_glyphs.rs +++ b/fauntlet/src/compare_glyphs.rs @@ -1,3 +1,5 @@ +use crate::Hinting; + use super::{FreeTypeInstance, InstanceOptions, RegularizingPen, SkrifaInstance}; use skrifa::{outline::pen::PathElement, GlyphId}; use std::{io::Write, path::Path}; @@ -13,6 +15,21 @@ pub fn compare_glyphs( // Don't run on bitmap fonts (yet) return true; } + if let Some(Hinting::Auto(_)) = options.hinting { + // This font is a pathological case for autohinting due to the + // extreme number of generated segments and edges. To be + // precise, it takes longer to test this single font than the + // entire remainder of the Google Fonts corpus so we skip it + // here. + // Discussion at + if ft_instance + .family_name() + .unwrap_or_default() + .contains("Handjet") + { + return true; + } + } let glyph_count = skrifa_instance.glyph_count(); let is_scaled = options.ppem != 0; @@ -86,6 +103,7 @@ pub fn compare_glyphs( if exit_on_fail { std::process::exit(1); } + break; } } ok diff --git a/fauntlet/src/font/freetype.rs b/fauntlet/src/font/freetype.rs index 2fbd24459..ca6f0be05 100644 --- a/fauntlet/src/font/freetype.rs +++ b/fauntlet/src/font/freetype.rs @@ -3,26 +3,12 @@ use freetype::{ ffi::{FT_Long, FT_Vector}, Face, Library, }; -use skrifa::{ - outline::{HintingMode, LcdLayout, OutlinePen}, - GlyphId, -}; +use skrifa::{outline::OutlinePen, GlyphId}; use std::ffi::{c_int, c_void}; use super::{InstanceOptions, SharedFontData}; -fn load_flags_from_hinting(mode: HintingMode) -> LoadFlag { - match mode { - HintingMode::Strong => LoadFlag::TARGET_MONO, - HintingMode::Smooth { lcd_subpixel, .. } => match lcd_subpixel { - Some(LcdLayout::Horizontal) => LoadFlag::TARGET_LCD, - Some(LcdLayout::Vertical) => LoadFlag::TARGET_LCD_V, - None => LoadFlag::TARGET_NORMAL, - }, - } -} - pub struct FreeTypeInstance { face: Face, load_flags: LoadFlag, @@ -37,10 +23,10 @@ impl FreeTypeInstance { let mut face = library .new_memory_face2(data.clone(), options.index as isize) .ok()?; - let mut load_flags = LoadFlag::NO_AUTOHINT | LoadFlag::NO_BITMAP; + let mut load_flags = LoadFlag::NO_BITMAP; match options.hinting { None => load_flags |= LoadFlag::NO_HINTING, - Some(hinting) => load_flags |= load_flags_from_hinting(hinting), + Some(hinting) => load_flags |= hinting.freetype_load_flags(), }; if options.ppem != 0 { face.set_pixel_sizes(options.ppem, options.ppem).ok()?; @@ -76,6 +62,10 @@ impl FreeTypeInstance { Some(Self { face, load_flags }) } + pub fn family_name(&self) -> Option { + self.face.family_name() + } + pub fn is_scalable(&self) -> bool { self.face.is_scalable() } diff --git a/fauntlet/src/font/mod.rs b/fauntlet/src/font/mod.rs index 6a8975f77..5d8f46a02 100644 --- a/fauntlet/src/font/mod.rs +++ b/fauntlet/src/font/mod.rs @@ -4,9 +4,9 @@ use std::{ sync::Arc, }; -use ::freetype::Library; +use ::freetype::{face::LoadFlag, Library}; use ::skrifa::{ - outline::HintingMode, + outline::{HintingOptions, SmoothMode, Target}, raw::{types::F2Dot14, FileRef, FontRef, TableProvider}, }; @@ -16,21 +16,76 @@ mod skrifa; pub use freetype::FreeTypeInstance; pub use skrifa::SkrifaInstance; +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] +pub enum HintingTarget { + #[default] + Normal, + Light, + Lcd, + VerticalLcd, + Mono, +} + +impl HintingTarget { + pub fn to_skrifa_target(self) -> Target { + match self { + Self::Normal => SmoothMode::Normal.into(), + Self::Light => SmoothMode::Light.into(), + Self::Lcd => SmoothMode::Lcd.into(), + Self::VerticalLcd => SmoothMode::VerticalLcd.into(), + Self::Mono => Target::Mono, + } + } + + pub fn to_freetype_load_flags(self) -> LoadFlag { + match self { + Self::Normal => LoadFlag::TARGET_NORMAL, + Self::Light => LoadFlag::TARGET_LIGHT, + Self::Lcd => LoadFlag::TARGET_LCD, + Self::VerticalLcd => LoadFlag::TARGET_LCD_V, + Self::Mono => LoadFlag::TARGET_MONO, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Hinting { + Interpreter(HintingTarget), + Auto(HintingTarget), +} + +impl Hinting { + pub fn skrifa_options(self) -> HintingOptions { + match self { + Self::Interpreter(target) => HintingOptions { + engine: ::skrifa::outline::Engine::Interpreter, + target: target.to_skrifa_target(), + }, + Self::Auto(target) => HintingOptions { + engine: ::skrifa::outline::Engine::Auto(None), + target: target.to_skrifa_target(), + }, + } + } + + pub fn freetype_load_flags(self) -> LoadFlag { + match self { + Self::Interpreter(target) => LoadFlag::NO_AUTOHINT | target.to_freetype_load_flags(), + Self::Auto(target) => LoadFlag::FORCE_AUTOHINT | target.to_freetype_load_flags(), + } + } +} + #[derive(Copy, Clone, Debug)] pub struct InstanceOptions<'a> { pub index: usize, pub ppem: u32, pub coords: &'a [F2Dot14], - pub hinting: Option, + pub hinting: Option, } impl<'a> InstanceOptions<'a> { - pub fn new( - index: usize, - ppem: u32, - coords: &'a [F2Dot14], - hinting: Option, - ) -> Self { + pub fn new(index: usize, ppem: u32, coords: &'a [F2Dot14], hinting: Option) -> Self { Self { index, ppem, diff --git a/fauntlet/src/font/skrifa.rs b/fauntlet/src/font/skrifa.rs index 44986ebdd..873f1e5d9 100644 --- a/fauntlet/src/font/skrifa.rs +++ b/fauntlet/src/font/skrifa.rs @@ -27,8 +27,13 @@ impl<'a> SkrifaInstance<'a> { let outlines = font.outline_glyphs(); let hinter = if options.ppem != 0 && options.hinting.is_some() { Some( - HintingInstance::new(&outlines, size, options.coords, options.hinting.unwrap()) - .ok()?, + HintingInstance::new( + &outlines, + size, + options.coords, + options.hinting.unwrap().skrifa_options(), + ) + .ok()?, ) } else { None diff --git a/fauntlet/src/lib.rs b/fauntlet/src/lib.rs index a2859b009..296a506a0 100644 --- a/fauntlet/src/lib.rs +++ b/fauntlet/src/lib.rs @@ -3,5 +3,7 @@ mod font; mod pen; pub use compare_glyphs::compare_glyphs; -pub use font::{Font, FreeTypeInstance, InstanceOptions, SharedFontData, SkrifaInstance}; +pub use font::{ + Font, FreeTypeInstance, Hinting, HintingTarget, InstanceOptions, SharedFontData, SkrifaInstance, +}; pub use pen::RegularizingPen; diff --git a/skrifa/generated/generated_autohint_styles.rs b/skrifa/generated/generated_autohint_styles.rs index 2a1f6733c..1d077f27a 100644 --- a/skrifa/generated/generated_autohint_styles.rs +++ b/skrifa/generated/generated_autohint_styles.rs @@ -8,811 +8,751 @@ pub(super) const SCRIPT_CLASSES: &[ScriptClass] = &[ name: "Adlam", group: ScriptGroup::Default, tag: Tag::new(b"ADLM"), - index: 0, hint_top_to_bottom: false, - std_chars: &['𞤌', '𞤮', ], + std_chars: "𞤌 𞤮", blues: &[ - (&['𞤌', '𞤅', '𞤈', '𞤏', '𞤔', '𞤚', ], TOP), - (&['𞤂', '𞤖', ], 0), - (&['𞤬', '𞤮', '𞤻', '𞤼', '𞤾', ], TOP | LATIN_X_HEIGHT), - (&['𞤤', '𞤨', '𞤩', '𞤭', '𞤴', '𞤸', '𞤺', '𞥀', ], 0), + ("𞤌 𞤅 𞤈 𞤏 𞤔 𞤚", TOP), + ("𞤂 𞤖", 0), + ("𞤬 𞤮 𞤻 𞤼 𞤾", TOP | LATIN_X_HEIGHT), + ("𞤤 𞤨 𞤩 𞤭 𞤴 𞤸 𞤺 𞥀", 0), ], }, ScriptClass { name: "Arabic", group: ScriptGroup::Default, tag: Tag::new(b"ARAB"), - index: 1, hint_top_to_bottom: false, - std_chars: &['ل', 'ح', 'ـ', ], + std_chars: "ل ح ـ", blues: &[ - (&['ا', 'إ', 'ل', 'ك', 'ط', 'ظ', ], TOP), - (&['ت', 'ث', 'ط', 'ظ', 'ك', ], 0), - (&['ـ', ], LATIN_NEUTRAL), + ("ا إ ل ك ط ظ", TOP), + ("ت ث ط ظ ك", 0), + ("ـ", LATIN_NEUTRAL), ], }, ScriptClass { name: "Armenian", group: ScriptGroup::Default, tag: Tag::new(b"ARMN"), - index: 2, hint_top_to_bottom: false, - std_chars: &['ս', 'Ս', ], + std_chars: "ս Ս", blues: &[ - (&['Ա', 'Մ', 'Ւ', 'Ս', 'Բ', 'Գ', 'Դ', 'Օ', ], TOP), - (&['Ւ', 'Ո', 'Դ', 'Ճ', 'Շ', 'Ս', 'Տ', 'Օ', ], 0), - (&['ե', 'է', 'ի', 'մ', 'վ', 'ֆ', 'ճ', ], TOP), - (&['ա', 'յ', 'ւ', 'ս', 'գ', 'շ', 'ր', 'օ', ], TOP | LATIN_X_HEIGHT), - (&['հ', 'ո', 'ճ', 'ա', 'ե', 'ծ', 'ս', 'օ', ], 0), - (&['բ', 'ը', 'ի', 'լ', 'ղ', 'պ', 'փ', 'ց', ], 0), + ("Ա Մ Ւ Ս Բ Գ Դ Օ", TOP), + ("Ւ Ո Դ Ճ Շ Ս Տ Օ", 0), + ("ե է ի մ վ ֆ ճ", TOP), + ("ա յ ւ ս գ շ ր օ", TOP | LATIN_X_HEIGHT), + ("հ ո ճ ա ե ծ ս օ", 0), + ("բ ը ի լ ղ պ փ ց", 0), ], }, ScriptClass { name: "Avestan", group: ScriptGroup::Default, tag: Tag::new(b"AVST"), - index: 3, hint_top_to_bottom: false, - std_chars: &['𐬚', ], + std_chars: "𐬚", blues: &[ - (&['𐬀', '𐬁', '𐬐', '𐬛', ], TOP), - (&['𐬀', '𐬁', ], 0), + ("𐬀 𐬁 𐬐 𐬛", TOP), + ("𐬀 𐬁", 0), ], }, ScriptClass { name: "Bamum", group: ScriptGroup::Default, tag: Tag::new(b"BAMU"), - index: 4, hint_top_to_bottom: false, - std_chars: &['ꛁ', 'ꛯ', ], + std_chars: "ꛁ ꛯ", blues: &[ - (&['ꚧ', 'ꚨ', 'ꛛ', 'ꛉ', 'ꛁ', 'ꛈ', 'ꛫ', 'ꛯ', ], TOP), - (&['ꚭ', 'ꚳ', 'ꚶ', 'ꛬ', 'ꚢ', 'ꚽ', 'ꛯ', '꛲', ], 0), + ("ꚧ ꚨ ꛛ ꛉ ꛁ ꛈ ꛫ ꛯ", TOP), + ("ꚭ ꚳ ꚶ ꛬ ꚢ ꚽ ꛯ ꛲", 0), ], }, ScriptClass { name: "Bengali", group: ScriptGroup::Default, tag: Tag::new(b"BENG"), - index: 5, hint_top_to_bottom: true, - std_chars: &['০', '৪', ], + std_chars: "০ ৪", blues: &[ - (&['ই', 'ট', 'ঠ', 'ি', 'ী', 'ৈ', 'ৗ', ], TOP), - (&['ও', 'এ', 'ড', 'ত', 'ন', 'ব', 'ল', 'ক', ], TOP), - (&['অ', 'ড', 'ত', 'ন', 'ব', 'ভ', 'ল', 'ক', ], TOP | LATIN_NEUTRAL | LATIN_X_HEIGHT), - (&['অ', 'ড', 'ত', 'ন', 'ব', 'ভ', 'ল', 'ক', ], 0), + ("ই ট ঠ ি ী ৈ ৗ", TOP), + ("ও এ ড ত ন ব ল ক", TOP), + ("অ ড ত ন ব ভ ল ক", TOP | LATIN_NEUTRAL | LATIN_X_HEIGHT), + ("অ ড ত ন ব ভ ল ক", 0), ], }, ScriptClass { name: "Buhid", group: ScriptGroup::Default, tag: Tag::new(b"BUHD"), - index: 6, hint_top_to_bottom: false, - std_chars: &['ᝋ', 'ᝏ', ], + std_chars: "ᝋ ᝏ", blues: &[ - (&['ᝐ', 'ᝈ', ], TOP), - (&['ᝅ', 'ᝊ', 'ᝎ', ], TOP), - (&['ᝂ', 'ᝃ', 'ᝉ', 'ᝌ', ], TOP | LATIN_X_HEIGHT), - (&['ᝀ', 'ᝃ', 'ᝆ', 'ᝉ', 'ᝋ', 'ᝏ', 'ᝑ', ], 0), + ("ᝐ ᝈ", TOP), + ("ᝅ ᝊ ᝎ", TOP), + ("ᝂ ᝃ ᝉ ᝌ", TOP | LATIN_X_HEIGHT), + ("ᝀ ᝃ ᝆ ᝉ ᝋ ᝏ ᝑ", 0), ], }, ScriptClass { name: "Chakma", group: ScriptGroup::Default, tag: Tag::new(b"CAKM"), - index: 7, hint_top_to_bottom: false, - std_chars: &['𑄤', '𑄉', '𑄛', ], + std_chars: "𑄤 𑄉 𑄛", blues: &[ - (&['𑄃', '𑄅', '𑄉', '𑄙', '𑄗', ], TOP), - (&['𑄅', '𑄛', '𑄝', '𑄗', '𑄓', ], 0), - (&['𑄖', '𑄘', '𑄙', '𑄤', '𑄥', ], 0), + ("𑄃 𑄅 𑄉 𑄙 𑄗", TOP), + ("𑄅 𑄛 𑄝 𑄗 𑄓", 0), + ("𑄖𑄳𑄢 𑄘𑄳𑄢 𑄙𑄳𑄢 𑄤𑄳𑄢 𑄥𑄳𑄢", 0), ], }, ScriptClass { name: "Canadian Syllabics", group: ScriptGroup::Default, tag: Tag::new(b"CANS"), - index: 8, hint_top_to_bottom: false, - std_chars: &['ᑌ', 'ᓚ', ], + std_chars: "ᑌ ᓚ", blues: &[ - (&['ᗜ', 'ᖴ', 'ᐁ', 'ᒣ', 'ᑫ', 'ᑎ', 'ᔑ', 'ᗰ', ], TOP), - (&['ᗶ', 'ᖵ', 'ᒧ', 'ᐃ', 'ᑌ', 'ᒍ', 'ᔑ', 'ᗢ', ], 0), - (&['ᓓ', 'ᓕ', 'ᓀ', 'ᓂ', 'ᓄ', 'ᕄ', 'ᕆ', 'ᘣ', ], TOP | LATIN_X_HEIGHT), - (&['ᕃ', 'ᓂ', 'ᓀ', 'ᕂ', 'ᓗ', 'ᓚ', 'ᕆ', 'ᘣ', ], 0), - (&['ᐪ', 'ᙆ', 'ᣘ', 'ᐢ', 'ᒾ', 'ᣗ', 'ᔆ', ], TOP), - (&['ᙆ', 'ᗮ', 'ᒻ', 'ᐞ', 'ᔆ', 'ᒡ', 'ᒢ', 'ᓑ', ], 0), + ("ᗜ ᖴ ᐁ ᒣ ᑫ ᑎ ᔑ ᗰ", TOP), + ("ᗶ ᖵ ᒧ ᐃ ᑌ ᒍ ᔑ ᗢ", 0), + ("ᓓ ᓕ ᓀ ᓂ ᓄ ᕄ ᕆ ᘣ", TOP | LATIN_X_HEIGHT), + ("ᕃ ᓂ ᓀ ᕂ ᓗ ᓚ ᕆ ᘣ", 0), + ("ᐪ ᙆ ᣘ ᐢ ᒾ ᣗ ᔆ", TOP), + ("ᙆ ᗮ ᒻ ᐞ ᔆ ᒡ ᒢ ᓑ", 0), ], }, ScriptClass { name: "Carian", group: ScriptGroup::Default, tag: Tag::new(b"CARI"), - index: 9, hint_top_to_bottom: false, - std_chars: &['𐊫', '𐋉', ], + std_chars: "𐊫 𐋉", blues: &[ - (&['𐊧', '𐊫', '𐊬', '𐊭', '𐊱', '𐊺', '𐊼', '𐊿', ], TOP), - (&['𐊣', '𐊧', '𐊷', '𐋀', '𐊫', '𐊸', '𐋉', ], 0), + ("𐊧 𐊫 𐊬 𐊭 𐊱 𐊺 𐊼 𐊿", TOP), + ("𐊣 𐊧 𐊷 𐋀 𐊫 𐊸 𐋉", 0), ], }, ScriptClass { name: "Cherokee", group: ScriptGroup::Default, tag: Tag::new(b"CHER"), - index: 10, hint_top_to_bottom: false, - std_chars: &['Ꭴ', 'Ꮕ', 'ꮕ', ], + std_chars: "Ꭴ Ꮕ ꮕ", blues: &[ - (&['Ꮖ', 'Ꮋ', 'Ꭼ', 'Ꮓ', 'Ꭴ', 'Ꮳ', 'Ꭶ', 'Ꮥ', ], TOP), - (&['Ꮖ', 'Ꮋ', 'Ꭼ', 'Ꮓ', 'Ꭴ', 'Ꮳ', 'Ꭶ', 'Ꮥ', ], 0), - (&['ꮒ', 'ꮤ', 'ꮶ', 'ꭴ', 'ꭾ', 'ꮗ', 'ꮝ', 'ꮿ', ], TOP), - (&['ꮖ', 'ꭼ', 'ꮓ', 'ꮠ', 'ꮳ', 'ꭶ', 'ꮥ', 'ꮻ', ], TOP | LATIN_X_HEIGHT), - (&['ꮖ', 'ꭼ', 'ꮓ', 'ꮠ', 'ꮳ', 'ꭶ', 'ꮥ', 'ꮻ', ], 0), - (&['ᏸ', 'ꮐ', 'ꭹ', 'ꭻ', ], 0), + ("Ꮖ Ꮋ Ꭼ Ꮓ Ꭴ Ꮳ Ꭶ Ꮥ", TOP), + ("Ꮖ Ꮋ Ꭼ Ꮓ Ꭴ Ꮳ Ꭶ Ꮥ", 0), + ("ꮒ ꮤ ꮶ ꭴ ꭾ ꮗ ꮝ ꮿ", TOP), + ("ꮖ ꭼ ꮓ ꮠ ꮳ ꭶ ꮥ ꮻ", TOP | LATIN_X_HEIGHT), + ("ꮖ ꭼ ꮓ ꮠ ꮳ ꭶ ꮥ ꮻ", 0), + ("ᏸ ꮐ ꭹ ꭻ", 0), ], }, ScriptClass { name: "Coptic", group: ScriptGroup::Default, tag: Tag::new(b"COPT"), - index: 11, hint_top_to_bottom: false, - std_chars: &['Ⲟ', 'ⲟ', ], + std_chars: "Ⲟ ⲟ", blues: &[ - (&['Ⲍ', 'Ⲏ', 'Ⲡ', 'Ⳟ', 'Ⲟ', 'Ⲑ', 'Ⲥ', 'Ⳋ', ], TOP), - (&['Ⳑ', 'Ⳙ', 'Ⳟ', 'Ⲏ', 'Ⲟ', 'Ⲑ', 'Ⳝ', 'Ⲱ', ], 0), - (&['ⲍ', 'ⲏ', 'ⲡ', 'ⳟ', 'ⲟ', 'ⲑ', 'ⲥ', 'ⳋ', ], TOP | LATIN_X_HEIGHT), - (&['ⳑ', 'ⳙ', 'ⳟ', 'ⲏ', 'ⲟ', 'ⲑ', 'ⳝ', 'Ⳓ', ], 0), + ("Ⲍ Ⲏ Ⲡ Ⳟ Ⲟ Ⲑ Ⲥ Ⳋ", TOP), + ("Ⳑ Ⳙ Ⳟ Ⲏ Ⲟ Ⲑ Ⳝ Ⲱ", 0), + ("ⲍ ⲏ ⲡ ⳟ ⲟ ⲑ ⲥ ⳋ", TOP | LATIN_X_HEIGHT), + ("ⳑ ⳙ ⳟ ⲏ ⲟ ⲑ ⳝ Ⳓ", 0), ], }, ScriptClass { name: "Cypriot", group: ScriptGroup::Default, tag: Tag::new(b"CPRT"), - index: 12, hint_top_to_bottom: false, - std_chars: &['𐠅', '𐠣', ], + std_chars: "𐠅 𐠣", blues: &[ - (&['𐠍', '𐠙', '𐠳', '𐠱', '𐠅', '𐠓', '𐠣', '𐠦', ], TOP), - (&['𐠃', '𐠊', '𐠛', '𐠣', '𐠳', '𐠵', '𐠐', ], 0), - (&['𐠈', '𐠏', '𐠖', ], TOP), - (&['𐠈', '𐠏', '𐠖', ], 0), + ("𐠍 𐠙 𐠳 𐠱 𐠅 𐠓 𐠣 𐠦", TOP), + ("𐠃 𐠊 𐠛 𐠣 𐠳 𐠵 𐠐", 0), + ("𐠈 𐠏 𐠖", TOP), + ("𐠈 𐠏 𐠖", 0), ], }, ScriptClass { name: "Cyrillic", group: ScriptGroup::Default, tag: Tag::new(b"CYRL"), - index: 13, hint_top_to_bottom: false, - std_chars: &['о', 'О', ], + std_chars: "о О", blues: &[ - (&['Б', 'В', 'Е', 'П', 'З', 'О', 'С', 'Э', ], TOP), - (&['Б', 'В', 'Е', 'Ш', 'З', 'О', 'С', 'Э', ], 0), - (&['х', 'п', 'н', 'ш', 'е', 'з', 'о', 'с', ], TOP | LATIN_X_HEIGHT), - (&['х', 'п', 'н', 'ш', 'е', 'з', 'о', 'с', ], 0), - (&['р', 'у', 'ф', ], 0), + ("Б В Е П З О С Э", TOP), + ("Б В Е Ш З О С Э", 0), + ("х п н ш е з о с", TOP | LATIN_X_HEIGHT), + ("х п н ш е з о с", 0), + ("р у ф", 0), ], }, ScriptClass { name: "Devanagari", group: ScriptGroup::Default, tag: Tag::new(b"DEVA"), - index: 14, hint_top_to_bottom: true, - std_chars: &['ठ', 'व', 'ट', ], + std_chars: "ठ व ट", blues: &[ - (&['ई', 'ऐ', 'ओ', 'औ', 'ि', 'ी', 'ो', 'ौ', ], TOP), - (&['क', 'म', 'अ', 'आ', 'थ', 'ध', 'भ', 'श', ], TOP), - (&['क', 'न', 'म', 'उ', 'छ', 'ट', 'ठ', 'ड', ], TOP | LATIN_NEUTRAL | LATIN_X_HEIGHT), - (&['क', 'न', 'म', 'उ', 'छ', 'ट', 'ठ', 'ड', ], 0), - (&['ु', 'ृ', ], 0), + ("ई ऐ ओ औ ि ी ो ौ", TOP), + ("क म अ आ थ ध भ श", TOP), + ("क न म उ छ ट ठ ड", TOP | LATIN_NEUTRAL | LATIN_X_HEIGHT), + ("क न म उ छ ट ठ ड", 0), + ("ु ृ", 0), ], }, ScriptClass { name: "Deseret", group: ScriptGroup::Default, tag: Tag::new(b"DSRT"), - index: 15, hint_top_to_bottom: false, - std_chars: &['𐐄', '𐐬', ], + std_chars: "𐐄 𐐬", blues: &[ - (&['𐐂', '𐐄', '𐐋', '𐐗', '𐐑', ], TOP), - (&['𐐀', '𐐂', '𐐄', '𐐗', '𐐛', ], 0), - (&['𐐪', '𐐬', '𐐳', '𐐿', '𐐹', ], TOP | LATIN_X_HEIGHT), - (&['𐐨', '𐐪', '𐐬', '𐐿', '𐑃', ], 0), + ("𐐂 𐐄 𐐋 𐐗 𐐑", TOP), + ("𐐀 𐐂 𐐄 𐐗 𐐛", 0), + ("𐐪 𐐬 𐐳 𐐿 𐐹", TOP | LATIN_X_HEIGHT), + ("𐐨 𐐪 𐐬 𐐿 𐑃", 0), ], }, ScriptClass { name: "Ethiopic", group: ScriptGroup::Default, tag: Tag::new(b"ETHI"), - index: 16, hint_top_to_bottom: false, - std_chars: &['ዐ', ], + std_chars: "ዐ", blues: &[ - (&['ሀ', 'ሃ', 'ዘ', 'ፐ', 'ማ', 'በ', 'ዋ', 'ዐ', ], TOP), - (&['ለ', 'ሐ', 'በ', 'ዘ', 'ሀ', 'ሪ', 'ዐ', 'ጨ', ], 0), + ("ሀ ሃ ዘ ፐ ማ በ ዋ ዐ", TOP), + ("ለ ሐ በ ዘ ሀ ሪ ዐ ጨ", 0), ], }, ScriptClass { name: "Georgian (Mkhedruli)", group: ScriptGroup::Default, tag: Tag::new(b"GEOR"), - index: 17, hint_top_to_bottom: false, - std_chars: &['ი', 'ე', 'ა', 'Ჿ', ], + std_chars: "ი ე ა Ჿ", blues: &[ - (&['გ', 'დ', 'ე', 'ვ', 'თ', 'ი', 'ო', 'ღ', ], TOP | LATIN_X_HEIGHT), - (&['ა', 'ზ', 'მ', 'ს', 'შ', 'ძ', 'ხ', 'პ', ], 0), - (&['ს', 'ხ', 'ქ', 'ზ', 'მ', 'შ', 'ჩ', 'წ', ], TOP), - (&['ე', 'ვ', 'ჟ', 'ტ', 'უ', 'ფ', 'ქ', 'ყ', ], 0), - (&['Ნ', 'Ჟ', 'Ჳ', 'Ჸ', 'Გ', 'Ე', 'Ო', 'Ჴ', ], TOP), - (&['Ი', 'Ჲ', 'Ო', 'Ჩ', 'Მ', 'Შ', 'Ჯ', 'Ჽ', ], 0), + ("გ დ ე ვ თ ი ო ღ", TOP | LATIN_X_HEIGHT), + ("ა ზ მ ს შ ძ ხ პ", 0), + ("ს ხ ქ ზ მ შ ჩ წ", TOP), + ("ე ვ ჟ ტ უ ფ ქ ყ", 0), + ("Ნ Ჟ Ჳ Ჸ Გ Ე Ო Ჴ", TOP), + ("Ი Ჲ Ო Ჩ Მ Შ Ჯ Ჽ", 0), ], }, ScriptClass { name: "Georgian (Khutsuri)", group: ScriptGroup::Default, tag: Tag::new(b"GEOK"), - index: 18, hint_top_to_bottom: false, - std_chars: &['Ⴖ', 'Ⴑ', 'ⴙ', ], + std_chars: "Ⴖ Ⴑ ⴙ", blues: &[ - (&['Ⴑ', 'Ⴇ', 'Ⴙ', 'Ⴜ', 'Ⴄ', 'Ⴅ', 'Ⴓ', 'Ⴚ', ], TOP), - (&['Ⴄ', 'Ⴅ', 'Ⴇ', 'Ⴈ', 'Ⴆ', 'Ⴑ', 'Ⴊ', 'Ⴋ', ], 0), - (&['ⴁ', 'ⴗ', 'ⴂ', 'ⴄ', 'ⴅ', 'ⴇ', 'ⴔ', 'ⴖ', ], TOP | LATIN_X_HEIGHT), - (&['ⴈ', 'ⴌ', 'ⴖ', 'ⴎ', 'ⴃ', 'ⴆ', 'ⴋ', 'ⴢ', ], 0), - (&['ⴐ', 'ⴑ', 'ⴓ', 'ⴕ', 'ⴙ', 'ⴛ', 'ⴡ', 'ⴣ', ], TOP), - (&['ⴄ', 'ⴅ', 'ⴔ', 'ⴕ', 'ⴁ', 'ⴂ', 'ⴘ', 'ⴝ', ], 0), + ("Ⴑ Ⴇ Ⴙ Ⴜ Ⴄ Ⴅ Ⴓ Ⴚ", TOP), + ("Ⴄ Ⴅ Ⴇ Ⴈ Ⴆ Ⴑ Ⴊ Ⴋ", 0), + ("ⴁ ⴗ ⴂ ⴄ ⴅ ⴇ ⴔ ⴖ", TOP | LATIN_X_HEIGHT), + ("ⴈ ⴌ ⴖ ⴎ ⴃ ⴆ ⴋ ⴢ", 0), + ("ⴐ ⴑ ⴓ ⴕ ⴙ ⴛ ⴡ ⴣ", TOP), + ("ⴄ ⴅ ⴔ ⴕ ⴁ ⴂ ⴘ ⴝ", 0), ], }, ScriptClass { name: "Glagolitic", group: ScriptGroup::Default, tag: Tag::new(b"GLAG"), - index: 19, hint_top_to_bottom: false, - std_chars: &['Ⱅ', 'ⱅ', ], + std_chars: "Ⱅ ⱅ", blues: &[ - (&['Ⰵ', 'Ⱄ', 'Ⱚ', 'Ⰴ', 'Ⰲ', 'Ⰺ', 'Ⱛ', 'Ⰻ', ], TOP), - (&['Ⰵ', 'Ⰴ', 'Ⰲ', 'Ⱚ', 'Ⱎ', 'Ⱑ', 'Ⰺ', 'Ⱄ', ], 0), - (&['ⰵ', 'ⱄ', 'ⱚ', 'ⰴ', 'ⰲ', 'ⰺ', 'ⱛ', 'ⰻ', ], TOP | LATIN_X_HEIGHT), - (&['ⰵ', 'ⰴ', 'ⰲ', 'ⱚ', 'ⱎ', 'ⱑ', 'ⰺ', 'ⱄ', ], 0), + ("Ⰵ Ⱄ Ⱚ Ⰴ Ⰲ Ⰺ Ⱛ Ⰻ", TOP), + ("Ⰵ Ⰴ Ⰲ Ⱚ Ⱎ Ⱑ Ⰺ Ⱄ", 0), + ("ⰵ ⱄ ⱚ ⰴ ⰲ ⰺ ⱛ ⰻ", TOP | LATIN_X_HEIGHT), + ("ⰵ ⰴ ⰲ ⱚ ⱎ ⱑ ⰺ ⱄ", 0), ], }, ScriptClass { name: "Gothic", group: ScriptGroup::Default, tag: Tag::new(b"GOTH"), - index: 20, hint_top_to_bottom: true, - std_chars: &['𐌴', '𐌾', '𐍃', ], + std_chars: "𐌴 𐌾 𐍃", blues: &[ - (&['𐌲', '𐌶', '𐍀', '𐍄', '𐌴', '𐍃', '𐍈', '𐌾', ], TOP), - (&['𐌶', '𐌴', '𐍃', '𐍈', ], 0), + ("𐌲 𐌶 𐍀 𐍄 𐌴 𐍃 𐍈 𐌾", TOP), + ("𐌶 𐌴 𐍃 𐍈", 0), ], }, ScriptClass { name: "Greek", group: ScriptGroup::Default, tag: Tag::new(b"GREK"), - index: 21, hint_top_to_bottom: false, - std_chars: &['ο', 'Ο', ], + std_chars: "ο Ο", blues: &[ - (&['Γ', 'Β', 'Ε', 'Ζ', 'Θ', 'Ο', 'Ω', ], TOP), - (&['Β', 'Δ', 'Ζ', 'Ξ', 'Θ', 'Ο', ], 0), - (&['β', 'θ', 'δ', 'ζ', 'λ', 'ξ', ], TOP), - (&['α', 'ε', 'ι', 'ο', 'π', 'σ', 'τ', 'ω', ], TOP | LATIN_X_HEIGHT), - (&['α', 'ε', 'ι', 'ο', 'π', 'σ', 'τ', 'ω', ], 0), - (&['β', 'γ', 'η', 'μ', 'ρ', 'φ', 'χ', 'ψ', ], 0), + ("Γ Β Ε Ζ Θ Ο Ω", TOP), + ("Β Δ Ζ Ξ Θ Ο", 0), + ("β θ δ ζ λ ξ", TOP), + ("α ε ι ο π σ τ ω", TOP | LATIN_X_HEIGHT), + ("α ε ι ο π σ τ ω", 0), + ("β γ η μ ρ φ χ ψ", 0), ], }, ScriptClass { name: "Gujarati", group: ScriptGroup::Default, tag: Tag::new(b"GUJR"), - index: 22, hint_top_to_bottom: false, - std_chars: &['ટ', '૦', ], + std_chars: "ટ ૦", blues: &[ - (&['ત', 'ન', 'ઋ', 'ઌ', 'છ', 'ટ', 'ર', '૦', ], TOP | LATIN_X_HEIGHT), - (&['ખ', 'ગ', 'ઘ', 'ઞ', 'ઇ', 'ઈ', 'ઠ', 'જ', ], 0), - (&['ઈ', 'ઊ', 'િ', 'ી', 'લ', 'શ', 'જ', 'સ', ], TOP), - (&['ુ', 'ૃ', 'ૄ', 'ખ', 'છ', 'છ', ], 0), - (&['૦', '૧', '૨', '૩', '૭', ], TOP), + ("ત ન ઋ ઌ છ ટ ર ૦", TOP | LATIN_X_HEIGHT), + ("ખ ગ ઘ ઞ ઇ ઈ ઠ જ", 0), + ("ઈ ઊ િ ી લી શ્ચિ જિ સી", TOP), + ("ુ ૃ ૄ ખુ છૃ છૄ", 0), + ("૦ ૧ ૨ ૩ ૭", TOP), ], }, ScriptClass { name: "Gurmukhi", group: ScriptGroup::Default, tag: Tag::new(b"GURU"), - index: 23, hint_top_to_bottom: true, - std_chars: &['ਠ', 'ਰ', '੦', ], + std_chars: "ਠ ਰ ੦", blues: &[ - (&['ਇ', 'ਈ', 'ਉ', 'ਏ', 'ਓ', 'ੳ', 'ਿ', 'ੀ', ], TOP), - (&['ਕ', 'ਗ', 'ਙ', 'ਚ', 'ਜ', 'ਤ', 'ਧ', 'ਸ', ], TOP), - (&['ਕ', 'ਗ', 'ਙ', 'ਚ', 'ਜ', 'ਤ', 'ਧ', 'ਸ', ], TOP | LATIN_NEUTRAL | LATIN_X_HEIGHT), - (&['ਅ', 'ਏ', 'ਓ', 'ਗ', 'ਜ', 'ਠ', 'ਰ', 'ਸ', ], 0), - (&['੦', '੧', '੨', '੩', '੭', ], TOP), + ("ਇ ਈ ਉ ਏ ਓ ੳ ਿ ੀ", TOP), + ("ਕ ਗ ਙ ਚ ਜ ਤ ਧ ਸ", TOP), + ("ਕ ਗ ਙ ਚ ਜ ਤ ਧ ਸ", TOP | LATIN_NEUTRAL | LATIN_X_HEIGHT), + ("ਅ ਏ ਓ ਗ ਜ ਠ ਰ ਸ", 0), + ("੦ ੧ ੨ ੩ ੭", TOP), ], }, ScriptClass { name: "Hebrew", group: ScriptGroup::Default, tag: Tag::new(b"HEBR"), - index: 24, hint_top_to_bottom: false, - std_chars: &['ם', ], + std_chars: "ם", blues: &[ - (&['ב', 'ד', 'ה', 'ח', 'ך', 'כ', 'ם', 'ס', ], TOP | LATIN_LONG), - (&['ב', 'ט', 'כ', 'ם', 'ס', 'צ', ], 0), - (&['ק', 'ך', 'ן', 'ף', 'ץ', ], 0), + ("ב ד ה ח ך כ ם ס", TOP | LATIN_LONG), + ("ב ט כ ם ס צ", 0), + ("ק ך ן ף ץ", 0), ], }, ScriptClass { name: "Kayah Li", group: ScriptGroup::Default, tag: Tag::new(b"KALI"), - index: 25, hint_top_to_bottom: false, - std_chars: &['ꤍ', '꤀', ], + std_chars: "ꤍ ꤀", blues: &[ - (&['꤅', 'ꤏ', '꤁', 'ꤋ', '꤀', 'ꤍ', ], TOP | LATIN_X_HEIGHT), - (&['꤈', 'ꤘ', '꤀', 'ꤍ', 'ꤢ', ], 0), - (&['ꤖ', 'ꤡ', ], TOP), - (&['ꤑ', 'ꤜ', 'ꤞ', ], 0), - (&['ꤑ', 'ꤜ', 'ꤔ', ], 0), + ("꤅ ꤏ ꤁ ꤋ ꤀ ꤍ", TOP | LATIN_X_HEIGHT), + ("꤈ ꤘ ꤀ ꤍ ꤢ", 0), + ("ꤖ ꤡ", TOP), + ("ꤑ ꤜ ꤞ", 0), + ("ꤑ꤬ ꤜ꤭ ꤔ꤬", 0), ], }, ScriptClass { name: "Khmer", group: ScriptGroup::Default, tag: Tag::new(b"KHMR"), - index: 26, hint_top_to_bottom: false, - std_chars: &['០', ], + std_chars: "០", blues: &[ - (&['ខ', 'ទ', 'ន', 'ឧ', 'ឩ', 'ា', ], TOP | LATIN_X_HEIGHT), - (&['ក', 'ក', 'ក', 'ក', ], LATIN_SUB_TOP), - (&['ខ', 'ឃ', 'ច', 'ឋ', 'ប', 'ម', 'យ', 'ឲ', ], 0), - (&['ត', 'រ', 'ឲ', 'អ', ], 0), - (&['ន', 'ង', 'ក', 'ច', 'ន', 'ល', ], 0), + ("ខ ទ ន ឧ ឩ ា", TOP | LATIN_X_HEIGHT), + ("ក្ក ក្ខ ក្គ ក្ថ", LATIN_SUB_TOP), + ("ខ ឃ ច ឋ ប ម យ ឲ", 0), + ("ត្រ រៀ ឲ្យ អឿ", 0), + ("ន្ត្រៃ ង្ខ្យ ក្បៀ ច្រៀ ន្តឿ ល្បឿ", 0), ], }, ScriptClass { name: "Khmer Symbols", group: ScriptGroup::Default, tag: Tag::new(b"KHMS"), - index: 27, hint_top_to_bottom: false, - std_chars: &['᧡', '᧪', ], + std_chars: "᧡ ᧪", blues: &[ - (&['᧠', '᧡', ], TOP | LATIN_X_HEIGHT), - (&['᧶', '᧹', ], 0), + ("᧠ ᧡", TOP | LATIN_X_HEIGHT), + ("᧶ ᧹", 0), ], }, ScriptClass { name: "Kannada", group: ScriptGroup::Default, tag: Tag::new(b"KNDA"), - index: 28, hint_top_to_bottom: false, - std_chars: &['೦', 'ಬ', ], + std_chars: "೦ ಬ", blues: &[ - (&['ಇ', 'ಊ', 'ಐ', 'ಣ', 'ಸ', 'ನ', 'ದ', 'ರ', ], TOP), - (&['ಅ', 'ಉ', 'ಎ', 'ಲ', '೦', '೨', '೬', '೭', ], 0), + ("ಇ ಊ ಐ ಣ ಸಾ ನಾ ದಾ ರಾ", TOP), + ("ಅ ಉ ಎ ಲ ೦ ೨ ೬ ೭", 0), ], }, ScriptClass { name: "Lao", group: ScriptGroup::Default, tag: Tag::new(b"LAOO"), - index: 29, hint_top_to_bottom: false, - std_chars: &['໐', ], + std_chars: "໐", blues: &[ - (&['າ', 'ດ', 'ອ', 'ມ', 'ລ', 'ວ', 'ຣ', 'ງ', ], TOP | LATIN_X_HEIGHT), - (&['າ', 'ອ', 'ບ', 'ຍ', 'ຣ', 'ຮ', 'ວ', 'ຢ', ], 0), - (&['ປ', 'ຢ', 'ຟ', 'ຝ', ], TOP), - (&['ໂ', 'ໄ', 'ໃ', ], TOP), - (&['ງ', 'ຊ', 'ຖ', 'ຽ', 'ໆ', 'ຯ', ], 0), + ("າ ດ ອ ມ ລ ວ ຣ ງ", TOP | LATIN_X_HEIGHT), + ("າ ອ ບ ຍ ຣ ຮ ວ ຢ", 0), + ("ປ ຢ ຟ ຝ", TOP), + ("ໂ ໄ ໃ", TOP), + ("ງ ຊ ຖ ຽ ໆ ຯ", 0), ], }, ScriptClass { name: "Latin", group: ScriptGroup::Default, tag: Tag::new(b"LATN"), - index: 30, hint_top_to_bottom: false, - std_chars: &['o', 'O', '0', ], + std_chars: "o O 0", blues: &[ - (&['T', 'H', 'E', 'Z', 'O', 'C', 'Q', 'S', ], TOP), - (&['H', 'E', 'Z', 'L', 'O', 'C', 'U', 'S', ], 0), - (&['f', 'i', 'j', 'k', 'd', 'b', 'h', ], TOP), - (&['u', 'v', 'x', 'z', 'o', 'e', 's', 'c', ], TOP | LATIN_X_HEIGHT), - (&['n', 'r', 'x', 'z', 'o', 'e', 's', 'c', ], 0), - (&['p', 'q', 'g', 'j', 'y', ], 0), + ("T H E Z O C Q S", TOP), + ("H E Z L O C U S", 0), + ("f i j k d b h", TOP), + ("u v x z o e s c", TOP | LATIN_X_HEIGHT), + ("n r x z o e s c", 0), + ("p q g j y", 0), ], }, ScriptClass { name: "Latin Subscript Fallback", group: ScriptGroup::Default, tag: Tag::new(b"LATB"), - index: 31, hint_top_to_bottom: false, - std_chars: &['ₒ', '₀', ], + std_chars: "ₒ ₀", blues: &[ - (&['₀', '₃', '₅', '₇', '₈', ], TOP), - (&['₀', '₁', '₂', '₃', '₈', ], 0), - (&['ᵢ', 'ⱼ', 'ₕ', 'ₖ', 'ₗ', ], TOP), - (&['ₐ', 'ₑ', 'ₒ', 'ₓ', 'ₙ', 'ₛ', 'ᵥ', 'ᵤ', 'ᵣ', ], TOP | LATIN_X_HEIGHT), - (&['ₐ', 'ₑ', 'ₒ', 'ₓ', 'ₙ', 'ₛ', 'ᵥ', 'ᵤ', 'ᵣ', ], 0), - (&['ᵦ', 'ᵧ', 'ᵨ', 'ᵩ', 'ₚ', ], 0), + ("₀ ₃ ₅ ₇ ₈", TOP), + ("₀ ₁ ₂ ₃ ₈", 0), + ("ᵢ ⱼ ₕ ₖ ₗ", TOP), + ("ₐ ₑ ₒ ₓ ₙ ₛ ᵥ ᵤ ᵣ", TOP | LATIN_X_HEIGHT), + ("ₐ ₑ ₒ ₓ ₙ ₛ ᵥ ᵤ ᵣ", 0), + ("ᵦ ᵧ ᵨ ᵩ ₚ", 0), ], }, ScriptClass { name: "Latin Superscript Fallback", group: ScriptGroup::Default, tag: Tag::new(b"LATP"), - index: 32, hint_top_to_bottom: false, - std_chars: &['ᵒ', 'ᴼ', '⁰', ], + std_chars: "ᵒ ᴼ ⁰", blues: &[ - (&['⁰', '³', '⁵', '⁷', 'ᵀ', 'ᴴ', 'ᴱ', 'ᴼ', ], TOP), - (&['⁰', '¹', '²', '³', 'ᴱ', 'ᴸ', 'ᴼ', 'ᵁ', ], 0), - (&['ᵇ', 'ᵈ', 'ᵏ', 'ʰ', 'ʲ', 'ᶠ', 'ⁱ', ], TOP), - (&['ᵉ', 'ᵒ', 'ʳ', 'ˢ', 'ˣ', 'ᶜ', 'ᶻ', ], TOP | LATIN_X_HEIGHT), - (&['ᵉ', 'ᵒ', 'ʳ', 'ˢ', 'ˣ', 'ᶜ', 'ᶻ', ], 0), - (&['ᵖ', 'ʸ', 'ᵍ', ], 0), + ("⁰ ³ ⁵ ⁷ ᵀ ᴴ ᴱ ᴼ", TOP), + ("⁰ ¹ ² ³ ᴱ ᴸ ᴼ ᵁ", 0), + ("ᵇ ᵈ ᵏ ʰ ʲ ᶠ ⁱ", TOP), + ("ᵉ ᵒ ʳ ˢ ˣ ᶜ ᶻ", TOP | LATIN_X_HEIGHT), + ("ᵉ ᵒ ʳ ˢ ˣ ᶜ ᶻ", 0), + ("ᵖ ʸ ᵍ", 0), ], }, ScriptClass { name: "Lisu", group: ScriptGroup::Default, tag: Tag::new(b"LISU"), - index: 33, hint_top_to_bottom: false, - std_chars: &['ꓳ', ], + std_chars: "ꓳ", blues: &[ - (&['ꓡ', 'ꓧ', 'ꓱ', 'ꓶ', 'ꓩ', 'ꓚ', 'ꓵ', 'ꓳ', ], TOP), - (&['ꓕ', 'ꓜ', 'ꓞ', 'ꓡ', 'ꓛ', 'ꓢ', 'ꓳ', 'ꓴ', ], 0), + ("ꓡ ꓧ ꓱ ꓶ ꓩ ꓚ ꓵ ꓳ", TOP), + ("ꓕ ꓜ ꓞ ꓡ ꓛ ꓢ ꓳ ꓴ", 0), ], }, ScriptClass { name: "Malayalam", group: ScriptGroup::Default, tag: Tag::new(b"MLYM"), - index: 34, hint_top_to_bottom: false, - std_chars: &['ഠ', 'റ', ], + std_chars: "ഠ റ", blues: &[ - (&['ഒ', 'ട', 'ഠ', 'റ', 'ച', 'പ', 'ച', 'പ', ], TOP), - (&['ട', 'ഠ', 'ധ', 'ശ', 'ഘ', 'ച', 'ഥ', 'ല', ], 0), + ("ഒ ട ഠ റ ച പ ച്ച പ്പ", TOP), + ("ട ഠ ധ ശ ഘ ച ഥ ല", 0), ], }, ScriptClass { name: "Medefaidrin", group: ScriptGroup::Default, tag: Tag::new(b"MEDF"), - index: 35, hint_top_to_bottom: false, - std_chars: &['𖹡', '𖹛', '𖹯', ], + std_chars: "𖹡 𖹛 𖹯", blues: &[ - (&['𖹀', '𖹁', '𖹂', '𖹃', '𖹏', '𖹚', '𖹟', ], TOP), - (&['𖹀', '𖹁', '𖹂', '𖹃', '𖹏', '𖹚', '𖹒', '𖹓', ], 0), - (&['𖹤', '𖹬', '𖹧', '𖹴', '𖹶', '𖹾', ], TOP), - (&['𖹠', '𖹡', '𖹢', '𖹹', '𖹳', '𖹮', ], TOP | LATIN_X_HEIGHT), - (&['𖹠', '𖹡', '𖹢', '𖹳', '𖹭', '𖹽', ], 0), - (&['𖹥', '𖹨', '𖹩', ], 0), - (&['𖺀', '𖺅', '𖺈', '𖺄', '𖺍', ], TOP), + ("𖹀 𖹁 𖹂 𖹃 𖹏 𖹚 𖹟", TOP), + ("𖹀 𖹁 𖹂 𖹃 𖹏 𖹚 𖹒 𖹓", 0), + ("𖹤 𖹬 𖹧 𖹴 𖹶 𖹾", TOP), + ("𖹠 𖹡 𖹢 𖹹 𖹳 𖹮", TOP | LATIN_X_HEIGHT), + ("𖹠 𖹡 𖹢 𖹳 𖹭 𖹽", 0), + ("𖹥 𖹨 𖹩", 0), + ("𖺀 𖺅 𖺈 𖺄 𖺍", TOP), ], }, ScriptClass { name: "Mongolian", group: ScriptGroup::Default, tag: Tag::new(b"MONG"), - index: 36, hint_top_to_bottom: true, - std_chars: &['ᡂ', 'ᠪ', ], + std_chars: "ᡂ ᠪ", blues: &[ - (&['ᠳ', 'ᠴ', 'ᠶ', 'ᠽ', 'ᡂ', 'ᡊ', '‍', '‍', ], TOP), - (&['ᡃ', ], 0), + ("ᠳ ᠴ ᠶ ᠽ ᡂ ᡊ ‍ᡡ‍ ‍ᡳ‍", TOP), + ("ᡃ", 0), ], }, ScriptClass { name: "Myanmar", group: ScriptGroup::Default, tag: Tag::new(b"MYMR"), - index: 37, hint_top_to_bottom: false, - std_chars: &['ဝ', 'င', 'ဂ', ], + std_chars: "ဝ င ဂ", blues: &[ - (&['ခ', 'ဂ', 'င', 'ဒ', 'ဝ', 'ၥ', '၊', '။', ], TOP | LATIN_X_HEIGHT), - (&['င', 'ဎ', 'ဒ', 'ပ', 'ဗ', 'ဝ', '၊', '။', ], 0), - (&['ဩ', 'ြ', '၍', '၏', '၆', 'ါ', 'ိ', ], TOP), - (&['ဉ', 'ည', 'ဥ', 'ဩ', 'ဨ', '၂', '၅', '၉', ], 0), + ("ခ ဂ င ဒ ဝ ၥ ၊ ။", TOP | LATIN_X_HEIGHT), + ("င ဎ ဒ ပ ဗ ဝ ၊ ။", 0), + ("ဩ ြ ၍ ၏ ၆ ါ ိ", TOP), + ("ဉ ည ဥ ဩ ဨ ၂ ၅ ၉", 0), ], }, ScriptClass { name: "N'Ko", group: ScriptGroup::Default, tag: Tag::new(b"NKOO"), - index: 38, hint_top_to_bottom: false, - std_chars: &['ߋ', '߀', ], + std_chars: "ߋ ߀", blues: &[ - (&['ߐ', '߉', 'ߒ', 'ߟ', 'ߖ', 'ߜ', 'ߠ', 'ߥ', ], TOP), - (&['߀', 'ߘ', 'ߡ', 'ߠ', 'ߥ', ], 0), - (&['ߏ', 'ߛ', 'ߋ', ], TOP | LATIN_X_HEIGHT), - (&['ߎ', 'ߏ', 'ߛ', 'ߋ', ], 0), + ("ߐ ߉ ߒ ߟ ߖ ߜ ߠ ߥ", TOP), + ("߀ ߘ ߡ ߠ ߥ", 0), + ("ߏ ߛ ߋ", TOP | LATIN_X_HEIGHT), + ("ߎ ߏ ߛ ߋ", 0), ], }, ScriptClass { name: "no script", group: ScriptGroup::Default, tag: Tag::new(b"NONE"), - index: 39, hint_top_to_bottom: false, - std_chars: &[], + std_chars: "", blues: &[], }, ScriptClass { name: "Ol Chiki", group: ScriptGroup::Default, tag: Tag::new(b"OLCK"), - index: 40, hint_top_to_bottom: false, - std_chars: &['ᱛ', ], + std_chars: "ᱛ", blues: &[ - (&['ᱛ', 'ᱜ', 'ᱝ', 'ᱡ', 'ᱢ', 'ᱥ', ], TOP), - (&['ᱛ', 'ᱜ', 'ᱝ', 'ᱡ', 'ᱢ', 'ᱥ', ], 0), + ("ᱛ ᱜ ᱝ ᱡ ᱢ ᱥ", TOP), + ("ᱛ ᱜ ᱝ ᱡ ᱢ ᱥ", 0), ], }, ScriptClass { name: "Old Turkic", group: ScriptGroup::Default, tag: Tag::new(b"ORKH"), - index: 41, hint_top_to_bottom: false, - std_chars: &['𐰗', ], + std_chars: "𐰗", blues: &[ - (&['𐰗', '𐰘', '𐰧', ], TOP), - (&['𐰉', '𐰗', '𐰦', '𐰧', ], 0), + ("𐰗 𐰘 𐰧", TOP), + ("𐰉 𐰗 𐰦 𐰧", 0), ], }, ScriptClass { name: "Osage", group: ScriptGroup::Default, tag: Tag::new(b"OSGE"), - index: 42, hint_top_to_bottom: false, - std_chars: &['𐓂', '𐓪', ], + std_chars: "𐓂 𐓪", blues: &[ - (&['𐒾', '𐓍', '𐓒', '𐓓', '𐒻', '𐓂', '𐒵', '𐓆', ], TOP), - (&['𐒰', '𐓍', '𐓂', '𐒿', '𐓎', '𐒹', ], 0), - (&['𐒼', '𐒽', '𐒾', ], 0), - (&['𐓵', '𐓶', '𐓺', '𐓻', '𐓝', '𐓣', '𐓪', '𐓮', ], TOP | LATIN_X_HEIGHT), - (&['𐓘', '𐓚', '𐓣', '𐓵', '𐓡', '𐓧', '𐓪', '𐓶', ], 0), - (&['𐓤', '𐓦', '𐓸', '𐓹', '𐓛', ], TOP), - (&['𐓤', '𐓥', '𐓦', ], 0), + ("𐒾 𐓍 𐓒 𐓓 𐒻 𐓂 𐒵 𐓆", TOP), + ("𐒰 𐓍 𐓂 𐒿 𐓎 𐒹", 0), + ("𐒼 𐒽 𐒾", 0), + ("𐓵 𐓶 𐓺 𐓻 𐓝 𐓣 𐓪 𐓮", TOP | LATIN_X_HEIGHT), + ("𐓘 𐓚 𐓣 𐓵 𐓡 𐓧 𐓪 𐓶", 0), + ("𐓤 𐓦 𐓸 𐓹 𐓛", TOP), + ("𐓤 𐓥 𐓦", 0), ], }, ScriptClass { name: "Osmanya", group: ScriptGroup::Default, tag: Tag::new(b"OSMA"), - index: 43, hint_top_to_bottom: false, - std_chars: &['𐒆', '𐒠', ], + std_chars: "𐒆 𐒠", blues: &[ - (&['𐒆', '𐒉', '𐒐', '𐒒', '𐒘', '𐒛', '𐒠', '𐒣', ], TOP), - (&['𐒀', '𐒂', '𐒆', '𐒈', '𐒊', '𐒒', '𐒠', '𐒩', ], 0), + ("𐒆 𐒉 𐒐 𐒒 𐒘 𐒛 𐒠 𐒣", TOP), + ("𐒀 𐒂 𐒆 𐒈 𐒊 𐒒 𐒠 𐒩", 0), ], }, ScriptClass { name: "Hanifi Rohingya", group: ScriptGroup::Default, tag: Tag::new(b"ROHG"), - index: 44, hint_top_to_bottom: false, - std_chars: &['𐴰', ], + std_chars: "𐴰", blues: &[ - (&['𐴃', '𐴀', '𐴆', '𐴖', '𐴕', ], TOP), - (&['𐴔', '𐴖', '𐴕', '𐴑', '𐴐', ], 0), - (&['ـ', ], LATIN_NEUTRAL), + ("𐴃 𐴀 𐴆 𐴖 𐴕", TOP), + ("𐴔 𐴖 𐴕 𐴑 𐴐", 0), + ("ـ", LATIN_NEUTRAL), ], }, ScriptClass { name: "Saurashtra", group: ScriptGroup::Default, tag: Tag::new(b"SAUR"), - index: 45, hint_top_to_bottom: false, - std_chars: &['ꢝ', '꣐', ], + std_chars: "ꢝ ꣐", blues: &[ - (&['ꢜ', 'ꢞ', 'ꢳ', 'ꢂ', 'ꢖ', 'ꢒ', 'ꢝ', 'ꢛ', ], TOP), - (&['ꢂ', 'ꢨ', 'ꢺ', 'ꢤ', 'ꢎ', ], 0), + ("ꢜ ꢞ ꢳ ꢂ ꢖ ꢒ ꢝ ꢛ", TOP), + ("ꢂ ꢨ ꢺ ꢤ ꢎ", 0), ], }, ScriptClass { name: "Shavian", group: ScriptGroup::Default, tag: Tag::new(b"SHAW"), - index: 46, hint_top_to_bottom: false, - std_chars: &['𐑴', ], + std_chars: "𐑴", blues: &[ - (&['𐑕', '𐑙', ], TOP), - (&['𐑔', '𐑖', '𐑗', '𐑹', '𐑻', ], 0), - (&['𐑟', '𐑣', ], 0), - (&['𐑱', '𐑲', '𐑳', '𐑴', '𐑸', '𐑺', '𐑼', ], TOP | LATIN_X_HEIGHT), - (&['𐑴', '𐑻', '𐑹', ], 0), + ("𐑕 𐑙", TOP), + ("𐑔 𐑖 𐑗 𐑹 𐑻", 0), + ("𐑟 𐑣", 0), + ("𐑱 𐑲 𐑳 𐑴 𐑸 𐑺 𐑼", TOP | LATIN_X_HEIGHT), + ("𐑴 𐑻 𐑹", 0), ], }, ScriptClass { name: "Sinhala", group: ScriptGroup::Default, tag: Tag::new(b"SINH"), - index: 47, hint_top_to_bottom: false, - std_chars: &['ට', ], + std_chars: "ට", blues: &[ - (&['ඉ', 'ක', 'ඝ', 'ඳ', 'ප', 'ය', 'ල', 'ෆ', ], TOP), - (&['එ', 'ඔ', 'ඝ', 'ජ', 'ට', 'ථ', 'ධ', 'ර', ], 0), - (&['ද', 'ඳ', 'උ', 'ල', 'ත', 'ත', 'බ', 'ද', ], 0), + ("ඉ ක ඝ ඳ ප ය ල ෆ", TOP), + ("එ ඔ ඝ ජ ට ථ ධ ර", 0), + ("ද ඳ උ ල තූ තු බු දු", 0), ], }, ScriptClass { name: "Sundanese", group: ScriptGroup::Default, tag: Tag::new(b"SUND"), - index: 48, hint_top_to_bottom: false, - std_chars: &['᮰', ], + std_chars: "᮰", blues: &[ - (&['ᮋ', 'ᮞ', 'ᮮ', 'ᮽ', '᮰', 'ᮈ', ], TOP), - (&['ᮄ', 'ᮔ', 'ᮕ', 'ᮗ', '᮰', 'ᮆ', 'ᮈ', 'ᮉ', ], 0), - (&['ᮼ', '᳄', ], 0), + ("ᮋ ᮞ ᮮ ᮽ ᮰ ᮈ", TOP), + ("ᮄ ᮔ ᮕ ᮗ ᮰ ᮆ ᮈ ᮉ", 0), + ("ᮼ ᳄", 0), ], }, ScriptClass { name: "Tamil", group: ScriptGroup::Default, tag: Tag::new(b"TAML"), - index: 49, hint_top_to_bottom: false, - std_chars: &['௦', ], + std_chars: "௦", blues: &[ - (&['உ', 'ஒ', 'ஓ', 'ற', 'ஈ', 'க', 'ங', 'ச', ], TOP), - (&['க', 'ச', 'ல', 'ஶ', 'உ', 'ங', 'ட', 'ப', ], 0), + ("உ ஒ ஓ ற ஈ க ங ச", TOP), + ("க ச ல ஶ உ ங ட ப", 0), ], }, ScriptClass { name: "Tai Viet", group: ScriptGroup::Default, tag: Tag::new(b"TAVT"), - index: 50, hint_top_to_bottom: false, - std_chars: &['ꪒ', 'ꪫ', ], + std_chars: "ꪒ ꪫ", blues: &[ - (&['ꪆ', 'ꪔ', 'ꪒ', 'ꪖ', 'ꪫ', ], TOP), - (&['ꪉ', 'ꪫ', 'ꪮ', ], 0), + ("ꪆ ꪔ ꪒ ꪖ ꪫ", TOP), + ("ꪉ ꪫ ꪮ", 0), ], }, ScriptClass { name: "Telugu", group: ScriptGroup::Default, tag: Tag::new(b"TELU"), - index: 51, hint_top_to_bottom: false, - std_chars: &['౦', '౧', ], + std_chars: "౦ ౧", blues: &[ - (&['ఇ', 'ఌ', 'ఙ', 'ఞ', 'ణ', 'ఱ', '౯', ], TOP), - (&['అ', 'క', 'చ', 'ర', 'ఽ', '౨', '౬', ], 0), + ("ఇ ఌ ఙ ఞ ణ ఱ ౯", TOP), + ("అ క చ ర ఽ ౨ ౬", 0), ], }, ScriptClass { name: "Tifinagh", group: ScriptGroup::Default, tag: Tag::new(b"TFNG"), - index: 52, hint_top_to_bottom: false, - std_chars: &['ⵔ', ], + std_chars: "ⵔ", blues: &[ - (&['ⵔ', 'ⵙ', 'ⵛ', 'ⵞ', 'ⴵ', 'ⴼ', 'ⴹ', 'ⵎ', ], TOP), - (&['ⵔ', 'ⵙ', 'ⵛ', 'ⵞ', 'ⴵ', 'ⴼ', 'ⴹ', 'ⵎ', ], 0), + ("ⵔ ⵙ ⵛ ⵞ ⴵ ⴼ ⴹ ⵎ", TOP), + ("ⵔ ⵙ ⵛ ⵞ ⴵ ⴼ ⴹ ⵎ", 0), ], }, ScriptClass { name: "Thai", group: ScriptGroup::Default, tag: Tag::new(b"THAI"), - index: 53, hint_top_to_bottom: false, - std_chars: &['า', 'ๅ', '๐', ], + std_chars: "า ๅ ๐", blues: &[ - (&['บ', 'เ', 'แ', 'อ', 'ก', 'า', ], TOP | LATIN_X_HEIGHT), - (&['บ', 'ป', 'ษ', 'ฯ', 'อ', 'ย', 'ฮ', ], 0), - (&['ป', 'ฝ', 'ฟ', ], TOP), - (&['โ', 'ใ', 'ไ', ], TOP), - (&['ฎ', 'ฏ', 'ฤ', 'ฦ', ], 0), - (&['ญ', 'ฐ', ], 0), - (&['๐', '๑', '๓', ], 0), + ("บ เ แ อ ก า", TOP | LATIN_X_HEIGHT), + ("บ ป ษ ฯ อ ย ฮ", 0), + ("ป ฝ ฟ", TOP), + ("โ ใ ไ", TOP), + ("ฎ ฏ ฤ ฦ", 0), + ("ญ ฐ", 0), + ("๐ ๑ ๓", 0), ], }, ScriptClass { name: "Vai", group: ScriptGroup::Default, tag: Tag::new(b"VAII"), - index: 54, hint_top_to_bottom: false, - std_chars: &['ꘓ', 'ꖜ', 'ꖴ', ], + std_chars: "ꘓ ꖜ ꖴ", blues: &[ - (&['ꗍ', 'ꘖ', 'ꘙ', 'ꘜ', 'ꖜ', 'ꖝ', 'ꔅ', 'ꕢ', ], TOP), - (&['ꗍ', 'ꘖ', 'ꘙ', 'ꗞ', 'ꔅ', 'ꕢ', 'ꖜ', 'ꔆ', ], 0), + ("ꗍ ꘖ ꘙ ꘜ ꖜ ꖝ ꔅ ꕢ", TOP), + ("ꗍ ꘖ ꘙ ꗞ ꔅ ꕢ ꖜ ꔆ", 0), ], }, ScriptClass { name: "Limbu", group: ScriptGroup::Indic, tag: Tag::new(b"LIMB"), - index: 55, hint_top_to_bottom: false, - std_chars: &['o', ], + std_chars: "o", blues: &[], }, ScriptClass { name: "Oriya", group: ScriptGroup::Indic, tag: Tag::new(b"ORYA"), - index: 56, hint_top_to_bottom: false, - std_chars: &['o', ], + std_chars: "o", blues: &[], }, ScriptClass { name: "Syloti Nagri", group: ScriptGroup::Indic, tag: Tag::new(b"SYLO"), - index: 57, hint_top_to_bottom: false, - std_chars: &['o', ], + std_chars: "o", blues: &[], }, ScriptClass { name: "Tibetan", group: ScriptGroup::Indic, tag: Tag::new(b"TIBT"), - index: 58, hint_top_to_bottom: false, - std_chars: &['o', ], + std_chars: "o", blues: &[], }, ScriptClass { name: "CJKV ideographs", group: ScriptGroup::Cjk, tag: Tag::new(b"HANI"), - index: 59, hint_top_to_bottom: false, - std_chars: &['田', '囗', ], + std_chars: "田 囗", blues: &[ - (&['他', '们', '你', '來', '們', '到', '和', '地', '对', '對', '就', '席', '我', '时', '時', '會', '来', '為', '能', '舰', '說', '说', '这', '這', '齊', '|', '军', '同', '已', '愿', '既', '星', '是', '景', '民', '照', '现', '現', '理', '用', '置', '要', '軍', '那', '配', '里', '開', '雷', '露', '面', '顾', ], TOP), - (&['个', '为', '人', '他', '以', '们', '你', '來', '個', '們', '到', '和', '大', '对', '對', '就', '我', '时', '時', '有', '来', '為', '要', '說', '说', '|', '主', '些', '因', '它', '想', '意', '理', '生', '當', '看', '着', '置', '者', '自', '著', '裡', '过', '还', '进', '進', '過', '道', '還', '里', '面', ], 0), - (&['些', '们', '你', '來', '們', '到', '和', '地', '她', '将', '將', '就', '年', '得', '情', '最', '样', '樣', '理', '能', '說', '说', '这', '這', '通', '|', '即', '吗', '吧', '听', '呢', '品', '响', '嗎', '师', '師', '收', '断', '斷', '明', '眼', '間', '间', '际', '陈', '限', '除', '陳', '随', '際', '隨', ], CJK_HORIZ), - (&['事', '前', '學', '将', '將', '情', '想', '或', '政', '斯', '新', '样', '樣', '民', '沒', '没', '然', '特', '现', '現', '球', '第', '經', '谁', '起', '|', '例', '別', '别', '制', '动', '動', '吗', '嗎', '增', '指', '明', '朝', '期', '构', '物', '确', '种', '調', '调', '費', '费', '那', '都', '間', '间', ], CJK_HORIZ | CJK_RIGHT), + ("他 们 你 來 們 到 和 地 对 對 就 席 我 时 時 會 来 為 能 舰 說 说 这 這 齊 | 军 同 已 愿 既 星 是 景 民 照 现 現 理 用 置 要 軍 那 配 里 開 雷 露 面 顾", TOP), + ("个 为 人 他 以 们 你 來 個 們 到 和 大 对 對 就 我 时 時 有 来 為 要 說 说 | 主 些 因 它 想 意 理 生 當 看 着 置 者 自 著 裡 过 还 进 進 過 道 還 里 面", 0), + (" 些 们 你 來 們 到 和 地 她 将 將 就 年 得 情 最 样 樣 理 能 說 说 这 這 通 | 即 吗 吧 听 呢 品 响 嗎 师 師 收 断 斷 明 眼 間 间 际 陈 限 除 陳 随 際 隨", CJK_HORIZ), + ("事 前 學 将 將 情 想 或 政 斯 新 样 樣 民 沒 没 然 特 现 現 球 第 經 谁 起 | 例 別 别 制 动 動 吗 嗎 增 指 明 朝 期 构 物 确 种 調 调 費 费 那 都 間 间", CJK_HORIZ | CJK_RIGHT), ], }, ]; -impl ScriptClass { +#[allow(unused)]impl ScriptClass { pub const ADLM: usize = 0; pub const ARAB: usize = 1; pub const ARMN: usize = 2; @@ -969,7 +909,7 @@ pub(super) const STYLE_CLASSES: &[StyleClass] = &[ StyleClass { name: "CJKV ideographs", index: 89, script: &SCRIPT_CLASSES[59], feature: None }, ]; -impl StyleClass { +#[allow(unused)]impl StyleClass { pub const ADLM: usize = 0; pub const ARAB: usize = 1; pub const ARMN: usize = 2; diff --git a/skrifa/scripts/gen_autohint_styles.py b/skrifa/scripts/gen_autohint_styles.py index 47562b2b0..62f163246 100644 --- a/skrifa/scripts/gen_autohint_styles.py +++ b/skrifa/scripts/gen_autohint_styles.py @@ -67,7 +67,7 @@ "name": "Adlam", "tag": "ADLM", "hint_top_to_bottom": False, - "std_chars": ['𞤌', '𞤮'], # 𞤌 𞤮 + "std_chars": "𞤌 𞤮", # 𞤌 𞤮 "base_ranges": [ (0x1E900, 0x1E95F), # Adlam ], @@ -75,17 +75,17 @@ (0x1D944, 0x1E94A), ], "blues": [ - (['𞤌', '𞤅', '𞤈', '𞤏', '𞤔', '𞤚'], "TOP"), - (['𞤂', '𞤖'], "0"), - (['𞤬', '𞤮', '𞤻', '𞤼', '𞤾'], "TOP | LATIN_X_HEIGHT"), - (['𞤤', '𞤨', '𞤩', '𞤭', '𞤴', '𞤸', '𞤺', '𞥀'], "0"), + ("𞤌 𞤅 𞤈 𞤏 𞤔 𞤚", "TOP"), + ("𞤂 𞤖", "0"), + ("𞤬 𞤮 𞤻 𞤼 𞤾", "TOP | LATIN_X_HEIGHT"), + ("𞤤 𞤨 𞤩 𞤭 𞤴 𞤸 𞤺 𞥀", "0"), ], }, { "name": "Arabic", "tag": "ARAB", "hint_top_to_bottom": False, - "std_chars": ['ل', 'ح', 'ـ'], # ل ح ـ + "std_chars": "ل ح ـ", # ل ح ـ "base_ranges": [ (0x0600, 0x06FF), # Arabic (0x0750, 0x07FF), # Arabic Supplement @@ -116,16 +116,16 @@ (0xFE7E, 0xFE7E), ], "blues": [ - (['ا', 'إ', 'ل', 'ك', 'ط', 'ظ'], "TOP"), - (['ت', 'ث', 'ط', 'ظ', 'ك'], "0"), - (['ـ'], "LATIN_NEUTRAL"), + ("ا إ ل ك ط ظ", "TOP"), + ("ت ث ط ظ ك", "0"), + ("ـ", "LATIN_NEUTRAL"), ], }, { "name": "Armenian", "tag": "ARMN", "hint_top_to_bottom": False, - "std_chars": ['ս', 'Ս'], # ս Ս + "std_chars": "ս Ս", # ս Ս "base_ranges": [ (0x0530, 0x058F), # Armenian (0xFB13, 0xFB17), # Alphab. Present. Forms (Armenian) @@ -134,19 +134,19 @@ (0x0559, 0x055F), ], "blues": [ - (['Ա', 'Մ', 'Ւ', 'Ս', 'Բ', 'Գ', 'Դ', 'Օ'], "TOP"), - (['Ւ', 'Ո', 'Դ', 'Ճ', 'Շ', 'Ս', 'Տ', 'Օ'], "0"), - (['ե', 'է', 'ի', 'մ', 'վ', 'ֆ', 'ճ'], "TOP"), - (['ա', 'յ', 'ւ', 'ս', 'գ', 'շ', 'ր', 'օ'], "TOP | LATIN_X_HEIGHT"), - (['հ', 'ո', 'ճ', 'ա', 'ե', 'ծ', 'ս', 'օ'], "0"), - (['բ', 'ը', 'ի', 'լ', 'ղ', 'պ', 'փ', 'ց'], "0"), + ("Ա Մ Ւ Ս Բ Գ Դ Օ", "TOP"), + ("Ւ Ո Դ Ճ Շ Ս Տ Օ", "0"), + ("ե է ի մ վ ֆ ճ", "TOP"), + ("ա յ ւ ս գ շ ր օ", "TOP | LATIN_X_HEIGHT"), + ("հ ո ճ ա ե ծ ս օ", "0"), + ("բ ը ի լ ղ պ փ ց", "0"), ], }, { "name": "Avestan", "tag": "AVST", "hint_top_to_bottom": False, - "std_chars": ['𐬚'], # 𐬚 + "std_chars": "𐬚", # 𐬚 "base_ranges": [ (0x10B00, 0x10B3F), # Avestan ], @@ -154,34 +154,33 @@ (0x10B39, 0x10B3F), ], "blues": [ - (['𐬀', '𐬁', '𐬐', '𐬛'], "TOP"), - (['𐬀', '𐬁'], "0"), + ("𐬀 𐬁 𐬐 𐬛", "TOP"), + ("𐬀 𐬁", "0"), ], }, { "name": "Bamum", "tag": "BAMU", "hint_top_to_bottom": False, - "std_chars": ['ꛁ', 'ꛯ'], # ꛁ ꛯ + "std_chars": "ꛁ ꛯ", # ꛁ ꛯ "base_ranges": [ (0xA6A0, 0xA6FF), # Bamum - # https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afranges.c#L139 - # "The characters in the Bamum supplement are pictograms, not (directly) related to the syllabic Bamum script" + # This is commented out in FreeType # (0x16800, 0x16A3F), # Bamum Supplement ], "non_base_ranges": [ (0xA6F0, 0xA6F1), ], "blues": [ - (['ꚧ', 'ꚨ', 'ꛛ', 'ꛉ', 'ꛁ', 'ꛈ', 'ꛫ', 'ꛯ'], "TOP"), - (['ꚭ', 'ꚳ', 'ꚶ', 'ꛬ', 'ꚢ', 'ꚽ', 'ꛯ', '꛲'], "0"), + ("ꚧ ꚨ ꛛ ꛉ ꛁ ꛈ ꛫ ꛯ", "TOP"), + ("ꚭ ꚳ ꚶ ꛬ ꚢ ꚽ ꛯ ꛲", "0"), ], }, { "name": "Bengali", "tag": "BENG", "hint_top_to_bottom": True, - "std_chars": ['০', '৪'], # ০ ৪ + "std_chars": "০ ৪", # ০ ৪ "base_ranges": [ (0x0980, 0x09FF), # Bengali ], @@ -194,17 +193,17 @@ (0x09FE, 0x09FE), ], "blues": [ - (['ই', 'ট', 'ঠ', 'ি', 'ী', 'ৈ', 'ৗ'], "TOP"), - (['ও', 'এ', 'ড', 'ত', 'ন', 'ব', 'ল', 'ক'], "TOP"), - (['অ', 'ড', 'ত', 'ন', 'ব', 'ভ', 'ল', 'ক'], "TOP | LATIN_NEUTRAL | LATIN_X_HEIGHT"), - (['অ', 'ড', 'ত', 'ন', 'ব', 'ভ', 'ল', 'ক'], "0"), + ("ই ট ঠ ি ী ৈ ৗ", "TOP"), + ("ও এ ড ত ন ব ল ক", "TOP"), + ("অ ড ত ন ব ভ ল ক", "TOP | LATIN_NEUTRAL | LATIN_X_HEIGHT"), + ("অ ড ত ন ব ভ ল ক", "0"), ], }, { "name": "Buhid", "tag": "BUHD", "hint_top_to_bottom": False, - "std_chars": ['ᝋ', 'ᝏ'], # ᝋ ᝏ + "std_chars": "ᝋ ᝏ", # ᝋ ᝏ "base_ranges": [ (0x1740, 0x175F), # Buhid ], @@ -212,17 +211,17 @@ (0x1752, 0x1753), ], "blues": [ - (['ᝐ', 'ᝈ'], "TOP"), - (['ᝅ', 'ᝊ', 'ᝎ'], "TOP"), - (['ᝂ', 'ᝃ', 'ᝉ', 'ᝌ'], "TOP | LATIN_X_HEIGHT"), - (['ᝀ', 'ᝃ', 'ᝆ', 'ᝉ', 'ᝋ', 'ᝏ', 'ᝑ'], "0"), + ("ᝐ ᝈ", "TOP"), + ("ᝅ ᝊ ᝎ", "TOP"), + ("ᝂ ᝃ ᝉ ᝌ", "TOP | LATIN_X_HEIGHT"), + ("ᝀ ᝃ ᝆ ᝉ ᝋ ᝏ ᝑ", "0"), ], }, { "name": "Chakma", "tag": "CAKM", "hint_top_to_bottom": False, - "std_chars": ['𑄤', '𑄉', '𑄛'], # 𑄤 𑄉 𑄛 + "std_chars": "𑄤 𑄉 𑄛", # 𑄤 𑄉 𑄛 "base_ranges": [ (0x11100, 0x1114F), # Chakma ], @@ -232,16 +231,16 @@ (0x11146, 0x11146), ], "blues": [ - (['𑄃', '𑄅', '𑄉', '𑄙', '𑄗'], "TOP"), - (['𑄅', '𑄛', '𑄝', '𑄗', '𑄓'], "0"), - (['𑄖', '𑄘', '𑄙', '𑄤', '𑄥'], "0"), + ("𑄃 𑄅 𑄉 𑄙 𑄗", "TOP"), + ("𑄅 𑄛 𑄝 𑄗 𑄓", "0"), + ("𑄖𑄳𑄢 𑄘𑄳𑄢 𑄙𑄳𑄢 𑄤𑄳𑄢 𑄥𑄳𑄢", "0"), ], }, { "name": "Canadian Syllabics", "tag": "CANS", "hint_top_to_bottom": False, - "std_chars": ['ᑌ', 'ᓚ'], # ᑌ ᓚ + "std_chars": "ᑌ ᓚ", # ᑌ ᓚ "base_ranges": [ (0x1400, 0x167F), # Unified Canadian Aboriginal Syllabics (0x18B0, 0x18FF), # Unified Canadian Aboriginal Syllabics Extended @@ -249,34 +248,34 @@ "non_base_ranges": [ ], "blues": [ - (['ᗜ', 'ᖴ', 'ᐁ', 'ᒣ', 'ᑫ', 'ᑎ', 'ᔑ', 'ᗰ'], "TOP"), - (['ᗶ', 'ᖵ', 'ᒧ', 'ᐃ', 'ᑌ', 'ᒍ', 'ᔑ', 'ᗢ'], "0"), - (['ᓓ', 'ᓕ', 'ᓀ', 'ᓂ', 'ᓄ', 'ᕄ', 'ᕆ', 'ᘣ'], "TOP | LATIN_X_HEIGHT"), - (['ᕃ', 'ᓂ', 'ᓀ', 'ᕂ', 'ᓗ', 'ᓚ', 'ᕆ', 'ᘣ'], "0"), - (['ᐪ', 'ᙆ', 'ᣘ', 'ᐢ', 'ᒾ', 'ᣗ', 'ᔆ'], "TOP"), - (['ᙆ', 'ᗮ', 'ᒻ', 'ᐞ', 'ᔆ', 'ᒡ', 'ᒢ', 'ᓑ'], "0"), + ("ᗜ ᖴ ᐁ ᒣ ᑫ ᑎ ᔑ ᗰ", "TOP"), + ("ᗶ ᖵ ᒧ ᐃ ᑌ ᒍ ᔑ ᗢ", "0"), + ("ᓓ ᓕ ᓀ ᓂ ᓄ ᕄ ᕆ ᘣ", "TOP | LATIN_X_HEIGHT"), + ("ᕃ ᓂ ᓀ ᕂ ᓗ ᓚ ᕆ ᘣ", "0"), + ("ᐪ ᙆ ᣘ ᐢ ᒾ ᣗ ᔆ", "TOP"), + ("ᙆ ᗮ ᒻ ᐞ ᔆ ᒡ ᒢ ᓑ", "0"), ], }, { "name": "Carian", "tag": "CARI", "hint_top_to_bottom": False, - "std_chars": ['𐊫', '𐋉'], # 𐊫 𐋉 + "std_chars": "𐊫 𐋉", # 𐊫 𐋉 "base_ranges": [ (0x102A0, 0x102DF), # Carian ], "non_base_ranges": [ ], "blues": [ - (['𐊧', '𐊫', '𐊬', '𐊭', '𐊱', '𐊺', '𐊼', '𐊿'], "TOP"), - (['𐊣', '𐊧', '𐊷', '𐋀', '𐊫', '𐊸', '𐋉'], "0"), + ("𐊧 𐊫 𐊬 𐊭 𐊱 𐊺 𐊼 𐊿", "TOP"), + ("𐊣 𐊧 𐊷 𐋀 𐊫 𐊸 𐋉", "0"), ], }, { "name": "Cherokee", "tag": "CHER", "hint_top_to_bottom": False, - "std_chars": ['Ꭴ', 'Ꮕ', 'ꮕ'], # Ꭴ Ꮕ ꮕ + "std_chars": "Ꭴ Ꮕ ꮕ", # Ꭴ Ꮕ ꮕ "base_ranges": [ (0x13A0, 0x13FF), # Cherokee (0xAB70, 0xABBF), # Cherokee Supplement @@ -284,19 +283,19 @@ "non_base_ranges": [ ], "blues": [ - (['Ꮖ', 'Ꮋ', 'Ꭼ', 'Ꮓ', 'Ꭴ', 'Ꮳ', 'Ꭶ', 'Ꮥ'], "TOP"), - (['Ꮖ', 'Ꮋ', 'Ꭼ', 'Ꮓ', 'Ꭴ', 'Ꮳ', 'Ꭶ', 'Ꮥ'], "0"), - (['ꮒ', 'ꮤ', 'ꮶ', 'ꭴ', 'ꭾ', 'ꮗ', 'ꮝ', 'ꮿ'], "TOP"), - (['ꮖ', 'ꭼ', 'ꮓ', 'ꮠ', 'ꮳ', 'ꭶ', 'ꮥ', 'ꮻ'], "TOP | LATIN_X_HEIGHT"), - (['ꮖ', 'ꭼ', 'ꮓ', 'ꮠ', 'ꮳ', 'ꭶ', 'ꮥ', 'ꮻ'], "0"), - (['ᏸ', 'ꮐ', 'ꭹ', 'ꭻ'], "0"), + ("Ꮖ Ꮋ Ꭼ Ꮓ Ꭴ Ꮳ Ꭶ Ꮥ", "TOP"), + ("Ꮖ Ꮋ Ꭼ Ꮓ Ꭴ Ꮳ Ꭶ Ꮥ", "0"), + ("ꮒ ꮤ ꮶ ꭴ ꭾ ꮗ ꮝ ꮿ", "TOP"), + ("ꮖ ꭼ ꮓ ꮠ ꮳ ꭶ ꮥ ꮻ", "TOP | LATIN_X_HEIGHT"), + ("ꮖ ꭼ ꮓ ꮠ ꮳ ꭶ ꮥ ꮻ", "0"), + ("ᏸ ꮐ ꭹ ꭻ", "0"), ], }, { "name": "Coptic", "tag": "COPT", "hint_top_to_bottom": False, - "std_chars": ['Ⲟ', 'ⲟ'], # Ⲟ ⲟ + "std_chars": "Ⲟ ⲟ", # Ⲟ ⲟ "base_ranges": [ (0x2C80, 0x2CFF), # Coptic ], @@ -304,34 +303,34 @@ (0x2CEF, 0x2CF1), ], "blues": [ - (['Ⲍ', 'Ⲏ', 'Ⲡ', 'Ⳟ', 'Ⲟ', 'Ⲑ', 'Ⲥ', 'Ⳋ'], "TOP"), - (['Ⳑ', 'Ⳙ', 'Ⳟ', 'Ⲏ', 'Ⲟ', 'Ⲑ', 'Ⳝ', 'Ⲱ'], "0"), - (['ⲍ', 'ⲏ', 'ⲡ', 'ⳟ', 'ⲟ', 'ⲑ', 'ⲥ', 'ⳋ'], "TOP | LATIN_X_HEIGHT"), - (['ⳑ', 'ⳙ', 'ⳟ', 'ⲏ', 'ⲟ', 'ⲑ', 'ⳝ', 'Ⳓ'], "0"), + ("Ⲍ Ⲏ Ⲡ Ⳟ Ⲟ Ⲑ Ⲥ Ⳋ", "TOP"), + ("Ⳑ Ⳙ Ⳟ Ⲏ Ⲟ Ⲑ Ⳝ Ⲱ", "0"), + ("ⲍ ⲏ ⲡ ⳟ ⲟ ⲑ ⲥ ⳋ", "TOP | LATIN_X_HEIGHT"), + ("ⳑ ⳙ ⳟ ⲏ ⲟ ⲑ ⳝ Ⳓ", "0"), ], }, { "name": "Cypriot", "tag": "CPRT", "hint_top_to_bottom": False, - "std_chars": ['𐠅', '𐠣'], # 𐠅 𐠣 + "std_chars": "𐠅 𐠣", # 𐠅 𐠣 "base_ranges": [ (0x10800, 0x1083F), # Cypriot ], "non_base_ranges": [ ], "blues": [ - (['𐠍', '𐠙', '𐠳', '𐠱', '𐠅', '𐠓', '𐠣', '𐠦'], "TOP"), - (['𐠃', '𐠊', '𐠛', '𐠣', '𐠳', '𐠵', '𐠐'], "0"), - (['𐠈', '𐠏', '𐠖'], "TOP"), - (['𐠈', '𐠏', '𐠖'], "0"), + ("𐠍 𐠙 𐠳 𐠱 𐠅 𐠓 𐠣 𐠦", "TOP"), + ("𐠃 𐠊 𐠛 𐠣 𐠳 𐠵 𐠐", "0"), + ("𐠈 𐠏 𐠖", "TOP"), + ("𐠈 𐠏 𐠖", "0"), ], }, { "name": "Cyrillic", "tag": "CYRL", "hint_top_to_bottom": False, - "std_chars": ['о', 'О'], # о О + "std_chars": "о О", # о О "base_ranges": [ (0x0400, 0x04FF), # Cyrillic (0x0500, 0x052F), # Cyrillic Supplement @@ -346,18 +345,18 @@ (0xA69E, 0xA69F), ], "blues": [ - (['Б', 'В', 'Е', 'П', 'З', 'О', 'С', 'Э'], "TOP"), - (['Б', 'В', 'Е', 'Ш', 'З', 'О', 'С', 'Э'], "0"), - (['х', 'п', 'н', 'ш', 'е', 'з', 'о', 'с'], "TOP | LATIN_X_HEIGHT"), - (['х', 'п', 'н', 'ш', 'е', 'з', 'о', 'с'], "0"), - (['р', 'у', 'ф'], "0"), + ("Б В Е П З О С Э", "TOP"), + ("Б В Е Ш З О С Э", "0"), + ("х п н ш е з о с", "TOP | LATIN_X_HEIGHT"), + ("х п н ш е з о с", "0"), + ("р у ф", "0"), ], }, { "name": "Devanagari", "tag": "DEVA", "hint_top_to_bottom": True, - "std_chars": ['ठ', 'व', 'ट'], # ठ व ट + "std_chars": "ठ व ट", # ठ व ट "base_ranges": [ (0x0900, 0x093B), # Devanagari (0x093D, 0x0950), # ... continued @@ -377,35 +376,35 @@ (0xA8FF, 0xA8FF), ], "blues": [ - (['ई', 'ऐ', 'ओ', 'औ', 'ि', 'ी', 'ो', 'ौ'], "TOP"), - (['क', 'म', 'अ', 'आ', 'थ', 'ध', 'भ', 'श'], "TOP"), - (['क', 'न', 'म', 'उ', 'छ', 'ट', 'ठ', 'ड'], "TOP | LATIN_NEUTRAL | LATIN_X_HEIGHT"), - (['क', 'न', 'म', 'उ', 'छ', 'ट', 'ठ', 'ड'], "0"), - (['ु', 'ृ'], "0"), + ("ई ऐ ओ औ ि ी ो ौ", "TOP"), + ("क म अ आ थ ध भ श", "TOP"), + ("क न म उ छ ट ठ ड", "TOP | LATIN_NEUTRAL | LATIN_X_HEIGHT"), + ("क न म उ छ ट ठ ड", "0"), + ("ु ृ", "0"), ], }, { "name": "Deseret", "tag": "DSRT", "hint_top_to_bottom": False, - "std_chars": ['𐐄', '𐐬'], # 𐐄 𐐬 + "std_chars": "𐐄 𐐬", # 𐐄 𐐬 "base_ranges": [ (0x10400, 0x1044F), # Deseret ], "non_base_ranges": [ ], "blues": [ - (['𐐂', '𐐄', '𐐋', '𐐗', '𐐑'], "TOP"), - (['𐐀', '𐐂', '𐐄', '𐐗', '𐐛'], "0"), - (['𐐪', '𐐬', '𐐳', '𐐿', '𐐹'], "TOP | LATIN_X_HEIGHT"), - (['𐐨', '𐐪', '𐐬', '𐐿', '𐑃'], "0"), + ("𐐂 𐐄 𐐋 𐐗 𐐑", "TOP"), + ("𐐀 𐐂 𐐄 𐐗 𐐛", "0"), + ("𐐪 𐐬 𐐳 𐐿 𐐹", "TOP | LATIN_X_HEIGHT"), + ("𐐨 𐐪 𐐬 𐐿 𐑃", "0"), ], }, { "name": "Ethiopic", "tag": "ETHI", "hint_top_to_bottom": False, - "std_chars": ['ዐ'], # ዐ + "std_chars": "ዐ", # ዐ "base_ranges": [ (0x1200, 0x137F), # Ethiopic (0x1380, 0x139F), # Ethiopic Supplement @@ -416,15 +415,15 @@ (0x135D, 0x135F), ], "blues": [ - (['ሀ', 'ሃ', 'ዘ', 'ፐ', 'ማ', 'በ', 'ዋ', 'ዐ'], "TOP"), - (['ለ', 'ሐ', 'በ', 'ዘ', 'ሀ', 'ሪ', 'ዐ', 'ጨ'], "0"), + ("ሀ ሃ ዘ ፐ ማ በ ዋ ዐ", "TOP"), + ("ለ ሐ በ ዘ ሀ ሪ ዐ ጨ", "0"), ], }, { "name": "Georgian (Mkhedruli)", "tag": "GEOR", "hint_top_to_bottom": False, - "std_chars": ['ი', 'ე', 'ა', 'Ჿ'], # ი ე ა Ი + "std_chars": "ი ე ა Ჿ", # ი ე ა Ი "base_ranges": [ (0x10D0, 0x10FF), # Georgian (Mkhedruli) (0x1C90, 0x1CBF), # Georgian Extended (Mtavruli) @@ -432,19 +431,19 @@ "non_base_ranges": [ ], "blues": [ - (['გ', 'დ', 'ე', 'ვ', 'თ', 'ი', 'ო', 'ღ'], "TOP | LATIN_X_HEIGHT"), - (['ა', 'ზ', 'მ', 'ს', 'შ', 'ძ', 'ხ', 'პ'], "0"), - (['ს', 'ხ', 'ქ', 'ზ', 'მ', 'შ', 'ჩ', 'წ'], "TOP"), - (['ე', 'ვ', 'ჟ', 'ტ', 'უ', 'ფ', 'ქ', 'ყ'], "0"), - (['Ნ', 'Ჟ', 'Ჳ', 'Ჸ', 'Გ', 'Ე', 'Ო', 'Ჴ'], "TOP"), - (['Ი', 'Ჲ', 'Ო', 'Ჩ', 'Მ', 'Შ', 'Ჯ', 'Ჽ'], "0"), + ("გ დ ე ვ თ ი ო ღ", "TOP | LATIN_X_HEIGHT"), + ("ა ზ მ ს შ ძ ხ პ", "0"), + ("ს ხ ქ ზ მ შ ჩ წ", "TOP"), + ("ე ვ ჟ ტ უ ფ ქ ყ", "0"), + ("Ნ Ჟ Ჳ Ჸ Გ Ე Ო Ჴ", "TOP"), + ("Ი Ჲ Ო Ჩ Მ Შ Ჯ Ჽ", "0"), ], }, { "name": "Georgian (Khutsuri)", "tag": "GEOK", "hint_top_to_bottom": False, - "std_chars": ['Ⴖ', 'Ⴑ', 'ⴙ'], # Ⴖ Ⴑ ⴙ + "std_chars": "Ⴖ Ⴑ ⴙ", # Ⴖ Ⴑ ⴙ "base_ranges": [ (0x10A0, 0x10CD), # Georgian (Asomtavruli) (0x2D00, 0x2D2D), # Georgian Supplement (Nuskhuri) @@ -452,19 +451,19 @@ "non_base_ranges": [ ], "blues": [ - (['Ⴑ', 'Ⴇ', 'Ⴙ', 'Ⴜ', 'Ⴄ', 'Ⴅ', 'Ⴓ', 'Ⴚ'], "TOP"), - (['Ⴄ', 'Ⴅ', 'Ⴇ', 'Ⴈ', 'Ⴆ', 'Ⴑ', 'Ⴊ', 'Ⴋ'], "0"), - (['ⴁ', 'ⴗ', 'ⴂ', 'ⴄ', 'ⴅ', 'ⴇ', 'ⴔ', 'ⴖ'], "TOP | LATIN_X_HEIGHT"), - (['ⴈ', 'ⴌ', 'ⴖ', 'ⴎ', 'ⴃ', 'ⴆ', 'ⴋ', 'ⴢ'], "0"), - (['ⴐ', 'ⴑ', 'ⴓ', 'ⴕ', 'ⴙ', 'ⴛ', 'ⴡ', 'ⴣ'], "TOP"), - (['ⴄ', 'ⴅ', 'ⴔ', 'ⴕ', 'ⴁ', 'ⴂ', 'ⴘ', 'ⴝ'], "0"), + ("Ⴑ Ⴇ Ⴙ Ⴜ Ⴄ Ⴅ Ⴓ Ⴚ", "TOP"), + ("Ⴄ Ⴅ Ⴇ Ⴈ Ⴆ Ⴑ Ⴊ Ⴋ", "0"), + ("ⴁ ⴗ ⴂ ⴄ ⴅ ⴇ ⴔ ⴖ", "TOP | LATIN_X_HEIGHT"), + ("ⴈ ⴌ ⴖ ⴎ ⴃ ⴆ ⴋ ⴢ", "0"), + ("ⴐ ⴑ ⴓ ⴕ ⴙ ⴛ ⴡ ⴣ", "TOP"), + ("ⴄ ⴅ ⴔ ⴕ ⴁ ⴂ ⴘ ⴝ", "0"), ], }, { "name": "Glagolitic", "tag": "GLAG", "hint_top_to_bottom": False, - "std_chars": ['Ⱅ', 'ⱅ'], # Ⱅ ⱅ + "std_chars": "Ⱅ ⱅ", # Ⱅ ⱅ "base_ranges": [ (0x2C00, 0x2C5F), # Glagolitic (0x1E000, 0x1E02F), # Glagolitic Supplement @@ -473,32 +472,32 @@ (0x1E000, 0x1E02F), ], "blues": [ - (['Ⰵ', 'Ⱄ', 'Ⱚ', 'Ⰴ', 'Ⰲ', 'Ⰺ', 'Ⱛ', 'Ⰻ'], "TOP"), - (['Ⰵ', 'Ⰴ', 'Ⰲ', 'Ⱚ', 'Ⱎ', 'Ⱑ', 'Ⰺ', 'Ⱄ'], "0"), - (['ⰵ', 'ⱄ', 'ⱚ', 'ⰴ', 'ⰲ', 'ⰺ', 'ⱛ', 'ⰻ'], "TOP | LATIN_X_HEIGHT"), - (['ⰵ', 'ⰴ', 'ⰲ', 'ⱚ', 'ⱎ', 'ⱑ', 'ⰺ', 'ⱄ'], "0"), + ("Ⰵ Ⱄ Ⱚ Ⰴ Ⰲ Ⰺ Ⱛ Ⰻ", "TOP"), + ("Ⰵ Ⰴ Ⰲ Ⱚ Ⱎ Ⱑ Ⰺ Ⱄ", "0"), + ("ⰵ ⱄ ⱚ ⰴ ⰲ ⰺ ⱛ ⰻ", "TOP | LATIN_X_HEIGHT"), + ("ⰵ ⰴ ⰲ ⱚ ⱎ ⱑ ⰺ ⱄ", "0"), ], }, { "name": "Gothic", "tag": "GOTH", "hint_top_to_bottom": True, - "std_chars": ['𐌴', '𐌾', '𐍃'], # 𐌴 𐌾 𐍃 + "std_chars": "𐌴 𐌾 𐍃", # 𐌴 𐌾 𐍃 "base_ranges": [ (0x10330, 0x1034F), # Gothic ], "non_base_ranges": [ ], "blues": [ - (['𐌲', '𐌶', '𐍀', '𐍄', '𐌴', '𐍃', '𐍈', '𐌾'], "TOP"), - (['𐌶', '𐌴', '𐍃', '𐍈'], "0"), + ("𐌲 𐌶 𐍀 𐍄 𐌴 𐍃 𐍈 𐌾", "TOP"), + ("𐌶 𐌴 𐍃 𐍈", "0"), ], }, { "name": "Greek", "tag": "GREK", "hint_top_to_bottom": False, - "std_chars": ['ο', 'Ο'], # ο Ο + "std_chars": "ο Ο", # ο Ο "base_ranges": [ (0x0370, 0x03FF), # Greek and Coptic (0x1F00, 0x1FFF), # Greek Extended @@ -513,19 +512,19 @@ (0x1FFD, 0x1FFE), ], "blues": [ - (['Γ', 'Β', 'Ε', 'Ζ', 'Θ', 'Ο', 'Ω'], "TOP"), - (['Β', 'Δ', 'Ζ', 'Ξ', 'Θ', 'Ο'], "0"), - (['β', 'θ', 'δ', 'ζ', 'λ', 'ξ'], "TOP"), - (['α', 'ε', 'ι', 'ο', 'π', 'σ', 'τ', 'ω'], "TOP | LATIN_X_HEIGHT"), - (['α', 'ε', 'ι', 'ο', 'π', 'σ', 'τ', 'ω'], "0"), - (['β', 'γ', 'η', 'μ', 'ρ', 'φ', 'χ', 'ψ'], "0"), + ("Γ Β Ε Ζ Θ Ο Ω", "TOP"), + ("Β Δ Ζ Ξ Θ Ο", "0"), + ("β θ δ ζ λ ξ", "TOP"), + ("α ε ι ο π σ τ ω", "TOP | LATIN_X_HEIGHT"), + ("α ε ι ο π σ τ ω", "0"), + ("β γ η μ ρ φ χ ψ", "0"), ], }, { "name": "Gujarati", "tag": "GUJR", "hint_top_to_bottom": False, - "std_chars": ['ટ', '૦'], # ટ ૦ + "std_chars": "ટ ૦", # ટ ૦ "base_ranges": [ (0x0A80, 0x0AFF), # Gujarati ], @@ -538,18 +537,18 @@ (0x0AFA, 0x0AFF), ], "blues": [ - (['ત', 'ન', 'ઋ', 'ઌ', 'છ', 'ટ', 'ર', '૦'], "TOP | LATIN_X_HEIGHT"), - (['ખ', 'ગ', 'ઘ', 'ઞ', 'ઇ', 'ઈ', 'ઠ', 'જ'], "0"), - (['ઈ', 'ઊ', 'િ', 'ી', 'લ', 'શ', 'જ', 'સ'], "TOP"), - (['ુ', 'ૃ', 'ૄ', 'ખ', 'છ', 'છ'], "0"), - (['૦', '૧', '૨', '૩', '૭'], "TOP"), + ("ત ન ઋ ઌ છ ટ ર ૦", "TOP | LATIN_X_HEIGHT"), + ("ખ ગ ઘ ઞ ઇ ઈ ઠ જ", "0"), + ("ઈ ઊ િ ી લી શ્ચિ જિ સી", "TOP"), + ("ુ ૃ ૄ ખુ છૃ છૄ", "0"), + ("૦ ૧ ૨ ૩ ૭", "TOP"), ], }, { "name": "Gurmukhi", "tag": "GURU", "hint_top_to_bottom": True, - "std_chars": ['ਠ', 'ਰ', '੦'], # ਠ ਰ ੦ + "std_chars": "ਠ ਰ ੦", # ਠ ਰ ੦ "base_ranges": [ (0x0A00, 0x0A7F), # Gurmukhi ], @@ -561,18 +560,18 @@ (0x0A75, 0x0A75), ], "blues": [ - (['ਇ', 'ਈ', 'ਉ', 'ਏ', 'ਓ', 'ੳ', 'ਿ', 'ੀ'], "TOP"), - (['ਕ', 'ਗ', 'ਙ', 'ਚ', 'ਜ', 'ਤ', 'ਧ', 'ਸ'], "TOP"), - (['ਕ', 'ਗ', 'ਙ', 'ਚ', 'ਜ', 'ਤ', 'ਧ', 'ਸ'], "TOP | LATIN_NEUTRAL | LATIN_X_HEIGHT"), - (['ਅ', 'ਏ', 'ਓ', 'ਗ', 'ਜ', 'ਠ', 'ਰ', 'ਸ'], "0"), - (['੦', '੧', '੨', '੩', '੭'], "TOP"), + ("ਇ ਈ ਉ ਏ ਓ ੳ ਿ ੀ", "TOP"), + ("ਕ ਗ ਙ ਚ ਜ ਤ ਧ ਸ", "TOP"), + ("ਕ ਗ ਙ ਚ ਜ ਤ ਧ ਸ", "TOP | LATIN_NEUTRAL | LATIN_X_HEIGHT"), + ("ਅ ਏ ਓ ਗ ਜ ਠ ਰ ਸ", "0"), + ("੦ ੧ ੨ ੩ ੭", "TOP"), ], }, { "name": "Hebrew", "tag": "HEBR", "hint_top_to_bottom": False, - "std_chars": ['ם'], # ם + "std_chars": "ם", # ם "base_ranges": [ (0x0590, 0x05FF), # Hebrew (0xFB1D, 0xFB4F), # Alphab. Present. Forms (Hebrew) @@ -585,16 +584,16 @@ (0xFB1E, 0xFB1E), ], "blues": [ - (['ב', 'ד', 'ה', 'ח', 'ך', 'כ', 'ם', 'ס'], "TOP | LATIN_LONG"), - (['ב', 'ט', 'כ', 'ם', 'ס', 'צ'], "0"), - (['ק', 'ך', 'ן', 'ף', 'ץ'], "0"), + ("ב ד ה ח ך כ ם ס", "TOP | LATIN_LONG"), + ("ב ט כ ם ס צ", "0"), + ("ק ך ן ף ץ", "0"), ], }, { "name": "Kayah Li", "tag": "KALI", "hint_top_to_bottom": False, - "std_chars": ['ꤍ', '꤀'], # ꤍ ꤀ + "std_chars": "ꤍ ꤀", # ꤍ ꤀ "base_ranges": [ (0xA900, 0xA92F), # Kayah Li ], @@ -602,18 +601,18 @@ (0xA926, 0xA92D), ], "blues": [ - (['꤅', 'ꤏ', '꤁', 'ꤋ', '꤀', 'ꤍ'], "TOP | LATIN_X_HEIGHT"), - (['꤈', 'ꤘ', '꤀', 'ꤍ', 'ꤢ'], "0"), - (['ꤖ', 'ꤡ'], "TOP"), - (['ꤑ', 'ꤜ', 'ꤞ'], "0"), - (['ꤑ', 'ꤜ', 'ꤔ'], "0"), + ("꤅ ꤏ ꤁ ꤋ ꤀ ꤍ", "TOP | LATIN_X_HEIGHT"), + ("꤈ ꤘ ꤀ ꤍ ꤢ", "0"), + ("ꤖ ꤡ", "TOP"), + ("ꤑ ꤜ ꤞ", "0"), + ("ꤑ꤬ ꤜ꤭ ꤔ꤬", "0"), ], }, { "name": "Khmer", "tag": "KHMR", "hint_top_to_bottom": False, - "std_chars": ['០'], # ០ + "std_chars": "០", # ០ "base_ranges": [ (0x1780, 0x17FF), # Khmer ], @@ -624,33 +623,33 @@ (0x17DD, 0x17DD), ], "blues": [ - (['ខ', 'ទ', 'ន', 'ឧ', 'ឩ', 'ា'], "TOP | LATIN_X_HEIGHT"), - (['ក', 'ក', 'ក', 'ក'], "LATIN_SUB_TOP"), - (['ខ', 'ឃ', 'ច', 'ឋ', 'ប', 'ម', 'យ', 'ឲ'], "0"), - (['ត', 'រ', 'ឲ', 'អ'], "0"), - (['ន', 'ង', 'ក', 'ច', 'ន', 'ល'], "0"), + ("ខ ទ ន ឧ ឩ ា", "TOP | LATIN_X_HEIGHT"), + ("ក្ក ក្ខ ក្គ ក្ថ", "LATIN_SUB_TOP"), + ("ខ ឃ ច ឋ ប ម យ ឲ", "0"), + ("ត្រ រៀ ឲ្យ អឿ", "0"), + ("ន្ត្រៃ ង្ខ្យ ក្បៀ ច្រៀ ន្តឿ ល្បឿ", "0"), ], }, { "name": "Khmer Symbols", "tag": "KHMS", "hint_top_to_bottom": False, - "std_chars": ['᧡', '᧪'], # ᧡ ᧪ + "std_chars": "᧡ ᧪", # ᧡ ᧪ "base_ranges": [ (0x19E0, 0x19FF), # Khmer Symbols ], "non_base_ranges": [ ], "blues": [ - (['᧠', '᧡'], "TOP | LATIN_X_HEIGHT"), - (['᧶', '᧹'], "0"), + ("᧠ ᧡", "TOP | LATIN_X_HEIGHT"), + ("᧶ ᧹", "0"), ], }, { "name": "Kannada", "tag": "KNDA", "hint_top_to_bottom": False, - "std_chars": ['೦', 'ಬ'], # ೦ ಬ + "std_chars": "೦ ಬ", # ೦ ಬ "base_ranges": [ (0x0C80, 0x0CFF), # Kannada ], @@ -663,15 +662,15 @@ (0x0CE2, 0x0CE3), ], "blues": [ - (['ಇ', 'ಊ', 'ಐ', 'ಣ', 'ಸ', 'ನ', 'ದ', 'ರ'], "TOP"), - (['ಅ', 'ಉ', 'ಎ', 'ಲ', '೦', '೨', '೬', '೭'], "0"), + ("ಇ ಊ ಐ ಣ ಸಾ ನಾ ದಾ ರಾ", "TOP"), + ("ಅ ಉ ಎ ಲ ೦ ೨ ೬ ೭", "0"), ], }, { "name": "Lao", "tag": "LAOO", "hint_top_to_bottom": False, - "std_chars": ['໐'], # ໐ + "std_chars": "໐", # ໐ "base_ranges": [ (0x0E80, 0x0EFF), # Lao ], @@ -681,18 +680,18 @@ (0x0EC8, 0x0ECD), ], "blues": [ - (['າ', 'ດ', 'ອ', 'ມ', 'ລ', 'ວ', 'ຣ', 'ງ'], "TOP | LATIN_X_HEIGHT"), - (['າ', 'ອ', 'ບ', 'ຍ', 'ຣ', 'ຮ', 'ວ', 'ຢ'], "0"), - (['ປ', 'ຢ', 'ຟ', 'ຝ'], "TOP"), - (['ໂ', 'ໄ', 'ໃ'], "TOP"), - (['ງ', 'ຊ', 'ຖ', 'ຽ', 'ໆ', 'ຯ'], "0"), + ("າ ດ ອ ມ ລ ວ ຣ ງ", "TOP | LATIN_X_HEIGHT"), + ("າ ອ ບ ຍ ຣ ຮ ວ ຢ", "0"), + ("ປ ຢ ຟ ຝ", "TOP"), + ("ໂ ໄ ໃ", "TOP"), + ("ງ ຊ ຖ ຽ ໆ ຯ", "0"), ], }, { "name": "Latin", "tag": "LATN", "hint_top_to_bottom": False, - "std_chars": ['o', 'O', '0'], + "std_chars": "o O 0", "base_ranges": [ (0x0020, 0x007F), # Basic Latin (no control chars) (0x00A0, 0x00A9), # Latin-1 Supplement (no control chars) @@ -746,19 +745,19 @@ (0xA7F8, 0xA7FA), ], "blues": [ - (['T', 'H', 'E', 'Z', 'O', 'C', 'Q', 'S'], "TOP"), - (['H', 'E', 'Z', 'L', 'O', 'C', 'U', 'S'], "0"), - (['f', 'i', 'j', 'k', 'd', 'b', 'h'], "TOP"), - (['u', 'v', 'x', 'z', 'o', 'e', 's', 'c'], "TOP | LATIN_X_HEIGHT"), - (['n', 'r', 'x', 'z', 'o', 'e', 's', 'c'], "0"), - (['p', 'q', 'g', 'j', 'y'], "0"), + ("T H E Z O C Q S", "TOP"), + ("H E Z L O C U S", "0"), + ("f i j k d b h", "TOP"), + ("u v x z o e s c", "TOP | LATIN_X_HEIGHT"), + ("n r x z o e s c", "0"), + ("p q g j y", "0"), ], }, { "name": "Latin Subscript Fallback", "tag": "LATB", "hint_top_to_bottom": False, - "std_chars": ['ₒ', '₀'], # ₒ ₀ + "std_chars": "ₒ ₀", # ₒ ₀ "base_ranges": [ (0x1D62, 0x1D6A), # some small subscript letters (0x2080, 0x209C), # subscript digits and letters @@ -767,19 +766,19 @@ "non_base_ranges": [ ], "blues": [ - (['₀', '₃', '₅', '₇', '₈'], "TOP"), - (['₀', '₁', '₂', '₃', '₈'], "0"), - (['ᵢ', 'ⱼ', 'ₕ', 'ₖ', 'ₗ'], "TOP"), - (['ₐ', 'ₑ', 'ₒ', 'ₓ', 'ₙ', 'ₛ', 'ᵥ', 'ᵤ', 'ᵣ'], "TOP | LATIN_X_HEIGHT"), - (['ₐ', 'ₑ', 'ₒ', 'ₓ', 'ₙ', 'ₛ', 'ᵥ', 'ᵤ', 'ᵣ'], "0"), - (['ᵦ', 'ᵧ', 'ᵨ', 'ᵩ', 'ₚ'], "0"), + ("₀ ₃ ₅ ₇ ₈", "TOP"), + ("₀ ₁ ₂ ₃ ₈", "0"), + ("ᵢ ⱼ ₕ ₖ ₗ", "TOP"), + ("ₐ ₑ ₒ ₓ ₙ ₛ ᵥ ᵤ ᵣ", "TOP | LATIN_X_HEIGHT"), + ("ₐ ₑ ₒ ₓ ₙ ₛ ᵥ ᵤ ᵣ", "0"), + ("ᵦ ᵧ ᵨ ᵩ ₚ", "0"), ], }, { "name": "Latin Superscript Fallback", "tag": "LATP", "hint_top_to_bottom": False, - "std_chars": ['ᵒ', 'ᴼ', '⁰'], # ᵒ ᴼ ⁰ + "std_chars": "ᵒ ᴼ ⁰", # ᵒ ᴼ ⁰ "base_ranges": [ (0x00AA, 0x00AA), # feminine ordinal indicator (0x00B2, 0x00B3), # superscript two and three @@ -798,34 +797,34 @@ "non_base_ranges": [ ], "blues": [ - (['⁰', '³', '⁵', '⁷', 'ᵀ', 'ᴴ', 'ᴱ', 'ᴼ'], "TOP"), - (['⁰', '¹', '²', '³', 'ᴱ', 'ᴸ', 'ᴼ', 'ᵁ'], "0"), - (['ᵇ', 'ᵈ', 'ᵏ', 'ʰ', 'ʲ', 'ᶠ', 'ⁱ'], "TOP"), - (['ᵉ', 'ᵒ', 'ʳ', 'ˢ', 'ˣ', 'ᶜ', 'ᶻ'], "TOP | LATIN_X_HEIGHT"), - (['ᵉ', 'ᵒ', 'ʳ', 'ˢ', 'ˣ', 'ᶜ', 'ᶻ'], "0"), - (['ᵖ', 'ʸ', 'ᵍ'], "0"), + ("⁰ ³ ⁵ ⁷ ᵀ ᴴ ᴱ ᴼ", "TOP"), + ("⁰ ¹ ² ³ ᴱ ᴸ ᴼ ᵁ", "0"), + ("ᵇ ᵈ ᵏ ʰ ʲ ᶠ ⁱ", "TOP"), + ("ᵉ ᵒ ʳ ˢ ˣ ᶜ ᶻ", "TOP | LATIN_X_HEIGHT"), + ("ᵉ ᵒ ʳ ˢ ˣ ᶜ ᶻ", "0"), + ("ᵖ ʸ ᵍ", "0"), ], }, { "name": "Lisu", "tag": "LISU", "hint_top_to_bottom": False, - "std_chars": ['ꓳ'], # ꓳ + "std_chars": "ꓳ", # ꓳ "base_ranges": [ (0xA4D0, 0xA4FF), # Lisu ], "non_base_ranges": [ ], "blues": [ - (['ꓡ', 'ꓧ', 'ꓱ', 'ꓶ', 'ꓩ', 'ꓚ', 'ꓵ', 'ꓳ'], "TOP"), - (['ꓕ', 'ꓜ', 'ꓞ', 'ꓡ', 'ꓛ', 'ꓢ', 'ꓳ', 'ꓴ'], "0"), + ("ꓡ ꓧ ꓱ ꓶ ꓩ ꓚ ꓵ ꓳ", "TOP"), + ("ꓕ ꓜ ꓞ ꓡ ꓛ ꓢ ꓳ ꓴ", "0"), ], }, { "name": "Malayalam", "tag": "MLYM", "hint_top_to_bottom": False, - "std_chars": ['ഠ', 'റ'], # ഠ റ + "std_chars": "ഠ റ", # ഠ റ "base_ranges": [ (0x0D00, 0x0D7F), # Malayalam ], @@ -836,35 +835,35 @@ (0x0D62, 0x0D63), ], "blues": [ - (['ഒ', 'ട', 'ഠ', 'റ', 'ച', 'പ', 'ച', 'പ'], "TOP"), - (['ട', 'ഠ', 'ധ', 'ശ', 'ഘ', 'ച', 'ഥ', 'ല'], "0"), + ("ഒ ട ഠ റ ച പ ച്ച പ്പ", "TOP"), + ("ട ഠ ധ ശ ഘ ച ഥ ല", "0"), ], }, { "name": "Medefaidrin", "tag": "MEDF", "hint_top_to_bottom": False, - "std_chars": ['𖹡', '𖹛', '𖹯'], # 𖹡 𖹛 𖹯 + "std_chars": "𖹡 𖹛 𖹯", # 𖹡 𖹛 𖹯 "base_ranges": [ (0x16E40, 0x16E9F), # Medefaidrin ], "non_base_ranges": [ ], "blues": [ - (['𖹀', '𖹁', '𖹂', '𖹃', '𖹏', '𖹚', '𖹟'], "TOP"), - (['𖹀', '𖹁', '𖹂', '𖹃', '𖹏', '𖹚', '𖹒', '𖹓'], "0"), - (['𖹤', '𖹬', '𖹧', '𖹴', '𖹶', '𖹾'], "TOP"), - (['𖹠', '𖹡', '𖹢', '𖹹', '𖹳', '𖹮'], "TOP | LATIN_X_HEIGHT"), - (['𖹠', '𖹡', '𖹢', '𖹳', '𖹭', '𖹽'], "0"), - (['𖹥', '𖹨', '𖹩'], "0"), - (['𖺀', '𖺅', '𖺈', '𖺄', '𖺍'], "TOP"), + ("𖹀 𖹁 𖹂 𖹃 𖹏 𖹚 𖹟", "TOP"), + ("𖹀 𖹁 𖹂 𖹃 𖹏 𖹚 𖹒 𖹓", "0"), + ("𖹤 𖹬 𖹧 𖹴 𖹶 𖹾", "TOP"), + ("𖹠 𖹡 𖹢 𖹹 𖹳 𖹮", "TOP | LATIN_X_HEIGHT"), + ("𖹠 𖹡 𖹢 𖹳 𖹭 𖹽", "0"), + ("𖹥 𖹨 𖹩", "0"), + ("𖺀 𖺅 𖺈 𖺄 𖺍", "TOP"), ], }, { "name": "Mongolian", "tag": "MONG", "hint_top_to_bottom": True, - "std_chars": ['ᡂ', 'ᠪ'], # ᡂ ᠪ + "std_chars": "ᡂ ᠪ", # ᡂ ᠪ "base_ranges": [ (0x1800, 0x18AF), # Mongolian (0x11660, 0x1167F), # Mongolian Supplement @@ -874,15 +873,15 @@ (0x18A9, 0x18A9), ], "blues": [ - (['ᠳ', 'ᠴ', 'ᠶ', 'ᠽ', 'ᡂ', 'ᡊ', '‍', '‍'], "TOP"), - (['ᡃ'], "0"), + ("ᠳ ᠴ ᠶ ᠽ ᡂ ᡊ ‍ᡡ‍ ‍ᡳ‍", "TOP"), + ("ᡃ", "0"), ], }, { "name": "Myanmar", "tag": "MYMR", "hint_top_to_bottom": False, - "std_chars": ['ဝ', 'င', 'ဂ'], # ဝ င ဂ + "std_chars": "ဝ င ဂ", # ဝ င ဂ "base_ranges": [ (0x1000, 0x109F), # Myanmar (0xA9E0, 0xA9FF), # Myanmar Extended-B @@ -903,17 +902,17 @@ (0xAA7C, 0xAA7C), ], "blues": [ - (['ခ', 'ဂ', 'င', 'ဒ', 'ဝ', 'ၥ', '၊', '။'], "TOP | LATIN_X_HEIGHT"), - (['င', 'ဎ', 'ဒ', 'ပ', 'ဗ', 'ဝ', '၊', '။'], "0"), - (['ဩ', 'ြ', '၍', '၏', '၆', 'ါ', 'ိ'], "TOP"), - (['ဉ', 'ည', 'ဥ', 'ဩ', 'ဨ', '၂', '၅', '၉'], "0"), + ("ခ ဂ င ဒ ဝ ၥ ၊ ။", "TOP | LATIN_X_HEIGHT"), + ("င ဎ ဒ ပ ဗ ဝ ၊ ။", "0"), + ("ဩ ြ ၍ ၏ ၆ ါ ိ", "TOP"), + ("ဉ ည ဥ ဩ ဨ ၂ ၅ ၉", "0"), ], }, { "name": "N'Ko", "tag": "NKOO", "hint_top_to_bottom": False, - "std_chars": ['ߋ', '߀'], # ߋ ߀ + "std_chars": "ߋ ߀", # ߋ ߀ "base_ranges": [ (0x07C0, 0x07FF), # N'Ko ], @@ -922,17 +921,17 @@ (0x07FD, 0x07FD), ], "blues": [ - (['ߐ', '߉', 'ߒ', 'ߟ', 'ߖ', 'ߜ', 'ߠ', 'ߥ'], "TOP"), - (['߀', 'ߘ', 'ߡ', 'ߠ', 'ߥ'], "0"), - (['ߏ', 'ߛ', 'ߋ'], "TOP | LATIN_X_HEIGHT"), - (['ߎ', 'ߏ', 'ߛ', 'ߋ'], "0"), + ("ߐ ߉ ߒ ߟ ߖ ߜ ߠ ߥ", "TOP"), + ("߀ ߘ ߡ ߠ ߥ", "0"), + ("ߏ ߛ ߋ", "TOP | LATIN_X_HEIGHT"), + ("ߎ ߏ ߛ ߋ", "0"), ], }, { "name": "no script", "tag": "NONE", "hint_top_to_bottom": False, - "std_chars": [], + "std_chars": "", "base_ranges": [ ], "non_base_ranges": [ @@ -944,88 +943,88 @@ "name": "Ol Chiki", "tag": "OLCK", "hint_top_to_bottom": False, - "std_chars": ['ᱛ'], # ᱛ + "std_chars": "ᱛ", # ᱛ "base_ranges": [ (0x1C50, 0x1C7F), # Ol Chiki ], "non_base_ranges": [ ], "blues": [ - (['ᱛ', 'ᱜ', 'ᱝ', 'ᱡ', 'ᱢ', 'ᱥ'], "TOP"), - (['ᱛ', 'ᱜ', 'ᱝ', 'ᱡ', 'ᱢ', 'ᱥ'], "0"), + ("ᱛ ᱜ ᱝ ᱡ ᱢ ᱥ", "TOP"), + ("ᱛ ᱜ ᱝ ᱡ ᱢ ᱥ", "0"), ], }, { "name": "Old Turkic", "tag": "ORKH", "hint_top_to_bottom": False, - "std_chars": ['𐰗'], # 𐰗 + "std_chars": "𐰗", # 𐰗 "base_ranges": [ (0x10C00, 0x10C4F), # Old Turkic ], "non_base_ranges": [ ], "blues": [ - (['𐰗', '𐰘', '𐰧'], "TOP"), - (['𐰉', '𐰗', '𐰦', '𐰧'], "0"), + ("𐰗 𐰘 𐰧", "TOP"), + ("𐰉 𐰗 𐰦 𐰧", "0"), ], }, { "name": "Osage", "tag": "OSGE", "hint_top_to_bottom": False, - "std_chars": ['𐓂', '𐓪'], # 𐓂 𐓪 + "std_chars": "𐓂 𐓪", # 𐓂 𐓪 "base_ranges": [ (0x104B0, 0x104FF), # Osage ], "non_base_ranges": [ ], "blues": [ - (['𐒾', '𐓍', '𐓒', '𐓓', '𐒻', '𐓂', '𐒵', '𐓆'], "TOP"), - (['𐒰', '𐓍', '𐓂', '𐒿', '𐓎', '𐒹'], "0"), - (['𐒼', '𐒽', '𐒾'], "0"), - (['𐓵', '𐓶', '𐓺', '𐓻', '𐓝', '𐓣', '𐓪', '𐓮'], "TOP | LATIN_X_HEIGHT"), - (['𐓘', '𐓚', '𐓣', '𐓵', '𐓡', '𐓧', '𐓪', '𐓶'], "0"), - (['𐓤', '𐓦', '𐓸', '𐓹', '𐓛'], "TOP"), - (['𐓤', '𐓥', '𐓦'], "0"), + ("𐒾 𐓍 𐓒 𐓓 𐒻 𐓂 𐒵 𐓆", "TOP"), + ("𐒰 𐓍 𐓂 𐒿 𐓎 𐒹", "0"), + ("𐒼 𐒽 𐒾", "0"), + ("𐓵 𐓶 𐓺 𐓻 𐓝 𐓣 𐓪 𐓮", "TOP | LATIN_X_HEIGHT"), + ("𐓘 𐓚 𐓣 𐓵 𐓡 𐓧 𐓪 𐓶", "0"), + ("𐓤 𐓦 𐓸 𐓹 𐓛", "TOP"), + ("𐓤 𐓥 𐓦", "0"), ], }, { "name": "Osmanya", "tag": "OSMA", "hint_top_to_bottom": False, - "std_chars": ['𐒆', '𐒠'], # 𐒆 𐒠 + "std_chars": "𐒆 𐒠", # 𐒆 𐒠 "base_ranges": [ (0x10480, 0x104AF), # Osmanya ], "non_base_ranges": [ ], "blues": [ - (['𐒆', '𐒉', '𐒐', '𐒒', '𐒘', '𐒛', '𐒠', '𐒣'], "TOP"), - (['𐒀', '𐒂', '𐒆', '𐒈', '𐒊', '𐒒', '𐒠', '𐒩'], "0"), + ("𐒆 𐒉 𐒐 𐒒 𐒘 𐒛 𐒠 𐒣", "TOP"), + ("𐒀 𐒂 𐒆 𐒈 𐒊 𐒒 𐒠 𐒩", "0"), ], }, { "name": "Hanifi Rohingya", "tag": "ROHG", "hint_top_to_bottom": False, - "std_chars": ['𐴰'], # 𐴰 + "std_chars": "𐴰", # 𐴰 "base_ranges": [ (0x10D00, 0x10D3F), # Hanifi Rohingya ], "non_base_ranges": [ ], "blues": [ - (['𐴃', '𐴀', '𐴆', '𐴖', '𐴕'], "TOP"), - (['𐴔', '𐴖', '𐴕', '𐴑', '𐴐'], "0"), - (['ـ'], "LATIN_NEUTRAL"), + ("𐴃 𐴀 𐴆 𐴖 𐴕", "TOP"), + ("𐴔 𐴖 𐴕 𐴑 𐴐", "0"), + ("ـ", "LATIN_NEUTRAL"), ], }, { "name": "Saurashtra", "tag": "SAUR", "hint_top_to_bottom": False, - "std_chars": ['ꢝ', '꣐'], # ꢝ ꣐ + "std_chars": "ꢝ ꣐", # ꢝ ꣐ "base_ranges": [ (0xA880, 0xA8DF), # Saurashtra ], @@ -1034,33 +1033,33 @@ (0xA8B4, 0xA8C5), ], "blues": [ - (['ꢜ', 'ꢞ', 'ꢳ', 'ꢂ', 'ꢖ', 'ꢒ', 'ꢝ', 'ꢛ'], "TOP"), - (['ꢂ', 'ꢨ', 'ꢺ', 'ꢤ', 'ꢎ'], "0"), + ("ꢜ ꢞ ꢳ ꢂ ꢖ ꢒ ꢝ ꢛ", "TOP"), + ("ꢂ ꢨ ꢺ ꢤ ꢎ", "0"), ], }, { "name": "Shavian", "tag": "SHAW", "hint_top_to_bottom": False, - "std_chars": ['𐑴'], # 𐑴 + "std_chars": "𐑴", # 𐑴 "base_ranges": [ (0x10450, 0x1047F), # Shavian ], "non_base_ranges": [ ], "blues": [ - (['𐑕', '𐑙'], "TOP"), - (['𐑔', '𐑖', '𐑗', '𐑹', '𐑻'], "0"), - (['𐑟', '𐑣'], "0"), - (['𐑱', '𐑲', '𐑳', '𐑴', '𐑸', '𐑺', '𐑼'], "TOP | LATIN_X_HEIGHT"), - (['𐑴', '𐑻', '𐑹'], "0"), + ("𐑕 𐑙", "TOP"), + ("𐑔 𐑖 𐑗 𐑹 𐑻", "0"), + ("𐑟 𐑣", "0"), + ("𐑱 𐑲 𐑳 𐑴 𐑸 𐑺 𐑼", "TOP | LATIN_X_HEIGHT"), + ("𐑴 𐑻 𐑹", "0"), ], }, { "name": "Sinhala", "tag": "SINH", "hint_top_to_bottom": False, - "std_chars": ['ට'], # ට + "std_chars": "ට", # ට "base_ranges": [ (0x0D80, 0x0DFF), # Sinhala ], @@ -1069,16 +1068,16 @@ (0x0DD2, 0x0DD6), ], "blues": [ - (['ඉ', 'ක', 'ඝ', 'ඳ', 'ප', 'ය', 'ල', 'ෆ'], "TOP"), - (['එ', 'ඔ', 'ඝ', 'ජ', 'ට', 'ථ', 'ධ', 'ර'], "0"), - (['ද', 'ඳ', 'උ', 'ල', 'ත', 'ත', 'බ', 'ද'], "0"), + ("ඉ ක ඝ ඳ ප ය ල ෆ", "TOP"), + ("එ ඔ ඝ ජ ට ථ ධ ර", "0"), + ("ද ඳ උ ල තූ තු බු දු", "0"), ], }, { "name": "Sundanese", "tag": "SUND", "hint_top_to_bottom": False, - "std_chars": ['᮰'], # ᮰ + "std_chars": "᮰", # ᮰ "base_ranges": [ (0x1B80, 0x1BBF), # Sundanese (0x1CC0, 0x1CCF), # Sundanese Supplement @@ -1088,16 +1087,16 @@ (0x1BA1, 0x1BAD), ], "blues": [ - (['ᮋ', 'ᮞ', 'ᮮ', 'ᮽ', '᮰', 'ᮈ'], "TOP"), - (['ᮄ', 'ᮔ', 'ᮕ', 'ᮗ', '᮰', 'ᮆ', 'ᮈ', 'ᮉ'], "0"), - (['ᮼ', '᳄'], "0"), + ("ᮋ ᮞ ᮮ ᮽ ᮰ ᮈ", "TOP"), + ("ᮄ ᮔ ᮕ ᮗ ᮰ ᮆ ᮈ ᮉ", "0"), + ("ᮼ ᳄", "0"), ], }, { "name": "Tamil", "tag": "TAML", "hint_top_to_bottom": False, - "std_chars": ['௦'], # ௦ + "std_chars": "௦", # ௦ "base_ranges": [ (0x0B80, 0x0BFF), # Tamil ], @@ -1107,15 +1106,15 @@ (0x0BCD, 0x0BCD), ], "blues": [ - (['உ', 'ஒ', 'ஓ', 'ற', 'ஈ', 'க', 'ங', 'ச'], "TOP"), - (['க', 'ச', 'ல', 'ஶ', 'உ', 'ங', 'ட', 'ப'], "0"), + ("உ ஒ ஓ ற ஈ க ங ச", "TOP"), + ("க ச ல ஶ உ ங ட ப", "0"), ], }, { "name": "Tai Viet", "tag": "TAVT", "hint_top_to_bottom": False, - "std_chars": ['ꪒ', 'ꪫ'], # ꪒ ꪫ + "std_chars": "ꪒ ꪫ", # ꪒ ꪫ "base_ranges": [ (0xAA80, 0xAADF), # Tai Viet ], @@ -1127,15 +1126,15 @@ (0xAAC1, 0xAAC1), ], "blues": [ - (['ꪆ', 'ꪔ', 'ꪒ', 'ꪖ', 'ꪫ'], "TOP"), - (['ꪉ', 'ꪫ', 'ꪮ'], "0"), + ("ꪆ ꪔ ꪒ ꪖ ꪫ", "TOP"), + ("ꪉ ꪫ ꪮ", "0"), ], }, { "name": "Telugu", "tag": "TELU", "hint_top_to_bottom": False, - "std_chars": ['౦', '౧'], # ౦ ౧ + "std_chars": "౦ ౧", # ౦ ౧ "base_ranges": [ (0x0C00, 0x0C7F), # Telugu ], @@ -1147,30 +1146,30 @@ (0x0C62, 0x0C63), ], "blues": [ - (['ఇ', 'ఌ', 'ఙ', 'ఞ', 'ణ', 'ఱ', '౯'], "TOP"), - (['అ', 'క', 'చ', 'ర', 'ఽ', '౨', '౬'], "0"), + ("ఇ ఌ ఙ ఞ ణ ఱ ౯", "TOP"), + ("అ క చ ర ఽ ౨ ౬", "0"), ], }, { "name": "Tifinagh", "tag": "TFNG", "hint_top_to_bottom": False, - "std_chars": ['ⵔ'], # ⵔ + "std_chars": "ⵔ", # ⵔ "base_ranges": [ (0x2D30, 0x2D7F), # Tifinagh ], "non_base_ranges": [ ], "blues": [ - (['ⵔ', 'ⵙ', 'ⵛ', 'ⵞ', 'ⴵ', 'ⴼ', 'ⴹ', 'ⵎ'], "TOP"), - (['ⵔ', 'ⵙ', 'ⵛ', 'ⵞ', 'ⴵ', 'ⴼ', 'ⴹ', 'ⵎ'], "0"), + ("ⵔ ⵙ ⵛ ⵞ ⴵ ⴼ ⴹ ⵎ", "TOP"), + ("ⵔ ⵙ ⵛ ⵞ ⴵ ⴼ ⴹ ⵎ", "0"), ], }, { "name": "Thai", "tag": "THAI", "hint_top_to_bottom": False, - "std_chars": ['า', 'ๅ', '๐'], # า ๅ ๐ + "std_chars": "า ๅ ๐", # า ๅ ๐ "base_ranges": [ (0x0E00, 0x0E7F), # Thai ], @@ -1180,35 +1179,35 @@ (0x0E47, 0x0E4E), ], "blues": [ - (['บ', 'เ', 'แ', 'อ', 'ก', 'า'], "TOP | LATIN_X_HEIGHT"), - (['บ', 'ป', 'ษ', 'ฯ', 'อ', 'ย', 'ฮ'], "0"), - (['ป', 'ฝ', 'ฟ'], "TOP"), - (['โ', 'ใ', 'ไ'], "TOP"), - (['ฎ', 'ฏ', 'ฤ', 'ฦ'], "0"), - (['ญ', 'ฐ'], "0"), - (['๐', '๑', '๓'], "0"), + ("บ เ แ อ ก า", "TOP | LATIN_X_HEIGHT"), + ("บ ป ษ ฯ อ ย ฮ", "0"), + ("ป ฝ ฟ", "TOP"), + ("โ ใ ไ", "TOP"), + ("ฎ ฏ ฤ ฦ", "0"), + ("ญ ฐ", "0"), + ("๐ ๑ ๓", "0"), ], }, { "name": "Vai", "tag": "VAII", "hint_top_to_bottom": False, - "std_chars": ['ꘓ', 'ꖜ', 'ꖴ'], # ꘓ ꖜ ꖴ + "std_chars": "ꘓ ꖜ ꖴ", # ꘓ ꖜ ꖴ "base_ranges": [ (0xA500, 0xA63F), # Vai ], "non_base_ranges": [ ], "blues": [ - (['ꗍ', 'ꘖ', 'ꘙ', 'ꘜ', 'ꖜ', 'ꖝ', 'ꔅ', 'ꕢ'], "TOP"), - (['ꗍ', 'ꘖ', 'ꘙ', 'ꗞ', 'ꔅ', 'ꕢ', 'ꖜ', 'ꔆ'], "0"), + ("ꗍ ꘖ ꘙ ꘜ ꖜ ꖝ ꔅ ꕢ", "TOP"), + ("ꗍ ꘖ ꘙ ꗞ ꔅ ꕢ ꖜ ꔆ", "0"), ], }, { "name": "Limbu", "tag": "LIMB", "hint_top_to_bottom": False, - "std_chars": ['o'], # XXX + "std_chars": "o", # XXX "base_ranges": [ (0x1900, 0x194F), # Limbu ], @@ -1223,7 +1222,7 @@ "name": "Oriya", "tag": "ORYA", "hint_top_to_bottom": False, - "std_chars": ['o'], # XXX + "std_chars": "o", # XXX "base_ranges": [ (0x0B00, 0x0B7F), # Oriya ], @@ -1241,7 +1240,7 @@ "name": "Syloti Nagri", "tag": "SYLO", "hint_top_to_bottom": False, - "std_chars": ['o'], # XXX + "std_chars": "o", # XXX "base_ranges": [ (0xA800, 0xA82F), # Syloti Nagri ], @@ -1257,7 +1256,7 @@ "name": "Tibetan", "tag": "TIBT", "hint_top_to_bottom": False, - "std_chars": ['o'], # XXX + "std_chars": "o", # XXX "base_ranges": [ (0x0F00, 0x0FFF), # Tibetan ], @@ -1278,7 +1277,7 @@ "name": "CJKV ideographs", "tag": "HANI", "hint_top_to_bottom": False, - "std_chars": ['田', '囗'], # 田 囗 + "std_chars": "田 囗", # 田 囗 "base_ranges": [ (0x1100, 0x11FF), # Hangul Jamo (0x2E80, 0x2EFF), # CJK Radicals Supplement @@ -1319,10 +1318,10 @@ (0x3190, 0x319F), ], "blues": [ - (['他', '们', '你', '來', '們', '到', '和', '地', '对', '對', '就', '席', '我', '时', '時', '會', '来', '為', '能', '舰', '說', '说', '这', '這', '齊', '|', '军', '同', '已', '愿', '既', '星', '是', '景', '民', '照', '现', '現', '理', '用', '置', '要', '軍', '那', '配', '里', '開', '雷', '露', '面', '顾'], "TOP"), - (['个', '为', '人', '他', '以', '们', '你', '來', '個', '們', '到', '和', '大', '对', '對', '就', '我', '时', '時', '有', '来', '為', '要', '說', '说', '|', '主', '些', '因', '它', '想', '意', '理', '生', '當', '看', '着', '置', '者', '自', '著', '裡', '过', '还', '进', '進', '過', '道', '還', '里', '面'], "0"), - (['些', '们', '你', '來', '們', '到', '和', '地', '她', '将', '將', '就', '年', '得', '情', '最', '样', '樣', '理', '能', '說', '说', '这', '這', '通', '|', '即', '吗', '吧', '听', '呢', '品', '响', '嗎', '师', '師', '收', '断', '斷', '明', '眼', '間', '间', '际', '陈', '限', '除', '陳', '随', '際', '隨'], "CJK_HORIZ"), - (['事', '前', '學', '将', '將', '情', '想', '或', '政', '斯', '新', '样', '樣', '民', '沒', '没', '然', '特', '现', '現', '球', '第', '經', '谁', '起', '|', '例', '別', '别', '制', '动', '動', '吗', '嗎', '增', '指', '明', '朝', '期', '构', '物', '确', '种', '調', '调', '費', '费', '那', '都', '間', '间'], "CJK_HORIZ | CJK_RIGHT"), + ("他 们 你 來 們 到 和 地 对 對 就 席 我 时 時 會 来 為 能 舰 說 说 这 這 齊 | 军 同 已 愿 既 星 是 景 民 照 现 現 理 用 置 要 軍 那 配 里 開 雷 露 面 顾", "TOP"), + ("个 为 人 他 以 们 你 來 個 們 到 和 大 对 對 就 我 时 時 有 来 為 要 說 说 | 主 些 因 它 想 意 理 生 當 看 着 置 者 自 著 裡 过 还 进 進 過 道 還 里 面", "0"), + (" 些 们 你 來 們 到 和 地 她 将 將 就 年 得 情 最 样 樣 理 能 說 说 这 這 通 | 即 吗 吧 听 呢 品 响 嗎 师 師 收 断 斷 明 眼 間 间 际 陈 限 除 陳 随 際 隨", "CJK_HORIZ"), + ("事 前 學 将 將 情 想 或 政 斯 新 样 樣 民 沒 没 然 特 现 現 球 第 經 谁 起 | 例 別 别 制 动 動 吗 嗎 增 指 明 朝 期 构 物 确 种 調 调 費 费 那 都 間 间", "CJK_HORIZ | CJK_RIGHT"), ], }, ] @@ -1356,23 +1355,16 @@ def generate() -> str: buf += " name: \"{}\",\n".format(script["name"]) buf += " group: ScriptGroup::{},\n".format(group) buf += " tag: Tag::new(b\"{}\"),\n".format(tag) - buf += " index: {},\n".format(i) buf += " hint_top_to_bottom: {},\n".format(str(script["hint_top_to_bottom"]).lower()) # standard characters - buf += " std_chars: &[" - if len(std_chars) != 0: - for std_char in std_chars: - buf += "'{}', ".format(std_char) - buf += "],\n" + buf += " std_chars: \"{}\",\n".format(script["std_chars"]) # blue characters buf += " blues: &[" if len(blues) != 0: buf += "\n"; for blue in blues: - buf += " (&[" - for ch in blue[0]: - buf += "'{}', ".format(ch) - buf += "], {}),\n".format(blue[1]) + buf += " (\"" + blue[0] + "\"" + buf += ", {}),\n".format(blue[1]) buf += " ],\n" else: buf += "],\n" @@ -1403,6 +1395,7 @@ def generate() -> str: # Add some symbolic indices for each script so they can be # referenced by ScriptClass::LATN for example + buf += "#[allow(unused)]" buf += "impl ScriptClass {\n" for i, script in enumerate(SCRIPT_CLASSES): buf += " pub const {}: usize = {};\n".format(script["tag"], i) @@ -1430,6 +1423,7 @@ def generate() -> str: buf += "];\n\n"; # Symbolic indices for style classes + buf += "#[allow(unused)]" buf += "impl StyleClass {\n" for (i, tag) in enumerate(style_class_tags): buf += " pub const {}: usize = {};\n".format(tag, i) diff --git a/skrifa/src/outline/autohint/axis.rs b/skrifa/src/outline/autohint/axis.rs index 5d53e9217..6f5922539 100644 --- a/skrifa/src/outline/autohint/axis.rs +++ b/skrifa/src/outline/autohint/axis.rs @@ -40,6 +40,7 @@ impl Axis { } impl Axis { + #[cfg(test)] pub fn new(dim: Dimension, orientation: Option) -> Self { let mut axis = Self::default(); axis.reset(dim, orientation); @@ -164,10 +165,6 @@ impl Segment { &points[self.first()] } - pub fn first_point_mut<'a>(&self, points: &'a mut [Point]) -> &'a mut Point { - &mut points[self.first()] - } - pub fn last(&self) -> usize { self.last_ix as usize } @@ -176,10 +173,6 @@ impl Segment { &points[self.last()] } - pub fn last_point_mut<'a>(&self, points: &'a mut [Point]) -> &'a mut Point { - &mut points[self.last()] - } - pub fn edge<'a>(&self, edges: &'a [Edge]) -> Option<&'a Edge> { edges.get(self.edge_ix.map(|ix| ix as usize)?) } @@ -237,10 +230,6 @@ impl Edge { } impl Edge { - pub fn first_segment<'a>(&self, segments: &'a [Segment]) -> Option<&'a Segment> { - segments.get(self.first_ix as usize) - } - pub fn link<'a>(&self, edges: &'a [Edge]) -> Option<&'a Edge> { edges.get(self.link_ix.map(|ix| ix as usize)?) } diff --git a/skrifa/src/outline/autohint/hint.rs b/skrifa/src/outline/autohint/hint.rs index f61c379e0..b151b3d5a 100644 --- a/skrifa/src/outline/autohint/hint.rs +++ b/skrifa/src/outline/autohint/hint.rs @@ -117,23 +117,45 @@ pub(crate) fn align_strong_points(outline: &mut Outline, axis: &mut Axis) -> Opt store_point(point, dim, edge.pos + (ou - edge.opos)); continue; } - // Find enclosing edges - let mut min_ix = 0; - let mut max_ix = edges.len(); - while min_ix < max_ix { - let mid_ix = (min_ix + max_ix) >> 1; - let edge = &edges[mid_ix]; - let fpos = edge.fpos as i32; - match u.cmp(&fpos) { - Ordering::Less => max_ix = mid_ix, - Ordering::Greater => min_ix = mid_ix + 1, - Ordering::Equal => { - // We are on an edge + // Find enclosing edges; for a small number of edges, use a linear + // search. + // Note: this is actually critical for matching FreeType in cases where + // we have more than one edge with the same fpos. When this happens, + // linear and binary searches can produce different results. + // See + let min_ix = if edges.len() <= 8 { + if let Some((min_ix, edge)) = edges + .iter() + .enumerate() + .find(|(_ix, edge)| edge.fpos as i32 >= u) + { + if edge.fpos as i32 == u { store_point(point, dim, edge.pos); continue 'points; } + min_ix + } else { + 0 } - } + } else { + let mut min_ix = 0; + let mut max_ix = edges.len(); + while min_ix < max_ix { + let mid_ix = (min_ix + max_ix) >> 1; + let edge = &edges[mid_ix]; + let fpos = edge.fpos as i32; + match u.cmp(&fpos) { + Ordering::Less => max_ix = mid_ix, + Ordering::Greater => min_ix = mid_ix + 1, + Ordering::Equal => { + // We are on an edge + store_point(point, dim, edge.pos); + continue 'points; + } + } + } + min_ix + }; // Point is not on an edge if let Some(before_ix) = min_ix.checked_sub(1) { let edge_before = edges.get(before_ix)?; @@ -628,7 +650,7 @@ mod tests { Default::default(), metrics.style_class().script.group, ); - let hinted_metrics = latin::hint_outline(&mut outline, &metrics, &scale); + let hinted_metrics = latin::hint_outline(&mut outline, &metrics, &scale, None); (outline, hinted_metrics) } } diff --git a/skrifa/src/outline/autohint/instance.rs b/skrifa/src/outline/autohint/instance.rs index 997476e01..e1231585a 100644 --- a/skrifa/src/outline/autohint/instance.rs +++ b/skrifa/src/outline/autohint/instance.rs @@ -5,7 +5,7 @@ use crate::{attribute::Style, prelude::Size, MetadataProvider}; use super::{ super::{ pen::PathStyle, AdjustedMetrics, DrawError, OutlineGlyph, OutlineGlyphCollection, - OutlinePen, SmoothMode, Target, + OutlinePen, Target, }, metrics::{fixed_mul, pix_round, Scale, UnscaledStyleMetricsSet}, outline::Outline, @@ -113,7 +113,8 @@ impl Instance { ); let mut outline = Outline::default(); outline.fill(glyph, coords)?; - let hinted_metrics = super::latin::hint_outline(&mut outline, &metrics, &scale); + let hinted_metrics = + super::latin::hint_outline(&mut outline, &metrics, &scale, Some(style)); let h_advance = common.advance_width(glyph_id, coords); let mut pp1x = 0; let mut pp2x = fixed_mul(h_advance, hinted_metrics.x_scale); diff --git a/skrifa/src/outline/autohint/latin/blues.rs b/skrifa/src/outline/autohint/latin/blues.rs index f94e8a6eb..7ffad1733 100644 --- a/skrifa/src/outline/autohint/latin/blues.rs +++ b/skrifa/src/outline/autohint/latin/blues.rs @@ -4,7 +4,7 @@ use super::super::{ super::{unscaled::UnscaledOutlineBuf, OutlineGlyphCollection}, cycling::{cycle_backward, cycle_forward}, metrics::{UnscaledBlue, UnscaledBlues, MAX_BLUES}, - style::{blue_flags, ScriptClass, ScriptGroup, StyleClass}, + style::{blue_flags, ScriptGroup, StyleClass}, }; use crate::{charmap::Charmap, FontRef, MetadataProvider}; use raw::types::F2Dot14; @@ -50,7 +50,7 @@ fn compute_default_blues(font: &FontRef, coords: &[F2Dot14], style: &StyleClass) let (glyphs, charmap, units_per_em) = things_all_blues_need(font); let flat_threshold = units_per_em / 14; // Walk over each of the blue character sets for our script. - for (blue_chars, blue_flags) in style.script.blues { + for (blue_str, blue_flags) in style.script.blues { let is_top_like = (blue_flags & (blue_flags::TOP | blue_flags::LATIN_SUB_TOP)) != 0; let is_top = blue_flags & blue_flags::TOP != 0; let is_x_height = blue_flags & blue_flags::LATIN_X_HEIGHT != 0; @@ -60,10 +60,19 @@ fn compute_default_blues(font: &FontRef, coords: &[F2Dot14], style: &StyleClass) let mut descender = i16::MAX; let mut n_flats = 0; let mut n_rounds = 0; - for ch in *blue_chars { + for cluster in blue_str.split(' ') { // TODO shaping: https://github.com/googlefonts/fontations/issues/1128 let y_offset = 0; - let Some(gid) = charmap.map(*ch) else { + let mut cluster_chars = cluster.chars(); + let Some(ch) = cluster_chars.next() else { + continue; + }; + // Without shaping, we need to skip multi-character clusters + // See + if cluster_chars.next().is_some() { + continue; + } + let Some(gid) = charmap.map(ch) else { continue; }; if gid.to_u32() == 0 { @@ -182,8 +191,9 @@ fn compute_default_blues(font: &FontRef, coords: &[F2Dot14], style: &StyleClass) // See // heuristic threshold value let length_threshold = units_per_em / 25; - let dist = - best_contour[segment_last].x as i32 - best_contour[segment_first].x as i32; + let dist = (best_contour[segment_last].x as i32 + - best_contour[segment_first].x as i32) + .abs(); if dist < length_threshold && segment_last - segment_first + 2 <= best_contour.len() { // heuristic threshold value @@ -237,6 +247,9 @@ fn compute_default_blues(font: &FontRef, coords: &[F2Dot14], style: &StyleClass) <= 20 * dist { hit = false; + if last == segment_first { + break; + } continue; } if best_contour[last].is_on_curve() { @@ -442,7 +455,7 @@ fn compute_cjk_blues(font: &FontRef, coords: &[F2Dot14], style: &StyleClass) -> let (mut outline_buf, mut flats, mut fills) = buffers(); let (glyphs, charmap, _) = things_all_blues_need(font); // Walk over each of the blue character sets for our script. - for (blue_chars, blue_flags) in style.script.blues { + for (blue_str, blue_flags) in style.script.blues { let is_horizontal = blue_flags & blue_flags::CJK_HORIZ != 0; // Note: horizontal blue zones are disabled by default and have been // for many years in FreeType: @@ -460,16 +473,25 @@ fn compute_cjk_blues(font: &FontRef, coords: &[F2Dot14], style: &StyleClass) -> let mut n_flats = 0; let mut n_fills = 0; let mut is_fill = true; - for ch in *blue_chars { + for cluster in blue_str.split(' ') { // The '|' character is used as a sentinel in the blue string that // signifies a switch to characters that define "flat" values // - if *ch == '|' { + if cluster == "|" { is_fill = false; continue; } // TODO shaping: https://github.com/googlefonts/fontations/issues/1128 - let Some(gid) = charmap.map(*ch) else { + let mut cluster_chars = cluster.chars(); + let Some(ch) = cluster_chars.next() else { + continue; + }; + // Without shaping, we need to skip multi-character clusters + // See + if cluster_chars.next().is_some() { + continue; + } + let Some(gid) = charmap.map(ch) else { continue; }; if gid.to_u32() == 0 { diff --git a/skrifa/src/outline/autohint/latin/edges.rs b/skrifa/src/outline/autohint/latin/edges.rs index c79e6611a..15d12f765 100644 --- a/skrifa/src/outline/autohint/latin/edges.rs +++ b/skrifa/src/outline/autohint/latin/edges.rs @@ -116,7 +116,7 @@ pub(crate) fn compute_edges( break; } } - if dist2 > edge_distance_threshold { + if dist2 >= edge_distance_threshold { continue; } } @@ -207,6 +207,9 @@ fn compute_edge_properties(axis: &mut Axis) { let mut segment_ix = edge.first_ix as usize; let last_segment_ix = edge.last_ix as usize; loop { + // This loop can modify the current edge, so make sure we + // reload it here + let edge = edges[edge_ix]; let segment = &segments[segment_ix]; let next_segment_ix = segment.edge_next_ix; // Check roundness diff --git a/skrifa/src/outline/autohint/latin/hint.rs b/skrifa/src/outline/autohint/latin/hint.rs index 30e92e3ad..96893c777 100644 --- a/skrifa/src/outline/autohint/latin/hint.rs +++ b/skrifa/src/outline/autohint/latin/hint.rs @@ -5,7 +5,7 @@ //! the original outline points. use super::super::{ - axis::{Axis, Dimension, Edge}, + axis::{Axis, Edge}, metrics::{fixed_mul_div, pix_floor, pix_round, Scale, ScaledAxisMetrics, ScaledWidth}, style::ScriptGroup, }; @@ -39,7 +39,7 @@ pub(crate) fn hint_edges( let edges = axis.edges.as_mut_slice(); // Special case for lowercase m if axis.dim == Axis::HORIZONTAL && (edges.len() == 6 || edges.len() == 12) { - hint_lowercase_m(edges); + hint_lowercase_m(edges, group); } // Handle serifs and single segment edges if serif_count > 0 || anchor_ix.is_none() { @@ -277,7 +277,7 @@ fn align_stem_edges( /// Make sure that lowercase m's maintain symmetry. /// /// See -fn hint_lowercase_m(edges: &mut [Edge]) { +fn hint_lowercase_m(edges: &mut [Edge], group: ScriptGroup) { let (edge1_ix, edge2_ix, edge3_ix) = if edges.len() == 6 { (0, 2, 4) } else { @@ -289,6 +289,15 @@ fn hint_lowercase_m(edges: &mut [Edge]) { let dist1 = edge2.opos - edge1.opos; let dist2 = edge3.opos - edge2.opos; let span = (dist1 - dist2).abs(); + if group != ScriptGroup::Default { + // CJK has additional conditions on the following... + // See + for (edge, ix) in [(edge1, edge1_ix), (edge2, edge2_ix), (edge3, edge3_ix)] { + if edge.link_ix != Some((ix + 1) as u16) { + return; + } + } + } if span < 8 { let delta = edge3.pos - (2 * edge2.pos - edge1.pos); let link_ix = edge3.link_ix.map(|ix| ix as usize); @@ -317,7 +326,7 @@ fn align_remaining_edges( mut anchor_ix: Option, ) { if group == ScriptGroup::Default { - /// See + // See for edge_ix in 0..axis.edges.len() { let edges = &mut axis.edges; let edge = &edges[edge_ix]; @@ -576,25 +585,26 @@ fn stem_width( } dist = (dist - new_base_delta.abs() + 32) & !63; } - } else { - // Divergent CJK behavior - // See - if dist < 54 { - dist += (54 - dist) / 2; - } else if dist < 3 * 64 { - let delta = dist & 63; - dist &= -64; - if delta < 10 { - dist += delta; - } else if delta < 22 { - dist += 10; - } else if delta < 42 { - dist += delta; - } else if delta < 54 { - dist += 54; - } else { - dist += delta; - } + } + } + if group != ScriptGroup::Default { + // Divergent CJK behavior + // See + if dist < 54 { + dist += (54 - dist) / 2; + } else if dist < 3 * 64 { + let delta = dist & 63; + dist &= -64; + if delta < 10 { + dist += delta; + } else if delta < 22 { + dist += 10; + } else if delta < 42 { + dist += delta; + } else if delta < 54 { + dist += 54; + } else { + dist += delta; } } } diff --git a/skrifa/src/outline/autohint/latin/metrics.rs b/skrifa/src/outline/autohint/latin/metrics.rs index 43aa8379f..c53beffe5 100644 --- a/skrifa/src/outline/autohint/latin/metrics.rs +++ b/skrifa/src/outline/autohint/latin/metrics.rs @@ -10,10 +10,10 @@ use super::super::{ axis::{Axis, Dimension}, metrics::{ fixed_div, fixed_mul, fixed_mul_div, pix_round, Scale, ScaledAxisMetrics, ScaledBlue, - ScaledStyleMetrics, ScaledWidth, UnscaledAxisMetrics, UnscaledBlue, UnscaledBlues, - UnscaledStyleMetrics, WidthMetrics, + ScaledStyleMetrics, ScaledWidth, UnscaledAxisMetrics, UnscaledBlue, UnscaledStyleMetrics, + WidthMetrics, }, - style::{blue_flags, ScriptClass, ScriptGroup, StyleClass}, + style::{blue_flags, ScriptGroup, StyleClass}, }; use crate::{prelude::Size, MetadataProvider}; use raw::{types::F2Dot14, FontRef}; @@ -26,9 +26,28 @@ pub(crate) fn compute_unscaled_style_metrics( coords: &[F2Dot14], style: &StyleClass, ) -> UnscaledStyleMetrics { + let charmap = font.charmap(); + // We don't attempt to produce any metrics if we don't have a Unicode + // cmap + // See + if charmap.is_symbol() { + return UnscaledStyleMetrics { + class_ix: style.index as u16, + axes: [ + UnscaledAxisMetrics { + dim: Axis::HORIZONTAL, + ..Default::default() + }, + UnscaledAxisMetrics { + dim: Axis::VERTICAL, + ..Default::default() + }, + ], + ..Default::default() + }; + } let [hwidths, vwidths] = super::widths::compute_widths(font, coords, style.script); let [hblues, vblues] = super::blues::compute_unscaled_blues(font, coords, style); - let charmap = font.charmap(); let glyph_metrics = font.glyph_metrics(Size::unscaled(), coords); let mut digit_advance = None; let mut digits_have_same_width = true; @@ -89,11 +108,7 @@ pub(crate) fn scale_style_metrics( scale_axis(&unscaled_metrics.axes[0]), scale_axis(&unscaled_metrics.axes[1]), ]; - ScaledStyleMetrics { - scale, - group: unscaled_metrics.style_class().script.group, - axes, - } + ScaledStyleMetrics { scale, axes } } /// Computes scaled metrics for a single axis. @@ -132,7 +147,7 @@ fn scale_default_axis_metrics( for blue in blues { max_height = max_height.max(blue.ascender).max(-blue.descender); } - let mut dist = fixed_mul(max_height, new_scale - axis.scale); + let mut dist = fixed_mul(max_height, new_scale - axis.scale).abs(); dist &= !127; if dist == 0 { axis.scale = new_scale; @@ -257,7 +272,7 @@ fn scale_cjk_axis_metrics( }; // A blue zone is only active if it is less than 3/4 pixels tall let dist = fixed_mul(unscaled_blue.position - unscaled_blue.overshoot, scale); - if dist <= 48 || dist >= -48 { + if (-48..=48).contains(&dist) { blue.position.fitted = pix_round(blue.position.scaled); // For CJK, "overshoot" is actually undershoot let delta1 = fixed_div(blue.position.fitted, scale) - unscaled_blue.overshoot; diff --git a/skrifa/src/outline/autohint/latin/mod.rs b/skrifa/src/outline/autohint/latin/mod.rs index 9bc2d6a13..1ec8ad1f6 100644 --- a/skrifa/src/outline/autohint/latin/mod.rs +++ b/skrifa/src/outline/autohint/latin/mod.rs @@ -11,6 +11,7 @@ use super::{ axis::Axis, metrics::{Scale, UnscaledStyleMetrics}, outline::Outline, + style::{GlyphStyle, ScriptGroup}, }; pub(crate) use metrics::compute_unscaled_style_metrics; @@ -49,14 +50,24 @@ pub(crate) fn hint_outline( outline: &mut Outline, metrics: &UnscaledStyleMetrics, scale: &Scale, + glyph_style: Option, ) -> HintedMetrics { let scaled_metrics = metrics::scale_style_metrics(metrics, *scale); - let y_scale = scaled_metrics.axes[1].scale; + let scale = &scaled_metrics.scale; let mut axis = Axis::default(); let hint_top_to_bottom = metrics.style_class().script.hint_top_to_bottom; outline.scale(&scaled_metrics.scale); - let mut hinted_metrics = HintedMetrics::default(); + let mut hinted_metrics = HintedMetrics { + x_scale: scale.x_scale, + ..Default::default() + }; let group = metrics.style_class().script.group; + // For default script group, we don't proceed with hinting if we're + // missing alignment zones. FreeType swaps in a "dummy" hinter here + // See + if group == ScriptGroup::Default && scaled_metrics.axes[1].blues.is_empty() { + return hinted_metrics; + } for dim in 0..2 { if (dim == Axis::HORIZONTAL && scale.flags & Scale::NO_HORIZONTAL != 0) || (dim == Axis::VERTICAL && scale.flags & Scale::NO_VERTICAL != 0) @@ -80,13 +91,19 @@ pub(crate) fn hint_outline( group, ); if dim == Axis::VERTICAL { - edges::compute_blue_edges( - &mut axis, - scale, - &metrics.axes[dim].blues, - &scaled_metrics.axes[dim].blues, - group, - ); + if group != ScriptGroup::Default + || glyph_style + .map(|style| !style.is_non_base()) + .unwrap_or(true) + { + edges::compute_blue_edges( + &mut axis, + scale, + &metrics.axes[dim].blues, + &scaled_metrics.axes[dim].blues, + group, + ); + } } else { hinted_metrics.x_scale = scaled_metrics.axes[0].scale; } diff --git a/skrifa/src/outline/autohint/latin/segments.rs b/skrifa/src/outline/autohint/latin/segments.rs index 27d5a6970..860261996 100644 --- a/skrifa/src/outline/autohint/latin/segments.rs +++ b/skrifa/src/outline/autohint/latin/segments.rs @@ -6,14 +6,13 @@ //! The linking stage associates pairs of segments to form stems and //! identifies serifs with a post-process pass. -use raw::tables::glyf::PointFlags; - use super::super::{ axis::{Axis, Dimension, Segment}, metrics::fixed_div, - outline::{Outline, Point}, + outline::Outline, style::ScriptGroup, }; +use raw::tables::glyf::PointFlags; // Bounds for score, position and coordinate values. // See @@ -23,7 +22,11 @@ const MIN_SCORE: i32 = -32000; /// Computes segments for the Latin writing system. /// /// See -pub(crate) fn compute_segments(outline: &mut Outline, axis: &mut Axis, group: ScriptGroup) -> bool { +pub(crate) fn compute_segments( + outline: &mut Outline, + axis: &mut Axis, + _group: ScriptGroup, +) -> bool { assign_point_uvs(outline, axis.dim); if !build_segments(outline, axis) { return false; @@ -229,7 +232,7 @@ fn link_segments_cjk(outline: &Outline, axis: &mut Axis, scale: i32) { let Some(link2) = seg2.link(segments).copied() else { continue; }; - if link2.link_ix != Some(ix2 as u16) || link2.pos <= link1.pos { + if link2.link_ix != Some(ix2 as u16) || link2.pos < link1.pos { continue; } if seg1.pos == seg2.pos && link1.pos == link2.pos { @@ -398,10 +401,10 @@ fn build_segments(outline: &mut Outline, axis: &mut Axis) -> bool { // Discard previous segment state.min_pos = state.min_pos.min(prev_state.min_pos); state.max_pos = state.max_pos.max(prev_state.max_pos); - let segment = &mut axis.segments[segment_ix]; + let mut segment = axis.segments[segment_ix]; segment.last_ix = point_ix as u16; - state.apply_to_segment(segment, flat_threshold); - prev_segment_ix = Some(segment_ix); + state.apply_to_segment(&mut segment, flat_threshold); + axis.segments[prev_segment_ix.unwrap()] = segment; prev_state = state; } } diff --git a/skrifa/src/outline/autohint/latin/widths.rs b/skrifa/src/outline/autohint/latin/widths.rs index b00f1563f..3e65a30b4 100644 --- a/skrifa/src/outline/autohint/latin/widths.rs +++ b/skrifa/src/outline/autohint/latin/widths.rs @@ -32,11 +32,14 @@ pub(super) fn compute_widths( // We take the first available glyph from the standard character set. if let Some(glyph) = script .std_chars - .iter() - .filter_map(|&ch| glyphs.get(charmap.map(ch)?)) + .split(' ') + .map(|cluster| cluster.chars()) + .filter_map(|mut chars| Some((chars.next()?, chars.count()))) + .filter(|(_ch, remaining_count)| *remaining_count == 0) + .filter_map(|(ch, _)| glyphs.get(charmap.map(ch)?)) .next() { - if outline.fill(&glyph, coords).is_ok() { + if outline.fill(&glyph, coords).is_ok() && !outline.points.is_empty() { // Now process each dimension for (dim, (_metrics, widths)) in result.iter_mut().enumerate() { axis.reset(dim, outline.orientation); @@ -60,6 +63,13 @@ pub(super) fn compute_widths( } } } + // FreeTypes `af_sort_and_quantize_widths()`` has the side effect + // of always updating the width count to 1 when we don't find + // any... + // See + if widths.is_empty() { + widths.push(0); + } // The value 100 is heuristic metrics::sort_and_quantize_widths(widths, units_per_em / 100); } diff --git a/skrifa/src/outline/autohint/metrics.rs b/skrifa/src/outline/autohint/metrics.rs index cc0cd01b5..275ef1ab6 100644 --- a/skrifa/src/outline/autohint/metrics.rs +++ b/skrifa/src/outline/autohint/metrics.rs @@ -153,8 +153,6 @@ impl UnscaledStyleMetricsSet { pub(crate) struct ScaledStyleMetrics { /// Multidimensional scaling factors and deltas. pub scale: Scale, - /// Script set for the associated style. - pub group: ScriptGroup, /// Per-dimension scaled metrics. pub axes: [ScaledAxisMetrics; 2], } @@ -256,10 +254,17 @@ impl Scale { if is_mono { flags |= Self::MONO; } - // Disable horizontal hinting completely for LCD, light hinting - // and italic fonts. - if target.is_lcd() || is_light || is_italic { - flags |= Self::NO_HORIZONTAL; + if group == ScriptGroup::Default { + // Disable horizontal hinting completely for LCD, light hinting + // and italic fonts + // See + if target.is_lcd() || is_light || is_italic { + flags |= Self::NO_HORIZONTAL; + } + } else { + // CJK doesn't hint advances + // See + flags |= Self::NO_ADVANCE; } // CJK doesn't hint advances // See @@ -375,7 +380,6 @@ mod tests { use super::{super::style::STYLE_CLASSES, *}; use crate::MetadataProvider; use raw::TableProvider; - use std::sync::Arc; #[test] fn sort_widths() { diff --git a/skrifa/src/outline/autohint/mod.rs b/skrifa/src/outline/autohint/mod.rs index 7a561aa1b..8b760f63b 100644 --- a/skrifa/src/outline/autohint/mod.rs +++ b/skrifa/src/outline/autohint/mod.rs @@ -1,8 +1,5 @@ //! Runtime autohinting support. -// Remove when the work is complete. -#![allow(dead_code, unused)] - mod axis; mod cycling; mod hint; diff --git a/skrifa/src/outline/autohint/outline.rs b/skrifa/src/outline/autohint/outline.rs index 3b2182739..d6ecb453c 100644 --- a/skrifa/src/outline/autohint/outline.rs +++ b/skrifa/src/outline/autohint/outline.rs @@ -266,7 +266,7 @@ impl Outline { let mut first_ix = contour.first(); let mut ix = first_ix; let mut prev_ix = contour.prev(first_ix); - let mut point = points[0]; + let mut point = points[first_ix]; while prev_ix != first_ix { let prev = points[prev_ix]; let out_x = point.fx - prev.fx; @@ -341,6 +341,9 @@ impl Outline { let points = self.points.as_mut_slice(); for i in 0..points.len() { let point = points[i]; + if point.flags.has_marker(PointMarker::WEAK_INTERPOLATION) { + continue; + } if point.in_dir == Direction::None && point.out_dir == Direction::None { let u_index = point.u as usize; let v_index = point.v as usize; @@ -761,7 +764,7 @@ mod tests { let mut base_svg = SvgPen::default(); let settings = DrawSettings::unhinted(Size::unscaled(), LocationRef::default()) .with_path_style(path_style); - glyph.draw(settings, &mut base_svg); + glyph.draw(settings, &mut base_svg).unwrap(); let base_svg = base_svg.to_string(); // Autohinter outline code path let mut outline = Outline::default(); @@ -774,7 +777,7 @@ mod tests { point.y = point.fy << 6; } let mut autohint_svg = SvgPen::default(); - outline.to_path(path_style, &mut autohint_svg); + outline.to_path(path_style, &mut autohint_svg).unwrap(); let autohint_svg = autohint_svg.to_string(); if base_svg != autohint_svg { results.push((gid, base_svg, autohint_svg)); diff --git a/skrifa/src/outline/autohint/style.rs b/skrifa/src/outline/autohint/style.rs index 7ff8ffbad..30515fdc9 100644 --- a/skrifa/src/outline/autohint/style.rs +++ b/skrifa/src/outline/autohint/style.rs @@ -43,6 +43,21 @@ impl GlyphStyle { None } } + + fn maybe_assign(&mut self, other: Self) { + // FreeType walks the style array in order so earlier styles + // have precedence. Since we walk the cmap and binary search + // on the full range mapping, our styles are mapped in a + // different order. This check allows us to replace a currently + // mapped style if the new style index is lower which matches + // FreeType's behavior. + // + // Note that we keep the extra bits because FreeType allows + // setting the NON_BASE bit to an already mapped style. + if other.0 & Self::STYLE_INDEX_MASK <= self.0 & Self::STYLE_INDEX_MASK { + self.0 = (self.0 & !Self::STYLE_INDEX_MASK) | other.0; + } + } } impl Default for GlyphStyle { @@ -89,14 +104,11 @@ impl GlyphStyleMap { let Some(style) = map.styles.get_mut(gid.to_u32() as usize) else { continue; }; - if !style.is_unassigned() { - continue; - } // Charmaps enumerate in order so we're likely to encounter at least // a few codepoints in the same range. if let Some(last) = last_range { if last.1.contains(ch) { - *style = last.1.style; + style.maybe_assign(last.1.style); continue; } } @@ -108,7 +120,7 @@ impl GlyphStyleMap { continue; }; if range.contains(ch) { - *style = range.style; + style.maybe_assign(range.style); if let Some(style_ix) = range.style.style_index() { let style_ix = style_ix as usize; if map.metrics_map[style_ix] == 0xFF { @@ -215,25 +227,19 @@ pub(crate) enum ScriptGroup { /// autohinter. #[derive(Clone, Debug)] pub(crate) struct ScriptClass { + #[allow(unused)] pub name: &'static str, /// Group that defines how glyphs belonging to this script are hinted. pub group: ScriptGroup, /// Unicode tag for the script. + #[allow(unused)] pub tag: Tag, - /// Index of self in the SCRIPT_CLASSES array. - pub index: usize, /// True if outline edges are processed top to bottom. pub hint_top_to_bottom: bool, /// Characters used to define standard width and height of stems. - pub std_chars: &'static [char], + pub std_chars: &'static str, /// "Blue" characters used to define alignment zones. - pub blues: &'static [(&'static [char], u32)], -} - -impl ScriptClass { - pub fn from_index(index: u16) -> Option<&'static ScriptClass> { - SCRIPT_CLASSES.get(index as usize) - } + pub blues: &'static [(&'static str, u32)], } /// Defines the basic properties for each style supported by the @@ -244,6 +250,7 @@ impl ScriptClass { /// coverage. #[derive(Clone, Debug)] pub(crate) struct StyleClass { + #[allow(unused)] pub name: &'static str, /// Index of self in the STYLE_CLASSES array. pub index: usize, @@ -251,6 +258,7 @@ pub(crate) struct StyleClass { pub script: &'static ScriptClass, /// OpenType feature tag for styles that derive coverage from layout /// tables. + #[allow(unused)] pub feature: Option, } diff --git a/skrifa/src/outline/mod.rs b/skrifa/src/outline/mod.rs index 19cbeb1f9..b6d73af63 100644 --- a/skrifa/src/outline/mod.rs +++ b/skrifa/src/outline/mod.rs @@ -446,7 +446,7 @@ impl<'a> OutlineGlyph<'a> { sink.try_reserve(outline.points.len())?; let mut contour_start = 0; for contour_end in outline.contours.iter().map(|contour| *contour as usize) { - if contour_end > contour_start { + if contour_end >= contour_start { if let Some(points) = outline.points.get(contour_start..=contour_end) { let flags = &outline.flags[contour_start..=contour_end]; sink.extend(points.iter().zip(flags).enumerate().map( diff --git a/skrifa/src/outline/path.rs b/skrifa/src/outline/path.rs index 5adb2138b..052511bdb 100644 --- a/skrifa/src/outline/path.rs +++ b/skrifa/src/outline/path.rs @@ -373,7 +373,7 @@ mod tests { // For this test case (in 26.6 fixed point): [(640, 128) + (128, 128)] / 2 = (384, 128) // which becomes (6.0, 2.0) when converted to floating point. let points = [pt(640, 128), pt(256, 64), pt(640, 64), pt(128, 128)]; - let mut pen = SvgPen::default(); + let mut pen = SvgPen::with_precision(1); to_path(&points, &flags, &contours, path_style, &mut pen).unwrap(); assert_eq!(pen.as_ref(), expected); } diff --git a/skrifa/src/outline/pen.rs b/skrifa/src/outline/pen.rs index 5a06cf146..4b12b2b98 100644 --- a/skrifa/src/outline/pen.rs +++ b/skrifa/src/outline/pen.rs @@ -111,9 +111,21 @@ impl OutlinePen for NullPen { /// Pen that generates SVG style path data. #[derive(Clone, Default, Debug)] -pub struct SvgPen(String); +pub struct SvgPen(String, Option); impl SvgPen { + /// Creates a new SVG pen that formats floating point values with the + /// standard behavior. + pub fn new() -> Self { + Self::default() + } + + /// Creates a new SVG pen with the given precision (the number of digits + /// that will be printed after the decimal). + pub fn with_precision(precision: usize) -> Self { + Self(String::default(), Some(precision)) + } + /// Clears the content of the internal string. pub fn clear(&mut self) { self.0.clear(); @@ -137,22 +149,42 @@ impl core::ops::Deref for SvgPen { impl OutlinePen for SvgPen { fn move_to(&mut self, x: f32, y: f32) { self.maybe_push_space(); - let _ = write!(self.0, "M{x:.1},{y:.1}"); + let _ = if let Some(prec) = self.1 { + write!(self.0, "M{x:.0$},{y:.0$}", prec) + } else { + write!(self.0, "M{x},{y}") + }; } fn line_to(&mut self, x: f32, y: f32) { self.maybe_push_space(); - let _ = write!(self.0, "L{x:.1},{y:.1}"); + let _ = if let Some(prec) = self.1 { + write!(self.0, "L{x:.0$},{y:.0$}", prec) + } else { + write!(self.0, "L{x},{y}") + }; } fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) { self.maybe_push_space(); - let _ = write!(self.0, "Q{cx0:.1},{cy0:.1} {x:.1},{y:.1}"); + let _ = if let Some(prec) = self.1 { + write!(self.0, "Q{cx0:.0$},{cy0:.0$} {x:.0$},{y:.0$}", prec) + } else { + write!(self.0, "Q{cx0},{cy0} {x},{y}") + }; } fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) { self.maybe_push_space(); - let _ = write!(self.0, "C{cx0:.1},{cy0:.1} {cx1:.1},{cy1:.1} {x:.1},{y:.1}"); + let _ = if let Some(prec) = self.1 { + write!( + self.0, + "C{cx0:.0$},{cy0:.0$} {cx1:.0$},{cy1:.0$} {x:.0$},{y:.0$}", + prec + ) + } else { + write!(self.0, "C{cx0},{cy0} {cx1},{cy1} {x},{y}") + }; } fn close(&mut self) { @@ -169,7 +201,7 @@ impl AsRef for SvgPen { impl From for SvgPen { fn from(value: String) -> Self { - Self(value) + Self(value, None) } } @@ -184,3 +216,32 @@ impl fmt::Display for SvgPen { write!(f, "{}", self.0) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn svg_pen_precision() { + let svg_data = [None, Some(1), Some(4)].map(|prec| { + let mut pen = match prec { + None => SvgPen::new(), + Some(prec) => SvgPen::with_precision(prec), + }; + pen.move_to(1.0, 2.45556); + pen.line_to(1.2, 4.0); + pen.quad_to(2.0345, 3.56789, -0.157, -425.07); + pen.curve_to(-37.0010, 4.5, 2.0, 1.0, -0.5, -0.25); + pen.close(); + pen.to_string() + }); + let expected = [ + "M1,2.45556 L1.2,4 Q2.0345,3.56789 -0.157,-425.07 C-37.001,4.5 2,1 -0.5,-0.25 Z", + "M1.0,2.5 L1.2,4.0 Q2.0,3.6 -0.2,-425.1 C-37.0,4.5 2.0,1.0 -0.5,-0.2 Z", + "M1.0000,2.4556 L1.2000,4.0000 Q2.0345,3.5679 -0.1570,-425.0700 C-37.0010,4.5000 2.0000,1.0000 -0.5000,-0.2500 Z" + ]; + for (result, expected) in svg_data.iter().zip(&expected) { + assert_eq!(result, expected); + } + } +} diff --git a/skrifa/src/outline/unscaled.rs b/skrifa/src/outline/unscaled.rs index 6281cef28..d5eca5332 100644 --- a/skrifa/src/outline/unscaled.rs +++ b/skrifa/src/outline/unscaled.rs @@ -115,6 +115,12 @@ impl<'a> UnscaledOutlineRef<'a> { } cur_contour = point_ix..point_ix; found_best_in_cur_contour = false; + // Ignore single point contours + match self.points.get(point_ix + 1) { + Some(next_point) if next_point.is_contour_start => continue, + None => continue, + _ => {} + } } cur_contour.end += 1; if f(point) {