Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

529
vendor/rustybuzz/src/hb/aat_layout.rs vendored Normal file
View File

@@ -0,0 +1,529 @@
#![allow(dead_code)]
use super::buffer::{hb_buffer_t, hb_glyph_info_t};
use super::hb_font_t;
use super::hb_tag_t;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::{aat_layout_kerx_table, aat_layout_morx_table, aat_layout_trak_table};
pub type hb_aat_layout_feature_type_t = u8;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_INVALID: u8 = 0xFF;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_ALL_TYPOGRAPHIC: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_LIGATURES: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_CURISVE_CONNECTION: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_SUBSTITUTION: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_LINGUISTIC_REARRANGEMENT: u8 = 5;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_NUMBER_SPACING: u8 = 6;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_SMART_SWASH_TYPE: u8 = 8;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_DIACRITICS_TYPE: u8 = 9;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_POSITION: u8 = 10;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_FRACTIONS: u8 = 11;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_OVERLAPPING_CHARACTERS_TYPE: u8 = 13;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_TYPOGRAPHIC_EXTRAS: u8 = 14;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_MATHEMATICAL_EXTRAS: u8 = 15;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_ORNAMENT_SETS_TYPE: u8 = 16;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_ALTERNATIVES: u8 = 17;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_DESIGN_COMPLEXITY_TYPE: u8 = 18;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_STYLE_OPTIONS: u8 = 19;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE: u8 = 20;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_NUMBER_CASE: u8 = 21;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING: u8 = 22;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_TRANSLITERATION: u8 = 23;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_ANNOTATION_TYPE: u8 = 24;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_KANA_SPACING_TYPE: u8 = 25;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_IDEOGRAPHIC_SPACING_TYPE: u8 = 26;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_UNICODE_DECOMPOSITION_TYPE: u8 = 27;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_RUBY_KANA: u8 = 28;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_CJK_SYMBOL_ALTERNATIVES_TYPE: u8 = 29;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_IDEOGRAPHIC_ALTERNATIVES_TYPE: u8 = 30;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_CJK_VERTICAL_ROMAN_PLACEMENT_TYPE: u8 = 31;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_ITALIC_CJK_ROMAN: u8 = 32;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_CASE_SENSITIVE_LAYOUT: u8 = 33;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_ALTERNATE_KANA: u8 = 34;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES: u8 = 35;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_CONTEXTUAL_ALTERNATIVES: u8 = 36;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE: u8 = 37;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_UPPER_CASE: u8 = 38;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_LANGUAGE_TAG_TYPE: u8 = 39;
pub const HB_AAT_LAYOUT_FEATURE_TYPE_CJK_ROMAN_SPACING_TYPE: u8 = 103;
pub type hb_aat_layout_feature_selector_t = u8;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_INVALID: u8 = 0xFF;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_ALL_TYPOGRAPHIC */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ALL_TYPE_FEATURES_ON: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ALL_TYPE_FEATURES_OFF: u8 = 1;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_LIGATURES */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_REQUIRED_LIGATURES_ON: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_REQUIRED_LIGATURES_OFF: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_COMMON_LIGATURES_ON: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_COMMON_LIGATURES_OFF: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_RARE_LIGATURES_ON: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_RARE_LIGATURES_OFF: u8 = 5;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_LOGOS_ON: u8 = 6;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_LOGOS_OFF: u8 = 7;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_REBUS_PICTURES_ON: u8 = 8;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_REBUS_PICTURES_OFF: u8 = 9;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DIPHTHONG_LIGATURES_ON: u8 = 10;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DIPHTHONG_LIGATURES_OFF: u8 = 11;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SQUARED_LIGATURES_ON: u8 = 12;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SQUARED_LIGATURES_OFF: u8 = 13;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ABBREV_SQUARED_LIGATURES_ON: u8 = 14;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ABBREV_SQUARED_LIGATURES_OFF: u8 = 15;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SYMBOL_LIGATURES_ON: u8 = 16;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SYMBOL_LIGATURES_OFF: u8 = 17;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_LIGATURES_ON: u8 = 18;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_LIGATURES_OFF: u8 = 19;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HISTORICAL_LIGATURES_ON: u8 = 20;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HISTORICAL_LIGATURES_OFF: u8 = 21;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_LIGATURES */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_UNCONNECTED: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PARTIALLY_CONNECTED: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CURSIVE: u8 = 2;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_UPPER_AND_LOWER_CASE: u8 = 0; /* deprecated */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ALL_CAPS: u8 = 1; /* deprecated */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ALL_LOWER_CASE: u8 = 2; /* deprecated */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SMALL_CAPS: u8 = 3; /* deprecated */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_INITIAL_CAPS: u8 = 4; /* deprecated */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_INITIAL_CAPS_AND_SMALL_CAPS: u8 = 5; /* deprecated */
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_SUBSTITUTION */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SUBSTITUTE_VERTICAL_FORMS_ON: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SUBSTITUTE_VERTICAL_FORMS_OFF: u8 = 1;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_LINGUISTIC_REARRANGEMENT */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_LINGUISTIC_REARRANGEMENT_ON: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_LINGUISTIC_REARRANGEMENT_OFF: u8 = 1;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_NUMBER_SPACING */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_MONOSPACED_NUMBERS: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PROPORTIONAL_NUMBERS: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_THIRD_WIDTH_NUMBERS: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_QUARTER_WIDTH_NUMBERS: u8 = 3;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_SMART_SWASH_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_WORD_INITIAL_SWASHES_ON: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_WORD_INITIAL_SWASHES_OFF: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_WORD_FINAL_SWASHES_ON: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_WORD_FINAL_SWASHES_OFF: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_LINE_INITIAL_SWASHES_ON: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_LINE_INITIAL_SWASHES_OFF: u8 = 5;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_LINE_FINAL_SWASHES_ON: u8 = 6;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_LINE_FINAL_SWASHES_OFF: u8 = 7;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NON_FINAL_SWASHES_ON: u8 = 8;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NON_FINAL_SWASHES_OFF: u8 = 9;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_DIACRITICS_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SHOW_DIACRITICS: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HIDE_DIACRITICS: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DECOMPOSE_DIACRITICS: u8 = 2;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_POSITION */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NORMAL_POSITION: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SUPERIORS: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_INFERIORS: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ORDINALS: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SCIENTIFIC_INFERIORS: u8 = 4;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_FRACTIONS */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_FRACTIONS: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_VERTICAL_FRACTIONS: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DIAGONAL_FRACTIONS: u8 = 2;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_OVERLAPPING_CHARACTERS_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PREVENT_OVERLAP_ON: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PREVENT_OVERLAP_OFF: u8 = 1;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_TYPOGRAPHIC_EXTRAS */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HYPHENS_TO_EM_DASH_ON: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HYPHENS_TO_EM_DASH_OFF: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HYPHEN_TO_EN_DASH_ON: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HYPHEN_TO_EN_DASH_OFF: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SLASHED_ZERO_ON: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SLASHED_ZERO_OFF: u8 = 5;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_FORM_INTERROBANG_ON: u8 = 6;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_FORM_INTERROBANG_OFF: u8 = 7;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SMART_QUOTES_ON: u8 = 8;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SMART_QUOTES_OFF: u8 = 9;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PERIODS_TO_ELLIPSIS_ON: u8 = 10;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PERIODS_TO_ELLIPSIS_OFF: u8 = 11;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_MATHEMATICAL_EXTRAS */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HYPHEN_TO_MINUS_ON: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HYPHEN_TO_MINUS_OFF: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ASTERISK_TO_MULTIPLY_ON: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ASTERISK_TO_MULTIPLY_OFF: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SLASH_TO_DIVIDE_ON: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SLASH_TO_DIVIDE_OFF: u8 = 5;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_INEQUALITY_LIGATURES_ON: u8 = 6;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_INEQUALITY_LIGATURES_OFF: u8 = 7;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_EXPONENTS_ON: u8 = 8;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_EXPONENTS_OFF: u8 = 9;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_MATHEMATICAL_GREEK_ON: u8 = 10;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_MATHEMATICAL_GREEK_OFF: u8 = 11;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_ORNAMENT_SETS_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_ORNAMENTS: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DINGBATS: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PI_CHARACTERS: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_FLEURONS: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DECORATIVE_BORDERS: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_INTERNATIONAL_SYMBOLS: u8 = 5;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_MATH_SYMBOLS: u8 = 6;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_ALTERNATIVES */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_ALTERNATES: u8 = 0;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_DESIGN_COMPLEXITY_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DESIGN_LEVEL1: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DESIGN_LEVEL2: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DESIGN_LEVEL3: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DESIGN_LEVEL4: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DESIGN_LEVEL5: u8 = 4;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_STYLE_OPTIONS */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_STYLE_OPTIONS: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DISPLAY_TEXT: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ENGRAVED_TEXT: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ILLUMINATED_CAPS: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_TITLING_CAPS: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_TALL_CAPS: u8 = 5;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_TRADITIONAL_CHARACTERS: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SIMPLIFIED_CHARACTERS: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_JIS1978_CHARACTERS: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_JIS1983_CHARACTERS: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_JIS1990_CHARACTERS: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_TRADITIONAL_ALT_ONE: u8 = 5;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_TRADITIONAL_ALT_TWO: u8 = 6;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_TRADITIONAL_ALT_THREE: u8 = 7;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_TRADITIONAL_ALT_FOUR: u8 = 8;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_TRADITIONAL_ALT_FIVE: u8 = 9;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_EXPERT_CHARACTERS: u8 = 10;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_JIS2004_CHARACTERS: u8 = 11;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HOJO_CHARACTERS: u8 = 12;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NLCCHARACTERS: u8 = 13;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_TRADITIONAL_NAMES_CHARACTERS: u8 = 14;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_NUMBER_CASE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_NUMBERS: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_UPPER_CASE_NUMBERS: u8 = 1;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PROPORTIONAL_TEXT: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_MONOSPACED_TEXT: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HALF_WIDTH_TEXT: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_THIRD_WIDTH_TEXT: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_QUARTER_WIDTH_TEXT: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ALT_PROPORTIONAL_TEXT: u8 = 5;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ALT_HALF_WIDTH_TEXT: u8 = 6;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_TRANSLITERATION */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_TRANSLITERATION: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HANJA_TO_HANGUL: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HIRAGANA_TO_KATAKANA: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_KATAKANA_TO_HIRAGANA: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_KANA_TO_ROMANIZATION: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ROMANIZATION_TO_HIRAGANA: u8 = 5;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ROMANIZATION_TO_KATAKANA: u8 = 6;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HANJA_TO_HANGUL_ALT_ONE: u8 = 7;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HANJA_TO_HANGUL_ALT_TWO: u8 = 8;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HANJA_TO_HANGUL_ALT_THREE: u8 = 9;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_ANNOTATION_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_ANNOTATION: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_BOX_ANNOTATION: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ROUNDED_BOX_ANNOTATION: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CIRCLE_ANNOTATION: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_INVERTED_CIRCLE_ANNOTATION: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PARENTHESIS_ANNOTATION: u8 = 5;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PERIOD_ANNOTATION: u8 = 6;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ROMAN_NUMERAL_ANNOTATION: u8 = 7;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DIAMOND_ANNOTATION: u8 = 8;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_INVERTED_BOX_ANNOTATION: u8 = 9;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_INVERTED_ROUNDED_BOX_ANNOTATIO: u8 = 10;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_KANA_SPACING_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_FULL_WIDTH_KANA: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PROPORTIONAL_KANA: u8 = 1;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_IDEOGRAPHIC_SPACING_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_FULL_WIDTH_IDEOGRAPHS: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PROPORTIONAL_IDEOGRAPHS: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HALF_WIDTH_IDEOGRAPHS: u8 = 2;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_UNICODE_DECOMPOSITION_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CANONICAL_COMPOSITION_ON: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CANONICAL_COMPOSITION_OFF: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_COMPATIBILITY_COMPOSITION_ON: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_COMPATIBILITY_COMPOSITION_OFF: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_TRANSCODING_COMPOSITION_ON: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_TRANSCODING_COMPOSITION_OFF: u8 = 5;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_RUBY_KANA */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_RUBY_KANA: u8 = 0; /* deprecated - use HB_AAT_LAYOUT_FEATURE_SELECTOR_RUBY_KANA_OFF instead */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_RUBY_KANA: u8 = 1; /* deprecated - use HB_AAT_LAYOUT_FEATURE_SELECTOR_RUBY_KANA_ON instead */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_RUBY_KANA_ON: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_RUBY_KANA_OFF: u8 = 3;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_CJK_SYMBOL_ALTERNATIVES_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_CJK_SYMBOL_ALTERNATIVES: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_SYMBOL_ALT_ONE: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_SYMBOL_ALT_TWO: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_SYMBOL_ALT_THREE: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_SYMBOL_ALT_FOUR: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_SYMBOL_ALT_FIVE: u8 = 5;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_IDEOGRAPHIC_ALTERNATIVES_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_IDEOGRAPHIC_ALTERNATIVES: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_IDEOGRAPHIC_ALT_ONE: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_IDEOGRAPHIC_ALT_TWO: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_IDEOGRAPHIC_ALT_THREE: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_IDEOGRAPHIC_ALT_FOUR: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_IDEOGRAPHIC_ALT_FIVE: u8 = 5;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_CJK_VERTICAL_ROMAN_PLACEMENT_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_VERTICAL_ROMAN_CENTERED: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_VERTICAL_ROMAN_HBASELINE: u8 = 1;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_ITALIC_CJK_ROMAN */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_CJK_ITALIC_ROMAN: u8 = 0; /* deprecated - use HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_ITALIC_ROMAN_OFF instead */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_ITALIC_ROMAN: u8 = 1; /* deprecated - use HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_ITALIC_ROMAN_ON instead */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_ITALIC_ROMAN_ON: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_ITALIC_ROMAN_OFF: u8 = 3;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_CASE_SENSITIVE_LAYOUT */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CASE_SENSITIVE_LAYOUT_ON: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CASE_SENSITIVE_LAYOUT_OFF: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CASE_SENSITIVE_SPACING_ON: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CASE_SENSITIVE_SPACING_OFF: u8 = 3;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_ALTERNATE_KANA */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ALTERNATE_HORIZ_KANA_ON: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ALTERNATE_HORIZ_KANA_OFF: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ALTERNATE_VERT_KANA_ON: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_ALTERNATE_VERT_KANA_OFF: u8 = 3;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_STYLISTIC_ALTERNATES: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_ONE_ON: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_ONE_OFF: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWO_ON: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWO_OFF: u8 = 5;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_THREE_ON: u8 = 6;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_THREE_OFF: u8 = 7;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FOUR_ON: u8 = 8;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FOUR_OFF: u8 = 9;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FIVE_ON: u8 = 10;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FIVE_OFF: u8 = 11;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SIX_ON: u8 = 12;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SIX_OFF: u8 = 13;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SEVEN_ON: u8 = 14;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SEVEN_OFF: u8 = 15;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_EIGHT_ON: u8 = 16;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_EIGHT_OFF: u8 = 17;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_NINE_ON: u8 = 18;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_NINE_OFF: u8 = 19;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TEN_ON: u8 = 20;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TEN_OFF: u8 = 21;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_ELEVEN_ON: u8 = 22;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_ELEVEN_OFF: u8 = 23;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWELVE_ON: u8 = 24;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWELVE_OFF: u8 = 25;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_THIRTEEN_ON: u8 = 26;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_THIRTEEN_OFF: u8 = 27;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FOURTEEN_ON: u8 = 28;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FOURTEEN_OFF: u8 = 29;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FIFTEEN_ON: u8 = 30;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FIFTEEN_OFF: u8 = 31;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SIXTEEN_ON: u8 = 32;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SIXTEEN_OFF: u8 = 33;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SEVENTEEN_ON: u8 = 34;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SEVENTEEN_OFF: u8 = 35;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_EIGHTEEN_ON: u8 = 36;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_EIGHTEEN_OFF: u8 = 37;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_NINETEEN_ON: u8 = 38;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_NINETEEN_OFF: u8 = 39;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWENTY_ON: u8 = 40;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWENTY_OFF: u8 = 41;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_CONTEXTUAL_ALTERNATIVES */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_ALTERNATES_ON: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_ALTERNATES_OFF: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SWASH_ALTERNATES_ON: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_SWASH_ALTERNATES_OFF: u8 = 3;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_SWASH_ALTERNATES_ON: u8 = 4;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_SWASH_ALTERNATES_OFF: u8 = 5;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DEFAULT_LOWER_CASE: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_PETITE_CAPS: u8 = 2;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_UPPER_CASE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DEFAULT_UPPER_CASE: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_UPPER_CASE_SMALL_CAPS: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_UPPER_CASE_PETITE_CAPS: u8 = 2;
/* Selectors for #HB_AAT_LAYOUT_FEATURE_TYPE_CJK_ROMAN_SPACING_TYPE */
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_HALF_WIDTH_CJK_ROMAN: u8 = 0;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_PROPORTIONAL_CJK_ROMAN: u8 = 1;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_DEFAULT_CJK_ROMAN: u8 = 2;
pub const HB_AAT_LAYOUT_FEATURE_SELECTOR_FULL_WIDTH_CJK_ROMAN: u8 = 3;
pub struct hb_aat_feature_mapping_t {
pub ot_feature_tag: hb_tag_t,
pub aat_feature_type: hb_aat_layout_feature_type_t,
pub selector_to_enable: u8,
pub selector_to_disable: u8,
}
impl hb_aat_feature_mapping_t {
const fn new(
ot_feature_tag: &[u8; 4],
aat_feature_type: hb_aat_layout_feature_type_t,
selector_to_enable: u8,
selector_to_disable: u8,
) -> Self {
hb_aat_feature_mapping_t {
ot_feature_tag: hb_tag_t::from_bytes(ot_feature_tag),
aat_feature_type,
selector_to_enable,
selector_to_disable,
}
}
}
/// Mapping from OpenType feature tags to AAT feature names and selectors.
///
/// Table data courtesy of Apple.
/// Converted from mnemonics to integers when moving to this file.
#[rustfmt::skip]
pub const feature_mappings: &[hb_aat_feature_mapping_t] = &[
hb_aat_feature_mapping_t::new(b"afrc", HB_AAT_LAYOUT_FEATURE_TYPE_FRACTIONS, HB_AAT_LAYOUT_FEATURE_SELECTOR_VERTICAL_FRACTIONS, HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_FRACTIONS),
hb_aat_feature_mapping_t::new(b"c2pc", HB_AAT_LAYOUT_FEATURE_TYPE_UPPER_CASE, HB_AAT_LAYOUT_FEATURE_SELECTOR_UPPER_CASE_PETITE_CAPS, HB_AAT_LAYOUT_FEATURE_SELECTOR_DEFAULT_UPPER_CASE),
hb_aat_feature_mapping_t::new(b"c2sc", HB_AAT_LAYOUT_FEATURE_TYPE_UPPER_CASE, HB_AAT_LAYOUT_FEATURE_SELECTOR_UPPER_CASE_SMALL_CAPS, HB_AAT_LAYOUT_FEATURE_SELECTOR_DEFAULT_UPPER_CASE),
hb_aat_feature_mapping_t::new(b"calt", HB_AAT_LAYOUT_FEATURE_TYPE_CONTEXTUAL_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_ALTERNATES_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_ALTERNATES_OFF),
hb_aat_feature_mapping_t::new(b"case", HB_AAT_LAYOUT_FEATURE_TYPE_CASE_SENSITIVE_LAYOUT, HB_AAT_LAYOUT_FEATURE_SELECTOR_CASE_SENSITIVE_LAYOUT_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_CASE_SENSITIVE_LAYOUT_OFF),
hb_aat_feature_mapping_t::new(b"clig", HB_AAT_LAYOUT_FEATURE_TYPE_LIGATURES, HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_LIGATURES_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_LIGATURES_OFF),
hb_aat_feature_mapping_t::new(b"cpsp", HB_AAT_LAYOUT_FEATURE_TYPE_CASE_SENSITIVE_LAYOUT, HB_AAT_LAYOUT_FEATURE_SELECTOR_CASE_SENSITIVE_SPACING_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_CASE_SENSITIVE_SPACING_OFF),
hb_aat_feature_mapping_t::new(b"cswh", HB_AAT_LAYOUT_FEATURE_TYPE_CONTEXTUAL_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_SWASH_ALTERNATES_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_CONTEXTUAL_SWASH_ALTERNATES_OFF),
hb_aat_feature_mapping_t::new(b"dlig", HB_AAT_LAYOUT_FEATURE_TYPE_LIGATURES, HB_AAT_LAYOUT_FEATURE_SELECTOR_RARE_LIGATURES_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_RARE_LIGATURES_OFF),
hb_aat_feature_mapping_t::new(b"expt", HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE, HB_AAT_LAYOUT_FEATURE_SELECTOR_EXPERT_CHARACTERS, 16),
hb_aat_feature_mapping_t::new(b"frac", HB_AAT_LAYOUT_FEATURE_TYPE_FRACTIONS, HB_AAT_LAYOUT_FEATURE_SELECTOR_DIAGONAL_FRACTIONS, HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_FRACTIONS),
hb_aat_feature_mapping_t::new(b"fwid", HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_MONOSPACED_TEXT, 7),
hb_aat_feature_mapping_t::new(b"halt", HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_ALT_HALF_WIDTH_TEXT, 7),
hb_aat_feature_mapping_t::new(b"hist", 40, 0, 1),
hb_aat_feature_mapping_t::new(b"hkna", HB_AAT_LAYOUT_FEATURE_TYPE_ALTERNATE_KANA, HB_AAT_LAYOUT_FEATURE_SELECTOR_ALTERNATE_HORIZ_KANA_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_ALTERNATE_HORIZ_KANA_OFF),
hb_aat_feature_mapping_t::new(b"hlig", HB_AAT_LAYOUT_FEATURE_TYPE_LIGATURES, HB_AAT_LAYOUT_FEATURE_SELECTOR_HISTORICAL_LIGATURES_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_HISTORICAL_LIGATURES_OFF),
hb_aat_feature_mapping_t::new(b"hngl", HB_AAT_LAYOUT_FEATURE_TYPE_TRANSLITERATION, HB_AAT_LAYOUT_FEATURE_SELECTOR_HANJA_TO_HANGUL, HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_TRANSLITERATION),
hb_aat_feature_mapping_t::new(b"hojo", HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE, HB_AAT_LAYOUT_FEATURE_SELECTOR_HOJO_CHARACTERS, 16),
hb_aat_feature_mapping_t::new(b"hwid", HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_HALF_WIDTH_TEXT, 7),
hb_aat_feature_mapping_t::new(b"ital", HB_AAT_LAYOUT_FEATURE_TYPE_ITALIC_CJK_ROMAN, HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_ITALIC_ROMAN_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_CJK_ITALIC_ROMAN_OFF),
hb_aat_feature_mapping_t::new(b"jp04", HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE, HB_AAT_LAYOUT_FEATURE_SELECTOR_JIS2004_CHARACTERS, 16),
hb_aat_feature_mapping_t::new(b"jp78", HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE, HB_AAT_LAYOUT_FEATURE_SELECTOR_JIS1978_CHARACTERS, 16),
hb_aat_feature_mapping_t::new(b"jp83", HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE, HB_AAT_LAYOUT_FEATURE_SELECTOR_JIS1983_CHARACTERS, 16),
hb_aat_feature_mapping_t::new(b"jp90", HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE, HB_AAT_LAYOUT_FEATURE_SELECTOR_JIS1990_CHARACTERS, 16),
hb_aat_feature_mapping_t::new(b"liga", HB_AAT_LAYOUT_FEATURE_TYPE_LIGATURES, HB_AAT_LAYOUT_FEATURE_SELECTOR_COMMON_LIGATURES_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_COMMON_LIGATURES_OFF),
hb_aat_feature_mapping_t::new(b"lnum", HB_AAT_LAYOUT_FEATURE_TYPE_NUMBER_CASE, HB_AAT_LAYOUT_FEATURE_SELECTOR_UPPER_CASE_NUMBERS, 2),
hb_aat_feature_mapping_t::new(b"mgrk", HB_AAT_LAYOUT_FEATURE_TYPE_MATHEMATICAL_EXTRAS, HB_AAT_LAYOUT_FEATURE_SELECTOR_MATHEMATICAL_GREEK_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_MATHEMATICAL_GREEK_OFF),
hb_aat_feature_mapping_t::new(b"nlck", HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE, HB_AAT_LAYOUT_FEATURE_SELECTOR_NLCCHARACTERS, 16),
hb_aat_feature_mapping_t::new(b"onum", HB_AAT_LAYOUT_FEATURE_TYPE_NUMBER_CASE, HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_NUMBERS, 2),
hb_aat_feature_mapping_t::new(b"ordn", HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_POSITION, HB_AAT_LAYOUT_FEATURE_SELECTOR_ORDINALS, HB_AAT_LAYOUT_FEATURE_SELECTOR_NORMAL_POSITION),
hb_aat_feature_mapping_t::new(b"palt", HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_ALT_PROPORTIONAL_TEXT, 7),
hb_aat_feature_mapping_t::new(b"pcap", HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE, HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_PETITE_CAPS, HB_AAT_LAYOUT_FEATURE_SELECTOR_DEFAULT_LOWER_CASE),
hb_aat_feature_mapping_t::new(b"pkna", HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_PROPORTIONAL_TEXT, 7),
hb_aat_feature_mapping_t::new(b"pnum", HB_AAT_LAYOUT_FEATURE_TYPE_NUMBER_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_PROPORTIONAL_NUMBERS, 4),
hb_aat_feature_mapping_t::new(b"pwid", HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_PROPORTIONAL_TEXT, 7),
hb_aat_feature_mapping_t::new(b"qwid", HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_QUARTER_WIDTH_TEXT, 7),
hb_aat_feature_mapping_t::new(b"ruby", HB_AAT_LAYOUT_FEATURE_TYPE_RUBY_KANA, HB_AAT_LAYOUT_FEATURE_SELECTOR_RUBY_KANA_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_RUBY_KANA_OFF),
hb_aat_feature_mapping_t::new(b"sinf", HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_POSITION, HB_AAT_LAYOUT_FEATURE_SELECTOR_SCIENTIFIC_INFERIORS, HB_AAT_LAYOUT_FEATURE_SELECTOR_NORMAL_POSITION),
hb_aat_feature_mapping_t::new(b"smcp", HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE, HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS, HB_AAT_LAYOUT_FEATURE_SELECTOR_DEFAULT_LOWER_CASE),
hb_aat_feature_mapping_t::new(b"smpl", HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE, HB_AAT_LAYOUT_FEATURE_SELECTOR_SIMPLIFIED_CHARACTERS, 16),
hb_aat_feature_mapping_t::new(b"ss01", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_ONE_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_ONE_OFF),
hb_aat_feature_mapping_t::new(b"ss02", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWO_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWO_OFF),
hb_aat_feature_mapping_t::new(b"ss03", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_THREE_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_THREE_OFF),
hb_aat_feature_mapping_t::new(b"ss04", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FOUR_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FOUR_OFF),
hb_aat_feature_mapping_t::new(b"ss05", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FIVE_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FIVE_OFF),
hb_aat_feature_mapping_t::new(b"ss06", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SIX_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SIX_OFF),
hb_aat_feature_mapping_t::new(b"ss07", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SEVEN_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SEVEN_OFF),
hb_aat_feature_mapping_t::new(b"ss08", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_EIGHT_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_EIGHT_OFF),
hb_aat_feature_mapping_t::new(b"ss09", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_NINE_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_NINE_OFF),
hb_aat_feature_mapping_t::new(b"ss10", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TEN_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TEN_OFF),
hb_aat_feature_mapping_t::new(b"ss11", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_ELEVEN_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_ELEVEN_OFF),
hb_aat_feature_mapping_t::new(b"ss12", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWELVE_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWELVE_OFF),
hb_aat_feature_mapping_t::new(b"ss13", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_THIRTEEN_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_THIRTEEN_OFF),
hb_aat_feature_mapping_t::new(b"ss14", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FOURTEEN_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FOURTEEN_OFF),
hb_aat_feature_mapping_t::new(b"ss15", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FIFTEEN_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_FIFTEEN_OFF),
hb_aat_feature_mapping_t::new(b"ss16", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SIXTEEN_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SIXTEEN_OFF),
hb_aat_feature_mapping_t::new(b"ss17", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SEVENTEEN_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_SEVENTEEN_OFF),
hb_aat_feature_mapping_t::new(b"ss18", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_EIGHTEEN_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_EIGHTEEN_OFF),
hb_aat_feature_mapping_t::new(b"ss19", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_NINETEEN_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_NINETEEN_OFF),
hb_aat_feature_mapping_t::new(b"ss20", HB_AAT_LAYOUT_FEATURE_TYPE_STYLISTIC_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWENTY_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_STYLISTIC_ALT_TWENTY_OFF),
hb_aat_feature_mapping_t::new(b"subs", HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_POSITION, HB_AAT_LAYOUT_FEATURE_SELECTOR_INFERIORS, HB_AAT_LAYOUT_FEATURE_SELECTOR_NORMAL_POSITION),
hb_aat_feature_mapping_t::new(b"sups", HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_POSITION, HB_AAT_LAYOUT_FEATURE_SELECTOR_SUPERIORS, HB_AAT_LAYOUT_FEATURE_SELECTOR_NORMAL_POSITION),
hb_aat_feature_mapping_t::new(b"swsh", HB_AAT_LAYOUT_FEATURE_TYPE_CONTEXTUAL_ALTERNATIVES, HB_AAT_LAYOUT_FEATURE_SELECTOR_SWASH_ALTERNATES_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_SWASH_ALTERNATES_OFF),
hb_aat_feature_mapping_t::new(b"titl", HB_AAT_LAYOUT_FEATURE_TYPE_STYLE_OPTIONS, HB_AAT_LAYOUT_FEATURE_SELECTOR_TITLING_CAPS, HB_AAT_LAYOUT_FEATURE_SELECTOR_NO_STYLE_OPTIONS),
hb_aat_feature_mapping_t::new(b"tnam", HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE, HB_AAT_LAYOUT_FEATURE_SELECTOR_TRADITIONAL_NAMES_CHARACTERS, 16),
hb_aat_feature_mapping_t::new(b"tnum", HB_AAT_LAYOUT_FEATURE_TYPE_NUMBER_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_MONOSPACED_NUMBERS, 4),
hb_aat_feature_mapping_t::new(b"trad", HB_AAT_LAYOUT_FEATURE_TYPE_CHARACTER_SHAPE, HB_AAT_LAYOUT_FEATURE_SELECTOR_TRADITIONAL_CHARACTERS, 16),
hb_aat_feature_mapping_t::new(b"twid", HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_THIRD_WIDTH_TEXT, 7),
hb_aat_feature_mapping_t::new(b"unic", HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE, 14, 15),
hb_aat_feature_mapping_t::new(b"valt", HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_ALT_PROPORTIONAL_TEXT, 7),
hb_aat_feature_mapping_t::new(b"vert", HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_SUBSTITUTION, HB_AAT_LAYOUT_FEATURE_SELECTOR_SUBSTITUTE_VERTICAL_FORMS_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_SUBSTITUTE_VERTICAL_FORMS_OFF),
hb_aat_feature_mapping_t::new(b"vhal", HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_ALT_HALF_WIDTH_TEXT, 7),
hb_aat_feature_mapping_t::new(b"vkna", HB_AAT_LAYOUT_FEATURE_TYPE_ALTERNATE_KANA, HB_AAT_LAYOUT_FEATURE_SELECTOR_ALTERNATE_VERT_KANA_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_ALTERNATE_VERT_KANA_OFF),
hb_aat_feature_mapping_t::new(b"vpal", HB_AAT_LAYOUT_FEATURE_TYPE_TEXT_SPACING, HB_AAT_LAYOUT_FEATURE_SELECTOR_ALT_PROPORTIONAL_TEXT, 7),
hb_aat_feature_mapping_t::new(b"vrt2", HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_SUBSTITUTION, HB_AAT_LAYOUT_FEATURE_SELECTOR_SUBSTITUTE_VERTICAL_FORMS_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_SUBSTITUTE_VERTICAL_FORMS_OFF),
hb_aat_feature_mapping_t::new(b"vrtr", HB_AAT_LAYOUT_FEATURE_TYPE_VERTICAL_SUBSTITUTION, 2, 3),
hb_aat_feature_mapping_t::new(b"zero", HB_AAT_LAYOUT_FEATURE_TYPE_TYPOGRAPHIC_EXTRAS, HB_AAT_LAYOUT_FEATURE_SELECTOR_SLASHED_ZERO_ON, HB_AAT_LAYOUT_FEATURE_SELECTOR_SLASHED_ZERO_OFF),
];
mod AAT {
pub const DELETED_GLYPH: u32 = 0xFFFF;
}
pub fn hb_aat_layout_substitute(
plan: &hb_ot_shape_plan_t,
face: &hb_font_t,
buffer: &mut hb_buffer_t,
) {
aat_layout_morx_table::apply(plan, face, buffer);
}
pub fn hb_aat_layout_zero_width_deleted_glyphs(buffer: &mut hb_buffer_t) {
for i in 0..buffer.len {
if buffer.info[i].glyph_id == AAT::DELETED_GLYPH {
buffer.pos[i].x_advance = 0;
buffer.pos[i].y_advance = 0;
buffer.pos[i].x_offset = 0;
buffer.pos[i].y_offset = 0;
}
}
}
fn is_deleted_glyph(info: &hb_glyph_info_t) -> bool {
info.glyph_id == AAT::DELETED_GLYPH
}
pub fn hb_aat_layout_remove_deleted_glyphs(buffer: &mut hb_buffer_t) {
buffer.delete_glyphs_inplace(is_deleted_glyph)
}
pub fn hb_aat_layout_position(
plan: &hb_ot_shape_plan_t,
face: &hb_font_t,
buffer: &mut hb_buffer_t,
) {
aat_layout_kerx_table::apply(plan, face, buffer);
}
pub fn hb_aat_layout_track(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
aat_layout_trak_table::apply(plan, face, buffer);
}

View File

@@ -0,0 +1,469 @@
use core::convert::TryFrom;
use ttf_parser::{ankr, apple_layout, kerx, FromData, GlyphId};
use super::buffer::*;
use super::hb_font_t;
use super::ot_layout::TableIndex;
use super::ot_layout_common::lookup_flags;
use super::ot_layout_gpos_table::attach_type;
use super::ot_layout_gsubgpos::{skipping_iterator_t, OT::hb_ot_apply_context_t};
use super::ot_shape_plan::hb_ot_shape_plan_t;
trait ExtendedStateTableExt<T: FromData + Copy> {
fn class(&self, glyph_id: GlyphId) -> Option<u16>;
fn entry(&self, state: u16, class: u16) -> Option<apple_layout::GenericStateEntry<T>>;
}
impl ExtendedStateTableExt<kerx::EntryData> for kerx::Subtable1<'_> {
fn class(&self, glyph_id: GlyphId) -> Option<u16> {
self.state_table.class(glyph_id)
}
fn entry(
&self,
state: u16,
class: u16,
) -> Option<apple_layout::GenericStateEntry<kerx::EntryData>> {
self.state_table.entry(state, class)
}
}
impl ExtendedStateTableExt<kerx::EntryData> for kerx::Subtable4<'_> {
fn class(&self, glyph_id: GlyphId) -> Option<u16> {
self.state_table.class(glyph_id)
}
fn entry(
&self,
state: u16,
class: u16,
) -> Option<apple_layout::GenericStateEntry<kerx::EntryData>> {
self.state_table.entry(state, class)
}
}
pub(crate) fn apply(
plan: &hb_ot_shape_plan_t,
face: &hb_font_t,
buffer: &mut hb_buffer_t,
) -> Option<()> {
let mut seen_cross_stream = false;
for subtable in face.tables().kerx?.subtables {
if subtable.variable {
continue;
}
if buffer.direction.is_horizontal() != subtable.horizontal {
continue;
}
let reverse = buffer.direction.is_backward();
if !seen_cross_stream && subtable.has_cross_stream {
seen_cross_stream = true;
// Attach all glyphs into a chain.
for pos in &mut buffer.pos {
pos.set_attach_type(attach_type::CURSIVE);
pos.set_attach_chain(if buffer.direction.is_forward() { -1 } else { 1 });
// We intentionally don't set BufferScratchFlags::HAS_GPOS_ATTACHMENT,
// since there needs to be a non-zero attachment for post-positioning to
// be needed.
}
}
if reverse {
buffer.reverse();
}
match subtable.format {
kerx::Format::Format0(_) => {
if !plan.requested_kerning {
continue;
}
apply_simple_kerning(&subtable, plan, face, buffer);
}
kerx::Format::Format1(ref sub) => {
let mut driver = Driver1 {
stack: [0; 8],
depth: 0,
};
apply_state_machine_kerning(&subtable, sub, &mut driver, plan, buffer);
}
kerx::Format::Format2(_) => {
if !plan.requested_kerning {
continue;
}
buffer.unsafe_to_concat(None, None);
apply_simple_kerning(&subtable, plan, face, buffer);
}
kerx::Format::Format4(ref sub) => {
let mut driver = Driver4 {
mark_set: false,
mark: 0,
ankr_table: face.tables().ankr.clone(),
};
apply_state_machine_kerning(&subtable, sub, &mut driver, plan, buffer);
}
kerx::Format::Format6(_) => {
if !plan.requested_kerning {
continue;
}
apply_simple_kerning(&subtable, plan, face, buffer);
}
}
if reverse {
buffer.reverse();
}
}
Some(())
}
fn apply_simple_kerning(
subtable: &kerx::Subtable,
plan: &hb_ot_shape_plan_t,
face: &hb_font_t,
buffer: &mut hb_buffer_t,
) {
let mut ctx = hb_ot_apply_context_t::new(TableIndex::GPOS, face, buffer);
ctx.lookup_mask = plan.kern_mask;
ctx.lookup_props = u32::from(lookup_flags::IGNORE_FLAGS);
let horizontal = ctx.buffer.direction.is_horizontal();
let mut i = 0;
while i < ctx.buffer.len {
if (ctx.buffer.info[i].mask & plan.kern_mask) == 0 {
i += 1;
continue;
}
let mut iter = skipping_iterator_t::new(&ctx, i, 1, false);
let mut unsafe_to = 0;
if !iter.next(Some(&mut unsafe_to)) {
ctx.buffer.unsafe_to_concat(Some(i), Some(unsafe_to));
i += 1;
continue;
}
let j = iter.index();
let info = &ctx.buffer.info;
let kern = subtable
.glyphs_kerning(info[i].as_glyph(), info[j].as_glyph())
.unwrap_or(0);
let kern = i32::from(kern);
let pos = &mut ctx.buffer.pos;
if kern != 0 {
if horizontal {
if subtable.has_cross_stream {
pos[j].y_offset = kern;
ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
} else {
let kern1 = kern >> 1;
let kern2 = kern - kern1;
pos[i].x_advance += kern1;
pos[j].x_advance += kern2;
pos[j].x_offset += kern2;
}
} else {
if subtable.has_cross_stream {
pos[j].x_offset = kern;
ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
} else {
let kern1 = kern >> 1;
let kern2 = kern - kern1;
pos[i].y_advance += kern1;
pos[j].y_advance += kern2;
pos[j].y_offset += kern2;
}
}
ctx.buffer.unsafe_to_break(Some(i), Some(j + 1))
}
i = j;
}
}
const START_OF_TEXT: u16 = 0;
trait KerxEntryDataExt {
fn action_index(self) -> u16;
fn is_actionable(&self) -> bool;
}
impl KerxEntryDataExt for apple_layout::GenericStateEntry<kerx::EntryData> {
fn action_index(self) -> u16 {
self.extra.action_index
}
fn is_actionable(&self) -> bool {
self.extra.action_index != 0xFFFF
}
}
fn apply_state_machine_kerning<T, E>(
subtable: &kerx::Subtable,
state_table: &T,
driver: &mut dyn StateTableDriver<T, E>,
plan: &hb_ot_shape_plan_t,
buffer: &mut hb_buffer_t,
) where
T: ExtendedStateTableExt<E>,
E: FromData + Copy,
apple_layout::GenericStateEntry<E>: KerxEntryDataExt,
{
let mut state = START_OF_TEXT;
buffer.idx = 0;
loop {
let class = if buffer.idx < buffer.len {
state_table
.class(buffer.info[buffer.idx].as_glyph())
.unwrap_or(1)
} else {
u16::from(apple_layout::class::END_OF_TEXT)
};
let entry: apple_layout::GenericStateEntry<E> = match state_table.entry(state, class) {
Some(v) => v,
None => break,
};
// Unsafe-to-break before this if not in state 0, as things might
// go differently if we start from state 0 here.
if state != START_OF_TEXT && buffer.backtrack_len() != 0 && buffer.idx < buffer.len {
// If there's no value and we're just epsilon-transitioning to state 0, safe to break.
if entry.is_actionable() || !(entry.new_state == START_OF_TEXT && !entry.has_advance())
{
buffer.unsafe_to_break_from_outbuffer(
Some(buffer.backtrack_len() - 1),
Some(buffer.idx + 1),
);
}
}
// Unsafe-to-break if end-of-text would kick in here.
if buffer.idx + 2 <= buffer.len {
let end_entry: Option<apple_layout::GenericStateEntry<E>> =
state_table.entry(state, u16::from(apple_layout::class::END_OF_TEXT));
let end_entry = match end_entry {
Some(v) => v,
None => break,
};
if end_entry.is_actionable() {
buffer.unsafe_to_break(Some(buffer.idx), Some(buffer.idx + 2));
}
}
let _ = driver.transition(
state_table,
entry,
subtable.has_cross_stream,
subtable.tuple_count,
plan,
buffer,
);
state = entry.new_state;
if buffer.idx >= buffer.len {
break;
}
if entry.has_advance() || buffer.max_ops <= 0 {
buffer.next_glyph();
}
buffer.max_ops -= 1;
}
}
trait StateTableDriver<Table, E: FromData> {
fn is_actionable(&self, entry: apple_layout::GenericStateEntry<E>) -> bool;
fn transition(
&mut self,
aat: &Table,
entry: apple_layout::GenericStateEntry<E>,
has_cross_stream: bool,
tuple_count: u32,
plan: &hb_ot_shape_plan_t,
buffer: &mut hb_buffer_t,
) -> Option<()>;
}
struct Driver1 {
stack: [usize; 8],
depth: usize,
}
impl StateTableDriver<kerx::Subtable1<'_>, kerx::EntryData> for Driver1 {
fn is_actionable(&self, entry: apple_layout::GenericStateEntry<kerx::EntryData>) -> bool {
entry.is_actionable()
}
fn transition(
&mut self,
aat: &kerx::Subtable1,
entry: apple_layout::GenericStateEntry<kerx::EntryData>,
has_cross_stream: bool,
tuple_count: u32,
plan: &hb_ot_shape_plan_t,
buffer: &mut hb_buffer_t,
) -> Option<()> {
if entry.has_reset() {
self.depth = 0;
}
if entry.has_push() {
if self.depth < self.stack.len() {
self.stack[self.depth] = buffer.idx;
self.depth += 1;
} else {
self.depth = 0; // Probably not what CoreText does, but better?
}
}
if entry.is_actionable() && self.depth != 0 {
let tuple_count = u16::try_from(tuple_count.max(1)).ok()?;
let mut action_index = entry.action_index();
// From Apple 'kern' spec:
// "Each pops one glyph from the kerning stack and applies the kerning value to it.
// The end of the list is marked by an odd value...
let mut last = false;
while !last && self.depth != 0 {
self.depth -= 1;
let idx = self.stack[self.depth];
let mut v = aat.glyphs_kerning(action_index)? as i32;
action_index = action_index.checked_add(tuple_count)?;
if idx >= buffer.len {
continue;
}
// "The end of the list is marked by an odd value..."
last = v & 1 != 0;
v &= !1;
// Testing shows that CoreText only applies kern (cross-stream or not)
// if none has been applied by previous subtables. That is, it does
// NOT seem to accumulate as otherwise implied by specs.
let mut has_gpos_attachment = false;
let glyph_mask = buffer.info[idx].mask;
let pos = &mut buffer.pos[idx];
if buffer.direction.is_horizontal() {
if has_cross_stream {
// The following flag is undocumented in the spec, but described
// in the 'kern' table example.
if v == -0x8000 {
pos.set_attach_type(0);
pos.set_attach_chain(0);
pos.y_offset = 0;
} else if pos.attach_type() != 0 {
pos.y_offset += v;
has_gpos_attachment = true;
}
} else if glyph_mask & plan.kern_mask != 0 {
pos.x_advance += v;
pos.x_offset += v;
}
} else {
if has_cross_stream {
// CoreText doesn't do crossStream kerning in vertical. We do.
if v == -0x8000 {
pos.set_attach_type(0);
pos.set_attach_chain(0);
pos.x_offset = 0;
} else if pos.attach_type() != 0 {
pos.x_offset += v;
has_gpos_attachment = true;
}
} else if glyph_mask & plan.kern_mask != 0 {
if pos.y_offset == 0 {
pos.y_advance += v;
pos.y_offset += v;
}
}
}
if has_gpos_attachment {
buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
}
}
}
Some(())
}
}
struct Driver4<'a> {
mark_set: bool,
mark: usize,
ankr_table: Option<ankr::Table<'a>>,
}
impl StateTableDriver<kerx::Subtable4<'_>, kerx::EntryData> for Driver4<'_> {
// TODO: remove
fn is_actionable(&self, entry: apple_layout::GenericStateEntry<kerx::EntryData>) -> bool {
entry.is_actionable()
}
fn transition(
&mut self,
aat: &kerx::Subtable4,
entry: apple_layout::GenericStateEntry<kerx::EntryData>,
_has_cross_stream: bool,
_tuple_count: u32,
_opt: &hb_ot_shape_plan_t,
buffer: &mut hb_buffer_t,
) -> Option<()> {
if self.mark_set && entry.is_actionable() && buffer.idx < buffer.len {
if let Some(ref ankr_table) = self.ankr_table {
let point = aat.anchor_points.get(entry.action_index())?;
let mark_idx = buffer.info[self.mark].as_glyph();
let mark_anchor = ankr_table
.points(mark_idx)
.and_then(|list| list.get(u32::from(point.0)))
.unwrap_or_default();
let curr_idx = buffer.cur(0).as_glyph();
let curr_anchor = ankr_table
.points(curr_idx)
.and_then(|list| list.get(u32::from(point.1)))
.unwrap_or_default();
let pos = buffer.cur_pos_mut();
pos.x_offset = i32::from(mark_anchor.x - curr_anchor.x);
pos.y_offset = i32::from(mark_anchor.y - curr_anchor.y);
}
buffer.cur_pos_mut().set_attach_type(attach_type::MARK);
let idx = buffer.idx;
buffer
.cur_pos_mut()
.set_attach_chain(self.mark as i16 - idx as i16);
buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
}
if entry.has_mark() {
self.mark_set = true;
self.mark = buffer.idx;
}
Some(())
}
}

View File

@@ -0,0 +1,771 @@
use ttf_parser::{apple_layout, morx, FromData, GlyphId, LazyArray32};
use super::aat_layout::*;
use super::aat_map::{hb_aat_map_builder_t, hb_aat_map_t};
use super::buffer::hb_buffer_t;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::{hb_font_t, hb_glyph_info_t};
// Chain::compile_flags in harfbuzz
pub fn compile_flags(face: &hb_font_t, builder: &hb_aat_map_builder_t) -> Option<hb_aat_map_t> {
let mut map = hb_aat_map_t::default();
for chain in face.tables().morx.as_ref()?.chains {
let mut flags = chain.default_flags;
for feature in chain.features {
// Check whether this type/setting pair was requested in the map,
// and if so, apply its flags.
if builder.has_feature(feature.kind, feature.setting) {
flags &= feature.disable_flags;
flags |= feature.enable_flags;
} else if feature.kind == HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE as u16
&& feature.setting == u16::from(HB_AAT_LAYOUT_FEATURE_SELECTOR_SMALL_CAPS)
{
// Deprecated. https://github.com/harfbuzz/harfbuzz/issues/1342
let ok = builder.has_feature(
HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE as u16,
u16::from(HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS),
);
if ok {
flags &= feature.disable_flags;
flags |= feature.enable_flags;
}
}
}
map.chain_flags.push(flags);
}
Some(map)
}
// Chain::apply in harfbuzz
pub fn apply(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) -> Option<()> {
for (chain_idx, chain) in face.tables().morx.as_ref()?.chains.into_iter().enumerate() {
let flags = plan.aat_map.chain_flags[chain_idx];
for subtable in chain.subtables {
if subtable.feature_flags & flags == 0 {
continue;
}
if !subtable.coverage.is_all_directions()
&& buffer.direction.is_vertical() != subtable.coverage.is_vertical()
{
continue;
}
// Buffer contents is always in logical direction. Determine if
// we need to reverse before applying this subtable. We reverse
// back after if we did reverse indeed.
//
// Quoting the spec:
// """
// Bits 28 and 30 of the coverage field control the order in which
// glyphs are processed when the subtable is run by the layout engine.
// Bit 28 is used to indicate if the glyph processing direction is
// the same as logical order or layout order. Bit 30 is used to
// indicate whether glyphs are processed forwards or backwards within
// that order.
//
// Bit 30 Bit 28 Interpretation for Horizontal Text
// 0 0 The subtable is processed in layout order
// (the same order as the glyphs, which is
// always left-to-right).
// 1 0 The subtable is processed in reverse layout order
// (the order opposite that of the glyphs, which is
// always right-to-left).
// 0 1 The subtable is processed in logical order
// (the same order as the characters, which may be
// left-to-right or right-to-left).
// 1 1 The subtable is processed in reverse logical order
// (the order opposite that of the characters, which
// may be right-to-left or left-to-right).
let reverse = if subtable.coverage.is_logical() {
subtable.coverage.is_backwards()
} else {
subtable.coverage.is_backwards() != buffer.direction.is_backward()
};
if reverse {
buffer.reverse();
}
apply_subtable(&subtable.kind, buffer, face);
if reverse {
buffer.reverse();
}
}
}
Some(())
}
trait driver_context_t<T: FromData> {
fn in_place(&self) -> bool;
fn can_advance(&self, entry: &apple_layout::GenericStateEntry<T>) -> bool;
fn is_actionable(
&self,
entry: &apple_layout::GenericStateEntry<T>,
buffer: &hb_buffer_t,
) -> bool;
fn transition(
&mut self,
entry: &apple_layout::GenericStateEntry<T>,
buffer: &mut hb_buffer_t,
) -> Option<()>;
}
const START_OF_TEXT: u16 = 0;
fn drive<T: FromData>(
machine: &apple_layout::ExtendedStateTable<T>,
c: &mut dyn driver_context_t<T>,
buffer: &mut hb_buffer_t,
) {
if !c.in_place() {
buffer.clear_output();
}
let mut state = START_OF_TEXT;
buffer.idx = 0;
loop {
let class = if buffer.idx < buffer.len {
machine
.class(buffer.info[buffer.idx].as_glyph())
.unwrap_or(1)
} else {
u16::from(apple_layout::class::END_OF_TEXT)
};
let entry: apple_layout::GenericStateEntry<T> = match machine.entry(state, class) {
Some(v) => v,
None => break,
};
let next_state = entry.new_state;
// Conditions under which it's guaranteed safe-to-break before current glyph:
//
// 1. There was no action in this transition; and
//
// 2. If we break before current glyph, the results will be the same. That
// is guaranteed if:
//
// 2a. We were already in start-of-text state; or
//
// 2b. We are epsilon-transitioning to start-of-text state; or
//
// 2c. Starting from start-of-text state seeing current glyph:
//
// 2c'. There won't be any actions; and
//
// 2c". We would end up in the same state that we were going to end up
// in now, including whether epsilon-transitioning.
//
// and
//
// 3. If we break before current glyph, there won't be any end-of-text action
// after previous glyph.
//
// This triples the transitions we need to look up, but is worth returning
// granular unsafe-to-break results. See eg.:
//
// https://github.com/harfbuzz/harfbuzz/issues/2860
let is_safe_to_break_extra = || {
// 2c
let wouldbe_entry = match machine.entry(START_OF_TEXT, class) {
Some(v) => v,
None => return false,
};
// 2c'
if c.is_actionable(&wouldbe_entry, &buffer) {
return false;
}
// 2c"
return next_state == wouldbe_entry.new_state
&& c.can_advance(&entry) == c.can_advance(&wouldbe_entry);
};
let is_safe_to_break = || {
// 1
if c.is_actionable(&entry, &buffer) {
return false;
}
// 2
let ok = state == START_OF_TEXT
|| (!c.can_advance(&entry) && next_state == START_OF_TEXT)
|| is_safe_to_break_extra();
if !ok {
return false;
}
// 3
let end_entry = match machine.entry(state, u16::from(apple_layout::class::END_OF_TEXT))
{
Some(v) => v,
None => return false,
};
return !c.is_actionable(&end_entry, &buffer);
};
if !is_safe_to_break() && buffer.backtrack_len() > 0 && buffer.idx < buffer.len {
buffer.unsafe_to_break_from_outbuffer(
Some(buffer.backtrack_len() - 1),
Some(buffer.idx + 1),
);
}
c.transition(&entry, buffer);
state = next_state;
if buffer.idx >= buffer.len || !buffer.successful {
break;
}
if c.can_advance(&entry) {
buffer.next_glyph();
} else {
if buffer.max_ops <= 0 {
buffer.next_glyph();
}
buffer.max_ops -= 1;
}
}
if !c.in_place() {
buffer.sync();
}
}
fn apply_subtable(kind: &morx::SubtableKind, buffer: &mut hb_buffer_t, face: &hb_font_t) {
match kind {
morx::SubtableKind::Rearrangement(ref table) => {
let mut c = RearrangementCtx { start: 0, end: 0 };
drive::<()>(table, &mut c, buffer);
}
morx::SubtableKind::Contextual(ref table) => {
let mut c = ContextualCtx {
mark_set: false,
face_if_has_glyph_classes:
matches!(face.tables().gdef, Some(gdef) if gdef.has_glyph_classes())
.then_some(face),
mark: 0,
table,
};
drive::<morx::ContextualEntryData>(&table.state, &mut c, buffer);
}
morx::SubtableKind::Ligature(ref table) => {
let mut c = LigatureCtx {
table,
match_length: 0,
match_positions: [0; LIGATURE_MAX_MATCHES],
};
drive::<u16>(&table.state, &mut c, buffer);
}
morx::SubtableKind::NonContextual(ref lookup) => {
let face_if_has_glyph_classes =
matches!(face.tables().gdef, Some(gdef) if gdef.has_glyph_classes())
.then_some(face);
for info in &mut buffer.info {
if let Some(replacement) = lookup.value(info.as_glyph()) {
info.glyph_id = u32::from(replacement);
if let Some(face) = face_if_has_glyph_classes {
info.set_glyph_props(face.glyph_props(GlyphId(replacement)));
}
}
}
}
morx::SubtableKind::Insertion(ref table) => {
let mut c = InsertionCtx {
mark: 0,
glyphs: table.glyphs,
};
drive::<morx::InsertionEntryData>(&table.state, &mut c, buffer);
}
}
}
struct RearrangementCtx {
start: usize,
end: usize,
}
impl RearrangementCtx {
const MARK_FIRST: u16 = 0x8000;
const DONT_ADVANCE: u16 = 0x4000;
const MARK_LAST: u16 = 0x2000;
const VERB: u16 = 0x000F;
}
impl driver_context_t<()> for RearrangementCtx {
fn in_place(&self) -> bool {
true
}
fn can_advance(&self, entry: &apple_layout::GenericStateEntry<()>) -> bool {
entry.flags & Self::DONT_ADVANCE == 0
}
fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<()>, _: &hb_buffer_t) -> bool {
entry.flags & Self::VERB != 0 && self.start < self.end
}
fn transition(
&mut self,
entry: &apple_layout::GenericStateEntry<()>,
buffer: &mut hb_buffer_t,
) -> Option<()> {
let flags = entry.flags;
if flags & Self::MARK_FIRST != 0 {
self.start = buffer.idx;
}
if flags & Self::MARK_LAST != 0 {
self.end = (buffer.idx + 1).min(buffer.len);
}
if flags & Self::VERB != 0 && self.start < self.end {
// The following map has two nibbles, for start-side
// and end-side. Values of 0,1,2 mean move that many
// to the other side. Value of 3 means move 2 and
// flip them.
const MAP: [u8; 16] = [
0x00, // 0 no change
0x10, // 1 Ax => xA
0x01, // 2 xD => Dx
0x11, // 3 AxD => DxA
0x20, // 4 ABx => xAB
0x30, // 5 ABx => xBA
0x02, // 6 xCD => CDx
0x03, // 7 xCD => DCx
0x12, // 8 AxCD => CDxA
0x13, // 9 AxCD => DCxA
0x21, // 10 ABxD => DxAB
0x31, // 11 ABxD => DxBA
0x22, // 12 ABxCD => CDxAB
0x32, // 13 ABxCD => CDxBA
0x23, // 14 ABxCD => DCxAB
0x33, // 15 ABxCD => DCxBA
];
let m = MAP[usize::from(flags & Self::VERB)];
let l = 2.min(m >> 4) as usize;
let r = 2.min(m & 0x0F) as usize;
let reverse_l = 3 == (m >> 4);
let reverse_r = 3 == (m & 0x0F);
if self.end - self.start >= l + r {
buffer.merge_clusters(self.start, (buffer.idx + 1).min(buffer.len));
buffer.merge_clusters(self.start, self.end);
let mut buf = [hb_glyph_info_t::default(); 4];
for (i, glyph_info) in buf[..l].iter_mut().enumerate() {
*glyph_info = buffer.info[self.start + i];
}
for i in 0..r {
buf[i + 2] = buffer.info[self.end - r + i];
}
if l > r {
for i in 0..(self.end - self.start - l - r) {
buffer.info[self.start + r + i] = buffer.info[self.start + l + i];
}
} else if l < r {
for i in (0..(self.end - self.start - l - r)).rev() {
buffer.info[self.start + r + i] = buffer.info[self.start + l + i];
}
}
for i in 0..r {
buffer.info[self.start + i] = buf[2 + i];
}
for i in 0..l {
buffer.info[self.end - l + i] = buf[i];
}
if reverse_l {
buffer.info.swap(self.end - 1, self.end - 2);
}
if reverse_r {
buffer.info.swap(self.start, self.start + 1);
}
}
}
Some(())
}
}
struct ContextualCtx<'a> {
mark_set: bool,
face_if_has_glyph_classes: Option<&'a hb_font_t<'a>>,
mark: usize,
table: &'a morx::ContextualSubtable<'a>,
}
impl ContextualCtx<'_> {
const SET_MARK: u16 = 0x8000;
const DONT_ADVANCE: u16 = 0x4000;
}
impl driver_context_t<morx::ContextualEntryData> for ContextualCtx<'_> {
fn in_place(&self) -> bool {
true
}
fn can_advance(
&self,
entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>,
) -> bool {
entry.flags & Self::DONT_ADVANCE == 0
}
fn is_actionable(
&self,
entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>,
buffer: &hb_buffer_t,
) -> bool {
if buffer.idx == buffer.len && !self.mark_set {
return false;
}
entry.extra.mark_index != 0xFFFF || entry.extra.current_index != 0xFFFF
}
fn transition(
&mut self,
entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>,
buffer: &mut hb_buffer_t,
) -> Option<()> {
// Looks like CoreText applies neither mark nor current substitution for
// end-of-text if mark was not explicitly set.
if buffer.idx == buffer.len && !self.mark_set {
return Some(());
}
let mut replacement = None;
if entry.extra.mark_index != 0xFFFF {
let lookup = self.table.lookup(u32::from(entry.extra.mark_index))?;
replacement = lookup.value(buffer.info[self.mark].as_glyph());
}
if let Some(replacement) = replacement {
buffer.unsafe_to_break(Some(self.mark), Some((buffer.idx + 1).min(buffer.len)));
buffer.info[self.mark].glyph_id = u32::from(replacement);
if let Some(face) = self.face_if_has_glyph_classes {
buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement)));
}
}
replacement = None;
let idx = buffer.idx.min(buffer.len - 1);
if entry.extra.current_index != 0xFFFF {
let lookup = self.table.lookup(u32::from(entry.extra.current_index))?;
replacement = lookup.value(buffer.info[idx].as_glyph());
}
if let Some(replacement) = replacement {
buffer.info[idx].glyph_id = u32::from(replacement);
if let Some(face) = self.face_if_has_glyph_classes {
buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement)));
}
}
if entry.flags & Self::SET_MARK != 0 {
self.mark_set = true;
self.mark = buffer.idx;
}
Some(())
}
}
struct InsertionCtx<'a> {
mark: u32,
glyphs: LazyArray32<'a, GlyphId>,
}
impl InsertionCtx<'_> {
const SET_MARK: u16 = 0x8000;
const DONT_ADVANCE: u16 = 0x4000;
const CURRENT_INSERT_BEFORE: u16 = 0x0800;
const MARKED_INSERT_BEFORE: u16 = 0x0400;
const CURRENT_INSERT_COUNT: u16 = 0x03E0;
const MARKED_INSERT_COUNT: u16 = 0x001F;
}
impl driver_context_t<morx::InsertionEntryData> for InsertionCtx<'_> {
fn in_place(&self) -> bool {
false
}
fn can_advance(
&self,
entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>,
) -> bool {
entry.flags & Self::DONT_ADVANCE == 0
}
fn is_actionable(
&self,
entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>,
_: &hb_buffer_t,
) -> bool {
(entry.flags & (Self::CURRENT_INSERT_COUNT | Self::MARKED_INSERT_COUNT) != 0)
&& (entry.extra.current_insert_index != 0xFFFF
|| entry.extra.marked_insert_index != 0xFFFF)
}
fn transition(
&mut self,
entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>,
buffer: &mut hb_buffer_t,
) -> Option<()> {
let flags = entry.flags;
let mark_loc = buffer.out_len;
if entry.extra.marked_insert_index != 0xFFFF {
let count = flags & Self::MARKED_INSERT_COUNT;
buffer.max_ops -= i32::from(count);
if buffer.max_ops <= 0 {
return Some(());
}
let start = entry.extra.marked_insert_index;
let before = flags & Self::MARKED_INSERT_BEFORE != 0;
let end = buffer.out_len;
buffer.move_to(self.mark as usize);
if buffer.idx < buffer.len && !before {
buffer.copy_glyph();
}
// TODO We ignore KashidaLike setting.
for i in 0..count {
let i = u32::from(start + i);
buffer.output_glyph(u32::from(self.glyphs.get(i)?.0));
}
if buffer.idx < buffer.len && !before {
buffer.skip_glyph();
}
buffer.move_to(end + usize::from(count));
buffer.unsafe_to_break_from_outbuffer(
Some(self.mark as usize),
Some((buffer.idx + 1).min(buffer.len)),
);
}
if flags & Self::SET_MARK != 0 {
self.mark = mark_loc as u32;
}
if entry.extra.current_insert_index != 0xFFFF {
let count = (flags & Self::CURRENT_INSERT_COUNT) >> 5;
buffer.max_ops -= i32::from(count);
if buffer.max_ops < 0 {
return Some(());
}
let start = entry.extra.current_insert_index;
let before = flags & Self::CURRENT_INSERT_BEFORE != 0;
let end = buffer.out_len;
if buffer.idx < buffer.len && !before {
buffer.copy_glyph();
}
// TODO We ignore KashidaLike setting.
for i in 0..count {
let i = u32::from(start + i);
buffer.output_glyph(u32::from(self.glyphs.get(i)?.0));
}
if buffer.idx < buffer.len && !before {
buffer.skip_glyph();
}
// Humm. Not sure where to move to. There's this wording under
// DontAdvance flag:
//
// "If set, don't update the glyph index before going to the new state.
// This does not mean that the glyph pointed to is the same one as
// before. If you've made insertions immediately downstream of the
// current glyph, the next glyph processed would in fact be the first
// one inserted."
//
// This suggests that if DontAdvance is NOT set, we should move to
// end+count. If it *was*, then move to end, such that newly inserted
// glyphs are now visible.
//
// https://github.com/harfbuzz/harfbuzz/issues/1224#issuecomment-427691417
buffer.move_to(if flags & Self::DONT_ADVANCE != 0 {
end
} else {
end + usize::from(count)
});
}
Some(())
}
}
const LIGATURE_MAX_MATCHES: usize = 64;
struct LigatureCtx<'a> {
table: &'a morx::LigatureSubtable<'a>,
match_length: usize,
match_positions: [usize; LIGATURE_MAX_MATCHES],
}
impl LigatureCtx<'_> {
const SET_COMPONENT: u16 = 0x8000;
const DONT_ADVANCE: u16 = 0x4000;
const PERFORM_ACTION: u16 = 0x2000;
const LIG_ACTION_LAST: u32 = 0x80000000;
const LIG_ACTION_STORE: u32 = 0x40000000;
const LIG_ACTION_OFFSET: u32 = 0x3FFFFFFF;
}
impl driver_context_t<u16> for LigatureCtx<'_> {
fn in_place(&self) -> bool {
false
}
fn can_advance(&self, entry: &apple_layout::GenericStateEntry<u16>) -> bool {
entry.flags & Self::DONT_ADVANCE == 0
}
fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<u16>, _: &hb_buffer_t) -> bool {
entry.flags & Self::PERFORM_ACTION != 0
}
fn transition(
&mut self,
entry: &apple_layout::GenericStateEntry<u16>,
buffer: &mut hb_buffer_t,
) -> Option<()> {
if entry.flags & Self::SET_COMPONENT != 0 {
// Never mark same index twice, in case DONT_ADVANCE was used...
if self.match_length != 0
&& self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES]
== buffer.out_len
{
self.match_length -= 1;
}
self.match_positions[self.match_length % LIGATURE_MAX_MATCHES] = buffer.out_len;
self.match_length += 1;
}
if entry.flags & Self::PERFORM_ACTION != 0 {
let end = buffer.out_len;
if self.match_length == 0 {
return Some(());
}
if buffer.idx >= buffer.len {
return Some(()); // TODO: Work on previous instead?
}
let mut cursor = self.match_length;
let mut ligature_actions_index = entry.extra;
let mut ligature_idx = 0;
loop {
if cursor == 0 {
// Stack underflow. Clear the stack.
self.match_length = 0;
break;
}
cursor -= 1;
buffer.move_to(self.match_positions[cursor % LIGATURE_MAX_MATCHES]);
// We cannot use ? in this loop, because we must call
// buffer.move_to(end) in the end.
let action = match self
.table
.ligature_actions
.get(u32::from(ligature_actions_index))
{
Some(v) => v,
None => break,
};
let mut uoffset = action & Self::LIG_ACTION_OFFSET;
if uoffset & 0x20000000 != 0 {
uoffset |= 0xC0000000; // Sign-extend.
}
let offset = uoffset as i32;
let component_idx = (buffer.cur(0).glyph_id as i32 + offset) as u32;
ligature_idx += match self.table.components.get(component_idx) {
Some(v) => v,
None => break,
};
if (action & (Self::LIG_ACTION_STORE | Self::LIG_ACTION_LAST)) != 0 {
let lig = match self.table.ligatures.get(u32::from(ligature_idx)) {
Some(v) => v,
None => break,
};
buffer.replace_glyph(u32::from(lig.0));
let lig_end =
self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES] + 1;
// Now go and delete all subsequent components.
while self.match_length - 1 > cursor {
self.match_length -= 1;
buffer.move_to(
self.match_positions[self.match_length % LIGATURE_MAX_MATCHES],
);
buffer.replace_glyph(0xFFFF);
}
buffer.move_to(lig_end);
buffer.merge_out_clusters(
self.match_positions[cursor % LIGATURE_MAX_MATCHES],
buffer.out_len,
);
}
ligature_actions_index += 1;
if action & Self::LIG_ACTION_LAST != 0 {
break;
}
}
buffer.move_to(end);
}
Some(())
}
}

View File

@@ -0,0 +1,115 @@
use super::buffer::hb_buffer_t;
use super::hb_font_t;
use super::ot_shape_plan::hb_ot_shape_plan_t;
pub fn apply(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) -> Option<()> {
let trak_mask = plan.trak_mask;
let ptem = face.points_per_em?;
if ptem <= 0.0 {
return None;
}
let trak = face.tables().trak?;
if !buffer.have_positions {
buffer.clear_positions();
}
if buffer.direction.is_horizontal() {
let tracking = trak.hor_tracking(ptem)?;
let advance_to_add = tracking;
let offset_to_add = tracking / 2;
foreach_grapheme!(buffer, start, end, {
if buffer.info[start].mask & trak_mask != 0 {
buffer.pos[start].x_advance += advance_to_add;
buffer.pos[start].x_offset += offset_to_add;
}
});
} else {
let tracking = trak.ver_tracking(ptem)?;
let advance_to_add = tracking;
let offset_to_add = tracking / 2;
foreach_grapheme!(buffer, start, end, {
if buffer.info[start].mask & trak_mask != 0 {
buffer.pos[start].y_advance += advance_to_add;
buffer.pos[start].y_offset += offset_to_add;
}
});
}
Some(())
}
trait TrackTableExt {
fn hor_tracking(&self, ptem: f32) -> Option<i32>;
fn ver_tracking(&self, ptem: f32) -> Option<i32>;
}
impl TrackTableExt for ttf_parser::trak::Table<'_> {
fn hor_tracking(&self, ptem: f32) -> Option<i32> {
self.horizontal.tracking(ptem)
}
fn ver_tracking(&self, ptem: f32) -> Option<i32> {
self.vertical.tracking(ptem)
}
}
trait TrackTableDataExt {
fn tracking(&self, ptem: f32) -> Option<i32>;
fn interpolate_at(
&self,
idx: u16,
target_size: f32,
track: &ttf_parser::trak::Track,
) -> Option<f32>;
}
impl TrackTableDataExt for ttf_parser::trak::TrackData<'_> {
fn tracking(&self, ptem: f32) -> Option<i32> {
// Choose track.
let track = self.tracks.into_iter().find(|t| t.value == 0.0)?;
// Choose size.
if self.sizes.is_empty() {
return None;
}
let mut idx = self
.sizes
.into_iter()
.position(|s| s.0 >= ptem)
.unwrap_or(self.sizes.len() as usize - 1);
if idx > 0 {
idx -= 1;
}
self.interpolate_at(idx as u16, ptem, &track)
.map(|n| crate::hb::round(n) as i32)
}
fn interpolate_at(
&self,
idx: u16,
target_size: f32,
track: &ttf_parser::trak::Track,
) -> Option<f32> {
debug_assert!(idx < self.sizes.len() - 1);
let s0 = self.sizes.get(idx)?.0;
let s1 = self.sizes.get(idx + 1)?.0;
let t = if s0 == s1 {
0.0
} else {
(target_size - s0) / (s1 - s0)
};
let n =
t * (track.values.get(idx + 1)? as f32) + (1.0 - t) * (track.values.get(idx)? as f32);
Some(n)
}
}

132
vendor/rustybuzz/src/hb/aat_map.rs vendored Normal file
View File

@@ -0,0 +1,132 @@
use alloc::vec::Vec;
use super::aat_layout::*;
use super::{hb_font_t, hb_mask_t, hb_tag_t};
#[derive(Default)]
pub struct hb_aat_map_t {
pub chain_flags: Vec<hb_mask_t>,
}
#[derive(Copy, Clone)]
pub struct feature_info_t {
pub kind: u16,
pub setting: u16,
pub is_exclusive: bool,
}
#[derive(Default)]
pub struct hb_aat_map_builder_t {
pub features: Vec<feature_info_t>,
}
impl hb_aat_map_builder_t {
pub fn add_feature(&mut self, face: &hb_font_t, tag: hb_tag_t, value: u32) -> Option<()> {
const FEATURE_TYPE_CHARACTER_ALTERNATIVES: u16 = 17;
let feat = face.tables().feat?;
if tag == hb_tag_t::from_bytes(b"aalt") {
let exposes_feature = feat
.names
.find(FEATURE_TYPE_CHARACTER_ALTERNATIVES)
.map(|f| f.setting_names.len() != 0)
.unwrap_or(false);
if !exposes_feature {
return Some(());
}
self.features.push(feature_info_t {
kind: FEATURE_TYPE_CHARACTER_ALTERNATIVES,
setting: value as u16,
is_exclusive: true,
});
}
let idx = feature_mappings
.binary_search_by(|map| map.ot_feature_tag.cmp(&tag))
.ok()?;
let mapping = &feature_mappings[idx];
let mut feature = feat.names.find(mapping.aat_feature_type as u16);
match feature {
Some(feature) if feature.setting_names.len() != 0 => {}
_ => {
// Special case: Chain::compile_flags will fall back to the deprecated version of
// small-caps if necessary, so we need to check for that possibility.
// https://github.com/harfbuzz/harfbuzz/issues/2307
if mapping.aat_feature_type == HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE
&& mapping.selector_to_enable
== HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS
{
feature = feat
.names
.find(HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE as u16);
}
}
}
match feature {
Some(feature) if feature.setting_names.len() != 0 => {
let setting = if value != 0 {
mapping.selector_to_enable
} else {
mapping.selector_to_disable
} as u16;
self.features.push(feature_info_t {
kind: mapping.aat_feature_type as u16,
setting,
is_exclusive: feature.exclusive,
});
}
_ => {}
}
Some(())
}
pub fn has_feature(&self, kind: u16, setting: u16) -> bool {
self.features
.binary_search_by(|probe| {
if probe.kind != kind {
probe.kind.cmp(&kind)
} else {
probe.setting.cmp(&setting)
}
})
.is_ok()
}
pub fn compile(&mut self, face: &hb_font_t) -> hb_aat_map_t {
// Sort features and merge duplicates.
self.features.sort_by(|a, b| {
if a.kind != b.kind {
a.kind.cmp(&b.kind)
} else if !a.is_exclusive && (a.setting & !1) != (b.setting & !1) {
a.setting.cmp(&b.setting)
} else {
core::cmp::Ordering::Equal
}
});
let mut j = 0;
for i in 0..self.features.len() {
// Nonexclusive feature selectors come in even/odd pairs to turn a setting on/off
// respectively, so we mask out the low-order bit when checking for "duplicates"
// (selectors referring to the same feature setting) here.
let non_exclusive = !self.features[i].is_exclusive
&& (self.features[i].setting & !1) != (self.features[j].setting & !1);
if self.features[i].kind != self.features[j].kind || non_exclusive {
j += 1;
self.features[j] = self.features[i];
}
}
self.features.truncate(j + 1);
super::aat_layout_morx_table::compile_flags(face, self).unwrap_or_default()
}
}

37
vendor/rustybuzz/src/hb/algs.rs vendored Normal file
View File

@@ -0,0 +1,37 @@
// FLAG macro in harfbuzz.
#[inline]
pub const fn rb_flag(x: u32) -> u32 {
1 << x
}
// FLAG_UNSAFE macro in harfbuzz.
#[inline]
pub fn rb_flag_unsafe(x: u32) -> u32 {
if x < 32 {
1 << x
} else {
0
}
}
// FLAG_RANGE macro in harfbuzz.
#[inline]
pub fn rb_flag_range(x: u32, y: u32) -> u32 {
(x < y) as u32 + rb_flag(y + 1) - rb_flag(x)
}
// FLAG64 macro in harfbuzz.
#[inline]
pub const fn rb_flag64(x: u32) -> u64 {
1 << x as u64
}
// FLAG64_UNSAFE macro in harfbuzz.
#[inline]
pub fn rb_flag64_unsafe(x: u32) -> u64 {
if x < 64 {
1 << (x as u64)
} else {
0
}
}

1763
vendor/rustybuzz/src/hb/buffer.rs vendored Normal file

File diff suppressed because it is too large Load Diff

739
vendor/rustybuzz/src/hb/common.rs vendored Normal file
View File

@@ -0,0 +1,739 @@
use alloc::string::String;
use core::ops::{Bound, RangeBounds};
use ttf_parser::Tag;
use super::text_parser::TextParser;
pub type hb_codepoint_t = char; // uint32_t in C++
/// Defines the direction in which text is to be read.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Direction {
/// Initial, unset direction.
Invalid,
/// Text is set horizontally from left to right.
LeftToRight,
/// Text is set horizontally from right to left.
RightToLeft,
/// Text is set vertically from top to bottom.
TopToBottom,
/// Text is set vertically from bottom to top.
BottomToTop,
}
impl Direction {
#[inline]
pub(crate) fn is_horizontal(self) -> bool {
match self {
Direction::Invalid => false,
Direction::LeftToRight => true,
Direction::RightToLeft => true,
Direction::TopToBottom => false,
Direction::BottomToTop => false,
}
}
#[inline]
pub(crate) fn is_vertical(self) -> bool {
!self.is_horizontal()
}
#[inline]
pub(crate) fn is_forward(self) -> bool {
match self {
Direction::Invalid => false,
Direction::LeftToRight => true,
Direction::RightToLeft => false,
Direction::TopToBottom => true,
Direction::BottomToTop => false,
}
}
#[inline]
pub(crate) fn is_backward(self) -> bool {
!self.is_forward()
}
#[inline]
pub(crate) fn reverse(self) -> Self {
match self {
Direction::Invalid => Direction::Invalid,
Direction::LeftToRight => Direction::RightToLeft,
Direction::RightToLeft => Direction::LeftToRight,
Direction::TopToBottom => Direction::BottomToTop,
Direction::BottomToTop => Direction::TopToBottom,
}
}
pub(crate) fn from_script(script: Script) -> Option<Self> {
// https://docs.google.com/spreadsheets/d/1Y90M0Ie3MUJ6UVCRDOypOtijlMDLNNyyLk36T6iMu0o
match script {
// Unicode-1.1 additions
script::ARABIC |
script::HEBREW |
// Unicode-3.0 additions
script::SYRIAC |
script::THAANA |
// Unicode-4.0 additions
script::CYPRIOT |
// Unicode-4.1 additions
script::KHAROSHTHI |
// Unicode-5.0 additions
script::PHOENICIAN |
script::NKO |
// Unicode-5.1 additions
script::LYDIAN |
// Unicode-5.2 additions
script::AVESTAN |
script::IMPERIAL_ARAMAIC |
script::INSCRIPTIONAL_PAHLAVI |
script::INSCRIPTIONAL_PARTHIAN |
script::OLD_SOUTH_ARABIAN |
script::OLD_TURKIC |
script::SAMARITAN |
// Unicode-6.0 additions
script::MANDAIC |
// Unicode-6.1 additions
script::MEROITIC_CURSIVE |
script::MEROITIC_HIEROGLYPHS |
// Unicode-7.0 additions
script::MANICHAEAN |
script::MENDE_KIKAKUI |
script::NABATAEAN |
script::OLD_NORTH_ARABIAN |
script::PALMYRENE |
script::PSALTER_PAHLAVI |
// Unicode-8.0 additions
script::HATRAN |
// Unicode-9.0 additions
script::ADLAM |
// Unicode-11.0 additions
script::HANIFI_ROHINGYA |
script::OLD_SOGDIAN |
script::SOGDIAN |
// Unicode-12.0 additions
script::ELYMAIC |
// Unicode-13.0 additions
script::CHORASMIAN |
script::YEZIDI |
// Unicode-14.0 additions
script::OLD_UYGHUR => {
Some(Direction::RightToLeft)
}
// https://github.com/harfbuzz/harfbuzz/issues/1000
script::OLD_HUNGARIAN |
script::OLD_ITALIC |
script::RUNIC => {
None
}
_ => Some(Direction::LeftToRight),
}
}
}
impl Default for Direction {
#[inline]
fn default() -> Self {
Direction::Invalid
}
}
impl core::str::FromStr for Direction {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err("invalid direction");
}
// harfbuzz also matches only the first letter.
match s.as_bytes()[0].to_ascii_lowercase() {
b'l' => Ok(Direction::LeftToRight),
b'r' => Ok(Direction::RightToLeft),
b't' => Ok(Direction::TopToBottom),
b'b' => Ok(Direction::BottomToTop),
_ => Err("invalid direction"),
}
}
}
/// A script language.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Language(String);
impl Language {
/// Returns the language as a string.
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl core::str::FromStr for Language {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.is_empty() {
Ok(Language(s.to_ascii_lowercase()))
} else {
Err("invalid language")
}
}
}
// In harfbuzz, despite having `hb_script_t`, script can actually have any tag.
// So we're doing the same.
// The only difference is that `Script` cannot be set to `HB_SCRIPT_INVALID`.
/// A text script.
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Script(pub(crate) Tag);
impl Script {
#[inline]
pub(crate) const fn from_bytes(bytes: &[u8; 4]) -> Self {
Script(Tag::from_bytes(bytes))
}
/// Converts an ISO 15924 script tag to a corresponding `Script`.
pub fn from_iso15924_tag(tag: Tag) -> Option<Script> {
if tag.is_null() {
return None;
}
// Be lenient, adjust case (one capital letter followed by three small letters).
let tag = Tag((tag.as_u32() & 0xDFDFDFDF) | 0x00202020);
match &tag.to_bytes() {
// These graduated from the 'Q' private-area codes, but
// the old code is still aliased by Unicode, and the Qaai
// one in use by ICU.
b"Qaai" => return Some(script::INHERITED),
b"Qaac" => return Some(script::COPTIC),
// Script variants from https://unicode.org/iso15924/
b"Aran" => return Some(script::ARABIC),
b"Cyrs" => return Some(script::CYRILLIC),
b"Geok" => return Some(script::GEORGIAN),
b"Hans" | b"Hant" => return Some(script::HAN),
b"Jamo" => return Some(script::HANGUL),
b"Latf" | b"Latg" => return Some(script::LATIN),
b"Syre" | b"Syrj" | b"Syrn" => return Some(script::SYRIAC),
_ => {}
}
if tag.as_u32() & 0xE0E0E0E0 == 0x40606060 {
Some(Script(tag))
} else {
Some(script::UNKNOWN)
}
}
/// Returns script's tag.
#[inline]
pub fn tag(&self) -> Tag {
self.0
}
}
impl core::str::FromStr for Script {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let tag = Tag::from_bytes_lossy(s.as_bytes());
Script::from_iso15924_tag(tag).ok_or("invalid script")
}
}
/// Predefined scripts.
pub mod script {
#![allow(missing_docs)]
use crate::Script;
// Since 1.1
pub const COMMON: Script = Script::from_bytes(b"Zyyy");
pub const INHERITED: Script = Script::from_bytes(b"Zinh");
pub const ARABIC: Script = Script::from_bytes(b"Arab");
pub const ARMENIAN: Script = Script::from_bytes(b"Armn");
pub const BENGALI: Script = Script::from_bytes(b"Beng");
pub const CYRILLIC: Script = Script::from_bytes(b"Cyrl");
pub const DEVANAGARI: Script = Script::from_bytes(b"Deva");
pub const GEORGIAN: Script = Script::from_bytes(b"Geor");
pub const GREEK: Script = Script::from_bytes(b"Grek");
pub const GUJARATI: Script = Script::from_bytes(b"Gujr");
pub const GURMUKHI: Script = Script::from_bytes(b"Guru");
pub const HANGUL: Script = Script::from_bytes(b"Hang");
pub const HAN: Script = Script::from_bytes(b"Hani");
pub const HEBREW: Script = Script::from_bytes(b"Hebr");
pub const HIRAGANA: Script = Script::from_bytes(b"Hira");
pub const KANNADA: Script = Script::from_bytes(b"Knda");
pub const KATAKANA: Script = Script::from_bytes(b"Kana");
pub const LAO: Script = Script::from_bytes(b"Laoo");
pub const LATIN: Script = Script::from_bytes(b"Latn");
pub const MALAYALAM: Script = Script::from_bytes(b"Mlym");
pub const ORIYA: Script = Script::from_bytes(b"Orya");
pub const TAMIL: Script = Script::from_bytes(b"Taml");
pub const TELUGU: Script = Script::from_bytes(b"Telu");
pub const THAI: Script = Script::from_bytes(b"Thai");
// Since 2.0
pub const TIBETAN: Script = Script::from_bytes(b"Tibt");
// Since 3.0
pub const BOPOMOFO: Script = Script::from_bytes(b"Bopo");
pub const BRAILLE: Script = Script::from_bytes(b"Brai");
pub const CANADIAN_SYLLABICS: Script = Script::from_bytes(b"Cans");
pub const CHEROKEE: Script = Script::from_bytes(b"Cher");
pub const ETHIOPIC: Script = Script::from_bytes(b"Ethi");
pub const KHMER: Script = Script::from_bytes(b"Khmr");
pub const MONGOLIAN: Script = Script::from_bytes(b"Mong");
pub const MYANMAR: Script = Script::from_bytes(b"Mymr");
pub const OGHAM: Script = Script::from_bytes(b"Ogam");
pub const RUNIC: Script = Script::from_bytes(b"Runr");
pub const SINHALA: Script = Script::from_bytes(b"Sinh");
pub const SYRIAC: Script = Script::from_bytes(b"Syrc");
pub const THAANA: Script = Script::from_bytes(b"Thaa");
pub const YI: Script = Script::from_bytes(b"Yiii");
// Since 3.1
pub const DESERET: Script = Script::from_bytes(b"Dsrt");
pub const GOTHIC: Script = Script::from_bytes(b"Goth");
pub const OLD_ITALIC: Script = Script::from_bytes(b"Ital");
// Since 3.2
pub const BUHID: Script = Script::from_bytes(b"Buhd");
pub const HANUNOO: Script = Script::from_bytes(b"Hano");
pub const TAGALOG: Script = Script::from_bytes(b"Tglg");
pub const TAGBANWA: Script = Script::from_bytes(b"Tagb");
// Since 4.0
pub const CYPRIOT: Script = Script::from_bytes(b"Cprt");
pub const LIMBU: Script = Script::from_bytes(b"Limb");
pub const LINEAR_B: Script = Script::from_bytes(b"Linb");
pub const OSMANYA: Script = Script::from_bytes(b"Osma");
pub const SHAVIAN: Script = Script::from_bytes(b"Shaw");
pub const TAI_LE: Script = Script::from_bytes(b"Tale");
pub const UGARITIC: Script = Script::from_bytes(b"Ugar");
// Since 4.1
pub const BUGINESE: Script = Script::from_bytes(b"Bugi");
pub const COPTIC: Script = Script::from_bytes(b"Copt");
pub const GLAGOLITIC: Script = Script::from_bytes(b"Glag");
pub const KHAROSHTHI: Script = Script::from_bytes(b"Khar");
pub const NEW_TAI_LUE: Script = Script::from_bytes(b"Talu");
pub const OLD_PERSIAN: Script = Script::from_bytes(b"Xpeo");
pub const SYLOTI_NAGRI: Script = Script::from_bytes(b"Sylo");
pub const TIFINAGH: Script = Script::from_bytes(b"Tfng");
// Since 5.0
pub const UNKNOWN: Script = Script::from_bytes(b"Zzzz"); // Script can be Unknown, but not Invalid.
pub const BALINESE: Script = Script::from_bytes(b"Bali");
pub const CUNEIFORM: Script = Script::from_bytes(b"Xsux");
pub const NKO: Script = Script::from_bytes(b"Nkoo");
pub const PHAGS_PA: Script = Script::from_bytes(b"Phag");
pub const PHOENICIAN: Script = Script::from_bytes(b"Phnx");
// Since 5.1
pub const CARIAN: Script = Script::from_bytes(b"Cari");
pub const CHAM: Script = Script::from_bytes(b"Cham");
pub const KAYAH_LI: Script = Script::from_bytes(b"Kali");
pub const LEPCHA: Script = Script::from_bytes(b"Lepc");
pub const LYCIAN: Script = Script::from_bytes(b"Lyci");
pub const LYDIAN: Script = Script::from_bytes(b"Lydi");
pub const OL_CHIKI: Script = Script::from_bytes(b"Olck");
pub const REJANG: Script = Script::from_bytes(b"Rjng");
pub const SAURASHTRA: Script = Script::from_bytes(b"Saur");
pub const SUNDANESE: Script = Script::from_bytes(b"Sund");
pub const VAI: Script = Script::from_bytes(b"Vaii");
// Since 5.2
pub const AVESTAN: Script = Script::from_bytes(b"Avst");
pub const BAMUM: Script = Script::from_bytes(b"Bamu");
pub const EGYPTIAN_HIEROGLYPHS: Script = Script::from_bytes(b"Egyp");
pub const IMPERIAL_ARAMAIC: Script = Script::from_bytes(b"Armi");
pub const INSCRIPTIONAL_PAHLAVI: Script = Script::from_bytes(b"Phli");
pub const INSCRIPTIONAL_PARTHIAN: Script = Script::from_bytes(b"Prti");
pub const JAVANESE: Script = Script::from_bytes(b"Java");
pub const KAITHI: Script = Script::from_bytes(b"Kthi");
pub const LISU: Script = Script::from_bytes(b"Lisu");
pub const MEETEI_MAYEK: Script = Script::from_bytes(b"Mtei");
pub const OLD_SOUTH_ARABIAN: Script = Script::from_bytes(b"Sarb");
pub const OLD_TURKIC: Script = Script::from_bytes(b"Orkh");
pub const SAMARITAN: Script = Script::from_bytes(b"Samr");
pub const TAI_THAM: Script = Script::from_bytes(b"Lana");
pub const TAI_VIET: Script = Script::from_bytes(b"Tavt");
// Since 6.0
pub const BATAK: Script = Script::from_bytes(b"Batk");
pub const BRAHMI: Script = Script::from_bytes(b"Brah");
pub const MANDAIC: Script = Script::from_bytes(b"Mand");
// Since 6.1
pub const CHAKMA: Script = Script::from_bytes(b"Cakm");
pub const MEROITIC_CURSIVE: Script = Script::from_bytes(b"Merc");
pub const MEROITIC_HIEROGLYPHS: Script = Script::from_bytes(b"Mero");
pub const MIAO: Script = Script::from_bytes(b"Plrd");
pub const SHARADA: Script = Script::from_bytes(b"Shrd");
pub const SORA_SOMPENG: Script = Script::from_bytes(b"Sora");
pub const TAKRI: Script = Script::from_bytes(b"Takr");
// Since 7.0
pub const BASSA_VAH: Script = Script::from_bytes(b"Bass");
pub const CAUCASIAN_ALBANIAN: Script = Script::from_bytes(b"Aghb");
pub const DUPLOYAN: Script = Script::from_bytes(b"Dupl");
pub const ELBASAN: Script = Script::from_bytes(b"Elba");
pub const GRANTHA: Script = Script::from_bytes(b"Gran");
pub const KHOJKI: Script = Script::from_bytes(b"Khoj");
pub const KHUDAWADI: Script = Script::from_bytes(b"Sind");
pub const LINEAR_A: Script = Script::from_bytes(b"Lina");
pub const MAHAJANI: Script = Script::from_bytes(b"Mahj");
pub const MANICHAEAN: Script = Script::from_bytes(b"Mani");
pub const MENDE_KIKAKUI: Script = Script::from_bytes(b"Mend");
pub const MODI: Script = Script::from_bytes(b"Modi");
pub const MRO: Script = Script::from_bytes(b"Mroo");
pub const NABATAEAN: Script = Script::from_bytes(b"Nbat");
pub const OLD_NORTH_ARABIAN: Script = Script::from_bytes(b"Narb");
pub const OLD_PERMIC: Script = Script::from_bytes(b"Perm");
pub const PAHAWH_HMONG: Script = Script::from_bytes(b"Hmng");
pub const PALMYRENE: Script = Script::from_bytes(b"Palm");
pub const PAU_CIN_HAU: Script = Script::from_bytes(b"Pauc");
pub const PSALTER_PAHLAVI: Script = Script::from_bytes(b"Phlp");
pub const SIDDHAM: Script = Script::from_bytes(b"Sidd");
pub const TIRHUTA: Script = Script::from_bytes(b"Tirh");
pub const WARANG_CITI: Script = Script::from_bytes(b"Wara");
// Since 8.0
pub const AHOM: Script = Script::from_bytes(b"Ahom");
pub const ANATOLIAN_HIEROGLYPHS: Script = Script::from_bytes(b"Hluw");
pub const HATRAN: Script = Script::from_bytes(b"Hatr");
pub const MULTANI: Script = Script::from_bytes(b"Mult");
pub const OLD_HUNGARIAN: Script = Script::from_bytes(b"Hung");
pub const SIGNWRITING: Script = Script::from_bytes(b"Sgnw");
// Since 9.0
pub const ADLAM: Script = Script::from_bytes(b"Adlm");
pub const BHAIKSUKI: Script = Script::from_bytes(b"Bhks");
pub const MARCHEN: Script = Script::from_bytes(b"Marc");
pub const OSAGE: Script = Script::from_bytes(b"Osge");
pub const TANGUT: Script = Script::from_bytes(b"Tang");
pub const NEWA: Script = Script::from_bytes(b"Newa");
// Since 10.0
pub const MASARAM_GONDI: Script = Script::from_bytes(b"Gonm");
pub const NUSHU: Script = Script::from_bytes(b"Nshu");
pub const SOYOMBO: Script = Script::from_bytes(b"Soyo");
pub const ZANABAZAR_SQUARE: Script = Script::from_bytes(b"Zanb");
// Since 11.0
pub const DOGRA: Script = Script::from_bytes(b"Dogr");
pub const GUNJALA_GONDI: Script = Script::from_bytes(b"Gong");
pub const HANIFI_ROHINGYA: Script = Script::from_bytes(b"Rohg");
pub const MAKASAR: Script = Script::from_bytes(b"Maka");
pub const MEDEFAIDRIN: Script = Script::from_bytes(b"Medf");
pub const OLD_SOGDIAN: Script = Script::from_bytes(b"Sogo");
pub const SOGDIAN: Script = Script::from_bytes(b"Sogd");
// Since 12.0
pub const ELYMAIC: Script = Script::from_bytes(b"Elym");
pub const NANDINAGARI: Script = Script::from_bytes(b"Nand");
pub const NYIAKENG_PUACHUE_HMONG: Script = Script::from_bytes(b"Hmnp");
pub const WANCHO: Script = Script::from_bytes(b"Wcho");
// Since 13.0
pub const CHORASMIAN: Script = Script::from_bytes(b"Chrs");
pub const DIVES_AKURU: Script = Script::from_bytes(b"Diak");
pub const KHITAN_SMALL_SCRIPT: Script = Script::from_bytes(b"Kits");
pub const YEZIDI: Script = Script::from_bytes(b"Yezi");
// Since 14.0
pub const CYPRO_MINOAN: Script = Script::from_bytes(b"Cpmn");
pub const OLD_UYGHUR: Script = Script::from_bytes(b"Ougr");
pub const TANGSA: Script = Script::from_bytes(b"Tnsa");
pub const TOTO: Script = Script::from_bytes(b"Toto");
pub const VITHKUQI: Script = Script::from_bytes(b"Vith");
pub const SCRIPT_MATH: Script = Script::from_bytes(b"Zmth");
// https://github.com/harfbuzz/harfbuzz/issues/1162
pub const MYANMAR_ZAWGYI: Script = Script::from_bytes(b"Qaag");
}
/// A feature tag with an accompanying range specifying on which subslice of
/// `shape`s input it should be applied.
#[repr(C)]
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Hash, Debug)]
pub struct Feature {
pub tag: Tag,
pub value: u32,
pub start: u32,
pub end: u32,
}
impl Feature {
/// Create a new `Feature` struct.
pub fn new(tag: Tag, value: u32, range: impl RangeBounds<usize>) -> Feature {
let max = u32::MAX as usize;
let start = match range.start_bound() {
Bound::Included(&included) => included.min(max) as u32,
Bound::Excluded(&excluded) => excluded.min(max - 1) as u32 + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&included) => included.min(max) as u32,
Bound::Excluded(&excluded) => excluded.saturating_sub(1).min(max) as u32,
Bound::Unbounded => max as u32,
};
Feature {
tag,
value,
start,
end,
}
}
pub(crate) fn is_global(&self) -> bool {
self.start == 0 && self.end == u32::MAX
}
}
impl core::str::FromStr for Feature {
type Err = &'static str;
/// Parses a `Feature` form a string.
///
/// Possible values:
///
/// - `kern` -> kern .. 1
/// - `+kern` -> kern .. 1
/// - `-kern` -> kern .. 0
/// - `kern=0` -> kern .. 0
/// - `kern=1` -> kern .. 1
/// - `aalt=2` -> altr .. 2
/// - `kern[]` -> kern .. 1
/// - `kern[:]` -> kern .. 1
/// - `kern[5:]` -> kern 5.. 1
/// - `kern[:5]` -> kern ..=5 1
/// - `kern[3:5]` -> kern 3..=5 1
/// - `kern[3]` -> kern 3..=4 1
/// - `aalt[3:5]=2` -> kern 3..=5 1
fn from_str(s: &str) -> Result<Self, Self::Err> {
fn parse(s: &str) -> Option<Feature> {
if s.is_empty() {
return None;
}
let mut p = TextParser::new(s);
// Parse prefix.
let mut value = 1;
match p.curr_byte()? {
b'-' => {
value = 0;
p.advance(1);
}
b'+' => {
value = 1;
p.advance(1);
}
_ => {}
}
// Parse tag.
p.skip_spaces();
let quote = p.consume_quote();
let tag = p.consume_tag()?;
// Force closing quote.
if let Some(quote) = quote {
p.consume_byte(quote)?;
}
// Parse indices.
p.skip_spaces();
let (start, end) = if p.consume_byte(b'[').is_some() {
let start_opt = p.consume_i32();
let start = start_opt.unwrap_or(0) as u32; // negative value overflow is ok
let end = if matches!(p.curr_byte(), Some(b':') | Some(b';')) {
p.advance(1);
p.consume_i32().unwrap_or(-1) as u32 // negative value overflow is ok
} else {
if start_opt.is_some() && start != core::u32::MAX {
start + 1
} else {
core::u32::MAX
}
};
p.consume_byte(b']')?;
(start, end)
} else {
(0, core::u32::MAX)
};
// Parse postfix.
let had_equal = p.consume_byte(b'=').is_some();
let value1 = p
.consume_i32()
.or_else(|| p.consume_bool().map(|b| b as i32));
if had_equal && value1.is_none() {
return None;
};
if let Some(value1) = value1 {
value = value1 as u32; // negative value overflow is ok
}
p.skip_spaces();
if !p.at_end() {
return None;
}
Some(Feature {
tag,
value,
start,
end,
})
}
parse(s).ok_or("invalid feature")
}
}
#[cfg(test)]
mod tests_features {
use super::*;
use core::str::FromStr;
macro_rules! test {
($name:ident, $text:expr, $tag:expr, $value:expr, $range:expr) => {
#[test]
fn $name() {
assert_eq!(
Feature::from_str($text).unwrap(),
Feature::new(Tag::from_bytes($tag), $value, $range)
);
}
};
}
test!(parse_01, "kern", b"kern", 1, ..);
test!(parse_02, "+kern", b"kern", 1, ..);
test!(parse_03, "-kern", b"kern", 0, ..);
test!(parse_04, "kern=0", b"kern", 0, ..);
test!(parse_05, "kern=1", b"kern", 1, ..);
test!(parse_06, "kern=2", b"kern", 2, ..);
test!(parse_07, "kern[]", b"kern", 1, ..);
test!(parse_08, "kern[:]", b"kern", 1, ..);
test!(parse_09, "kern[5:]", b"kern", 1, 5..);
test!(parse_10, "kern[:5]", b"kern", 1, ..=5);
test!(parse_11, "kern[3:5]", b"kern", 1, 3..=5);
test!(parse_12, "kern[3]", b"kern", 1, 3..=4);
test!(parse_13, "kern[3:5]=2", b"kern", 2, 3..=5);
test!(parse_14, "kern[3;5]=2", b"kern", 2, 3..=5);
test!(parse_15, "kern[:-1]", b"kern", 1, ..);
test!(parse_16, "kern[-1]", b"kern", 1, core::u32::MAX as usize..);
test!(parse_17, "kern=on", b"kern", 1, ..);
test!(parse_18, "kern=off", b"kern", 0, ..);
test!(parse_19, "kern=oN", b"kern", 1, ..);
test!(parse_20, "kern=oFf", b"kern", 0, ..);
}
/// A font variation.
#[repr(C)]
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Variation {
pub tag: Tag,
pub value: f32,
}
impl core::str::FromStr for Variation {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
fn parse(s: &str) -> Option<Variation> {
if s.is_empty() {
return None;
}
let mut p = TextParser::new(s);
// Parse tag.
p.skip_spaces();
let quote = p.consume_quote();
let tag = p.consume_tag()?;
// Force closing quote.
if let Some(quote) = quote {
p.consume_byte(quote)?;
}
let _ = p.consume_byte(b'=');
let value = p.consume_f32()?;
p.skip_spaces();
if !p.at_end() {
return None;
}
Some(Variation { tag, value })
}
parse(s).ok_or("invalid variation")
}
}
pub trait TagExt {
fn default_script() -> Self;
fn default_language() -> Self;
fn to_lowercase(&self) -> Self;
fn to_uppercase(&self) -> Self;
}
impl TagExt for Tag {
#[inline]
fn default_script() -> Self {
Tag::from_bytes(b"DFLT")
}
#[inline]
fn default_language() -> Self {
Tag::from_bytes(b"dflt")
}
/// Converts tag to lowercase.
#[inline]
fn to_lowercase(&self) -> Self {
let b = self.to_bytes();
Tag::from_bytes(&[
b[0].to_ascii_lowercase(),
b[1].to_ascii_lowercase(),
b[2].to_ascii_lowercase(),
b[3].to_ascii_lowercase(),
])
}
/// Converts tag to uppercase.
#[inline]
fn to_uppercase(&self) -> Self {
let b = self.to_bytes();
Tag::from_bytes(&[
b[0].to_ascii_uppercase(),
b[1].to_ascii_uppercase(),
b[2].to_ascii_uppercase(),
b[3].to_ascii_uppercase(),
])
}
}

354
vendor/rustybuzz/src/hb/face.rs vendored Normal file
View File

@@ -0,0 +1,354 @@
use ttf_parser::gdef::GlyphClass;
use ttf_parser::opentype_layout::LayoutTable;
use ttf_parser::GlyphId;
use super::buffer::GlyphPropsFlags;
use super::ot_layout::TableIndex;
use super::ot_layout_common::{PositioningTable, SubstitutionTable};
use crate::Variation;
// https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#windows-platform-platform-id--3
const WINDOWS_SYMBOL_ENCODING: u16 = 0;
const WINDOWS_UNICODE_BMP_ENCODING: u16 = 1;
const WINDOWS_UNICODE_FULL_ENCODING: u16 = 10;
// https://docs.microsoft.com/en-us/typography/opentype/spec/name#platform-specific-encoding-and-language-ids-unicode-platform-platform-id--0
const UNICODE_1_0_ENCODING: u16 = 0;
const UNICODE_1_1_ENCODING: u16 = 1;
const UNICODE_ISO_ENCODING: u16 = 2;
const UNICODE_2_0_BMP_ENCODING: u16 = 3;
const UNICODE_2_0_FULL_ENCODING: u16 = 4;
//const UNICODE_VARIATION_ENCODING: u16 = 5;
const UNICODE_FULL_ENCODING: u16 = 6;
/// A font face handle.
#[derive(Clone)]
pub struct hb_font_t<'a> {
pub(crate) ttfp_face: ttf_parser::Face<'a>,
pub(crate) units_per_em: u16,
pixels_per_em: Option<(u16, u16)>,
pub(crate) points_per_em: Option<f32>,
prefered_cmap_encoding_subtable: Option<u16>,
pub(crate) gsub: Option<SubstitutionTable<'a>>,
pub(crate) gpos: Option<PositioningTable<'a>>,
}
impl<'a> AsRef<ttf_parser::Face<'a>> for hb_font_t<'a> {
#[inline]
fn as_ref(&self) -> &ttf_parser::Face<'a> {
&self.ttfp_face
}
}
impl<'a> AsMut<ttf_parser::Face<'a>> for hb_font_t<'a> {
#[inline]
fn as_mut(&mut self) -> &mut ttf_parser::Face<'a> {
&mut self.ttfp_face
}
}
impl<'a> core::ops::Deref for hb_font_t<'a> {
type Target = ttf_parser::Face<'a>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.ttfp_face
}
}
impl<'a> core::ops::DerefMut for hb_font_t<'a> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.ttfp_face
}
}
impl<'a> hb_font_t<'a> {
/// Creates a new `Face` from data.
///
/// Data will be referenced, not owned.
pub fn from_slice(data: &'a [u8], face_index: u32) -> Option<Self> {
let face = ttf_parser::Face::parse(data, face_index).ok()?;
Some(Self::from_face(face))
}
/// Creates a new [`Face`] from [`ttf_parser::Face`].
///
/// Data will be referenced, not owned.
pub fn from_face(face: ttf_parser::Face<'a>) -> Self {
hb_font_t {
units_per_em: face.units_per_em(),
pixels_per_em: None,
points_per_em: None,
prefered_cmap_encoding_subtable: find_best_cmap_subtable(&face),
gsub: face.tables().gsub.map(SubstitutionTable::new),
gpos: face.tables().gpos.map(PositioningTable::new),
ttfp_face: face,
}
}
// TODO: remove
/// Returns faces units per EM.
#[inline]
pub fn units_per_em(&self) -> i32 {
self.units_per_em as i32
}
#[inline]
pub(crate) fn pixels_per_em(&self) -> Option<(u16, u16)> {
self.pixels_per_em
}
/// Sets pixels per EM.
///
/// Used during raster glyphs processing and hinting.
///
/// `None` by default.
#[inline]
pub fn set_pixels_per_em(&mut self, ppem: Option<(u16, u16)>) {
self.pixels_per_em = ppem;
}
/// Sets point size per EM.
///
/// Used for optical-sizing in Apple fonts.
///
/// `None` by default.
#[inline]
pub fn set_points_per_em(&mut self, ptem: Option<f32>) {
self.points_per_em = ptem;
}
/// Sets font variations.
pub fn set_variations(&mut self, variations: &[Variation]) {
for variation in variations {
self.set_variation(variation.tag, variation.value);
}
}
pub(crate) fn has_glyph(&self, c: u32) -> bool {
self.get_nominal_glyph(c).is_some()
}
pub(crate) fn get_nominal_glyph(&self, c: u32) -> Option<GlyphId> {
let subtable_idx = self.prefered_cmap_encoding_subtable?;
let subtable = self.tables().cmap?.subtables.get(subtable_idx)?;
match subtable.glyph_index(c) {
Some(gid) => Some(gid),
None => {
// Special case for Windows Symbol fonts.
// TODO: add tests
if subtable.platform_id == ttf_parser::PlatformId::Windows
&& subtable.encoding_id == WINDOWS_SYMBOL_ENCODING
{
if c <= 0x00FF {
// For symbol-encoded OpenType fonts, we duplicate the
// U+F000..F0FF range at U+0000..U+00FF. That's what
// Windows seems to do, and that's hinted about at:
// https://docs.microsoft.com/en-us/typography/opentype/spec/recom
// under "Non-Standard (Symbol) Fonts".
return self.get_nominal_glyph(0xF000 + c);
}
}
None
}
}
}
pub(crate) fn glyph_h_advance(&self, glyph: GlyphId) -> i32 {
self.glyph_advance(glyph, false) as i32
}
pub(crate) fn glyph_v_advance(&self, glyph: GlyphId) -> i32 {
-(self.glyph_advance(glyph, true) as i32)
}
fn glyph_advance(&self, glyph: GlyphId, is_vertical: bool) -> u32 {
let face = &self.ttfp_face;
if face.is_variable()
&& face.has_non_default_variation_coordinates()
&& face.tables().hvar.is_none()
&& face.tables().vvar.is_none()
{
return match face.glyph_bounding_box(glyph) {
Some(bbox) => {
(if is_vertical {
bbox.y_max + bbox.y_min
} else {
bbox.x_max + bbox.x_min
}) as u32
}
None => 0,
};
}
if is_vertical {
if face.tables().vmtx.is_some() {
return face.glyph_ver_advance(glyph).unwrap_or(0) as u32;
} else {
// TODO: Original code calls `h_extents_with_fallback`
return (face.ascender() - face.descender()) as u32;
}
} else if !is_vertical && face.tables().hmtx.is_some() {
face.glyph_hor_advance(glyph).unwrap_or(0) as u32
} else {
face.units_per_em() as u32
}
}
pub(crate) fn glyph_h_origin(&self, glyph: GlyphId) -> i32 {
self.glyph_h_advance(glyph) / 2
}
pub(crate) fn glyph_v_origin(&self, glyph: GlyphId) -> i32 {
match self.ttfp_face.glyph_y_origin(glyph) {
Some(y) => i32::from(y),
None => {
let mut extents = GlyphExtents::default();
if self.glyph_extents(glyph, &mut extents) {
if self.ttfp_face.tables().vmtx.is_some() {
extents.y_bearing + self.glyph_side_bearing(glyph, true)
} else {
let advance = self.ttfp_face.ascender() - self.ttfp_face.descender();
let diff = advance as i32 - -extents.height;
return extents.y_bearing + (diff >> 1);
}
} else {
// TODO: Original code calls `h_extents_with_fallback`
self.ttfp_face.ascender() as i32
}
}
}
}
pub(crate) fn glyph_side_bearing(&self, glyph: GlyphId, is_vertical: bool) -> i32 {
let face = &self.ttfp_face;
if face.is_variable() && face.tables().hvar.is_none() && face.tables().vvar.is_none() {
return match face.glyph_bounding_box(glyph) {
Some(bbox) => (if is_vertical { bbox.x_min } else { bbox.y_min }) as i32,
None => 0,
};
}
if is_vertical {
face.glyph_ver_side_bearing(glyph).unwrap_or(0) as i32
} else {
face.glyph_hor_side_bearing(glyph).unwrap_or(0) as i32
}
}
pub(crate) fn glyph_extents(&self, glyph: GlyphId, glyph_extents: &mut GlyphExtents) -> bool {
let pixels_per_em = match self.pixels_per_em {
Some(ppem) => ppem.0,
None => core::u16::MAX,
};
if let Some(img) = self.ttfp_face.glyph_raster_image(glyph, pixels_per_em) {
// HarfBuzz also supports only PNG.
if img.format == ttf_parser::RasterImageFormat::PNG {
let scale = self.units_per_em as f32 / img.pixels_per_em as f32;
glyph_extents.x_bearing = super::round(f32::from(img.x) * scale) as i32;
glyph_extents.y_bearing =
super::round((f32::from(img.y) + f32::from(img.height)) * scale) as i32;
glyph_extents.width = super::round(f32::from(img.width) * scale) as i32;
glyph_extents.height = super::round(-f32::from(img.height) * scale) as i32;
return true;
}
}
let bbox = self.ttfp_face.glyph_bounding_box(glyph);
// See https://github.com/RazrFalcon/rustybuzz/pull/98#issuecomment-1948430785
if self.ttfp_face.tables().glyf.is_some() && bbox.is_none() {
// Empty glyph; zero extents.
return true;
}
let Some(bbox) = bbox else {
return false;
};
glyph_extents.x_bearing = i32::from(bbox.x_min);
glyph_extents.y_bearing = i32::from(bbox.y_max);
glyph_extents.width = i32::from(bbox.width());
glyph_extents.height = i32::from(bbox.y_min - bbox.y_max);
return true;
}
pub(crate) fn glyph_name(&self, glyph: GlyphId) -> Option<&str> {
self.ttfp_face.glyph_name(glyph)
}
pub(crate) fn glyph_props(&self, glyph: GlyphId) -> u16 {
let table = match self.tables().gdef {
Some(v) => v,
None => return 0,
};
match table.glyph_class(glyph) {
Some(GlyphClass::Base) => GlyphPropsFlags::BASE_GLYPH.bits(),
Some(GlyphClass::Ligature) => GlyphPropsFlags::LIGATURE.bits(),
Some(GlyphClass::Mark) => {
let class = table.glyph_mark_attachment_class(glyph);
(class << 8) | GlyphPropsFlags::MARK.bits()
}
_ => 0,
}
}
pub(crate) fn layout_table(&self, table_index: TableIndex) -> Option<&LayoutTable<'a>> {
match table_index {
TableIndex::GSUB => self.gsub.as_ref().map(|table| &table.inner),
TableIndex::GPOS => self.gpos.as_ref().map(|table| &table.inner),
}
}
pub(crate) fn layout_tables(
&self,
) -> impl Iterator<Item = (TableIndex, &LayoutTable<'a>)> + '_ {
TableIndex::iter().filter_map(move |idx| self.layout_table(idx).map(|table| (idx, table)))
}
}
#[derive(Clone, Copy, Default)]
pub struct GlyphExtents {
pub x_bearing: i32,
pub y_bearing: i32,
pub width: i32,
pub height: i32,
}
fn find_best_cmap_subtable(face: &ttf_parser::Face) -> Option<u16> {
use ttf_parser::PlatformId;
// Symbol subtable.
// Prefer symbol if available.
// https://github.com/harfbuzz/harfbuzz/issues/1918
find_cmap_subtable(face, PlatformId::Windows, WINDOWS_SYMBOL_ENCODING)
// 32-bit subtables:
.or_else(|| find_cmap_subtable(face, PlatformId::Windows, WINDOWS_UNICODE_FULL_ENCODING))
.or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_FULL_ENCODING))
.or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_2_0_FULL_ENCODING))
// 16-bit subtables:
.or_else(|| find_cmap_subtable(face, PlatformId::Windows, WINDOWS_UNICODE_BMP_ENCODING))
.or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_2_0_BMP_ENCODING))
.or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_ISO_ENCODING))
.or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_1_1_ENCODING))
.or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_1_0_ENCODING))
}
fn find_cmap_subtable(
face: &ttf_parser::Face,
platform_id: ttf_parser::PlatformId,
encoding_id: u16,
) -> Option<u16> {
for (i, subtable) in face.tables().cmap?.subtables.into_iter().enumerate() {
if subtable.platform_id == platform_id && subtable.encoding_id == encoding_id {
return Some(i as u16);
}
}
None
}

152
vendor/rustybuzz/src/hb/glyph_set.rs vendored Normal file
View File

@@ -0,0 +1,152 @@
use alloc::vec::Vec;
use core::cmp::{self, Ordering};
use core::ops::RangeInclusive;
use ttf_parser::GlyphId;
/// A set of glyphs.
///
/// Performs best when the glyphs are in consecutive ranges.
#[derive(Clone, Debug)]
pub struct GlyphSet {
ranges: Vec<RangeInclusive<GlyphId>>,
}
impl GlyphSet {
/// Create a new glyph set builder.
pub fn builder() -> GlyphSetBuilder {
GlyphSetBuilder { ranges: Vec::new() }
}
/// Check whether the glyph is contained in the set.
pub fn contains(&self, glyph: GlyphId) -> bool {
self.ranges
.binary_search_by(|range| {
if glyph < *range.start() {
Ordering::Greater
} else if glyph <= *range.end() {
Ordering::Equal
} else {
Ordering::Less
}
})
.is_ok()
}
}
/// A builder for a [`GlyphSet`].
#[derive(Clone, Debug)]
pub struct GlyphSetBuilder {
ranges: Vec<RangeInclusive<GlyphId>>,
}
impl GlyphSetBuilder {
/// Insert a single glyph.
pub fn insert(&mut self, glyph: GlyphId) {
self.ranges.push(glyph..=glyph);
}
/// Insert a range of glyphs.
pub fn insert_range(&mut self, range: RangeInclusive<GlyphId>) {
self.ranges.push(range);
}
/// Finish the set building.
pub fn finish(self) -> GlyphSet {
let mut ranges = self.ranges;
// Sort because we want to use binary search in `GlyphSet::contains`.
ranges.sort_by_key(|range| *range.start());
// The visited and merged ranges are in `ranges[..=left]` and the
// unvisited ranges in `ranges[right..]`.
let mut left = 0;
let mut right = 1;
// Merge touching and overlapping adjacent ranges.
//
// The cloning is cheap, it's just needed because `RangeInclusive<T>`
// does not implement `Copy`.
while let Some(next) = ranges.get(right).cloned() {
right += 1;
if let Some(prev) = ranges.get_mut(left) {
// Detect whether the ranges can be merged.
//
// We add one to `prev.end` because we want to merge touching ranges
// like `1..=3` and `4..=5`. We have to be careful with overflow,
// hence `saturating_add`.
if next.start().0 <= prev.end().0.saturating_add(1) {
*prev = *prev.start()..=cmp::max(*prev.end(), *next.end());
continue;
}
}
left += 1;
ranges[left] = next.clone();
}
// Can't overflow because `left < ranges.len() <= isize::MAX`.
ranges.truncate(left + 1);
GlyphSet { ranges }
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn test_empty() {
assert!(GlyphSet::builder().finish().ranges.is_empty());
}
#[test]
fn test_contains() {
let mut builder = GlyphSet::builder();
builder.insert(GlyphId(1));
builder.insert_range(GlyphId(3)..=GlyphId(5));
let set = builder.finish();
assert!(set.contains(GlyphId(1)));
assert!(!set.contains(GlyphId(2)));
assert!(set.contains(GlyphId(3)));
assert!(set.contains(GlyphId(4)));
assert!(set.contains(GlyphId(5)));
assert!(!set.contains(GlyphId(6)));
}
#[test]
fn test_merge_ranges() {
let mut builder = GlyphSet::builder();
builder.insert(GlyphId(0));
builder.insert_range(GlyphId(2)..=GlyphId(6));
builder.insert_range(GlyphId(3)..=GlyphId(7));
builder.insert_range(GlyphId(9)..=GlyphId(10));
builder.insert(GlyphId(9));
builder.insert_range(GlyphId(18)..=GlyphId(21));
builder.insert_range(GlyphId(11)..=GlyphId(14));
assert_eq!(
builder.finish().ranges,
vec![
GlyphId(0)..=GlyphId(0),
GlyphId(2)..=GlyphId(7),
GlyphId(9)..=GlyphId(14),
GlyphId(18)..=GlyphId(21),
]
)
}
#[test]
fn test_merge_ranges_at_numeric_boundaries() {
let mut builder = GlyphSet::builder();
builder.insert_range(GlyphId(3)..=GlyphId(u16::MAX));
builder.insert(GlyphId(u16::MAX - 1));
builder.insert(GlyphId(2));
assert_eq!(
builder.finish().ranges,
vec![GlyphId(2)..=GlyphId(u16::MAX)]
);
}
}

330
vendor/rustybuzz/src/hb/kerning.rs vendored Normal file
View File

@@ -0,0 +1,330 @@
use ttf_parser::{apple_layout, kern, GlyphId};
use super::buffer::*;
use super::ot_layout::TableIndex;
use super::ot_layout_common::lookup_flags;
use super::ot_layout_gpos_table::attach_type;
use super::ot_layout_gsubgpos::{skipping_iterator_t, OT::hb_ot_apply_context_t};
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::{hb_font_t, hb_mask_t};
pub fn kern(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
let subtables = match face.tables().kern {
Some(table) => table.subtables,
None => return,
};
let mut seen_cross_stream = false;
for subtable in subtables {
if subtable.variable {
continue;
}
if buffer.direction.is_horizontal() != subtable.horizontal {
continue;
}
let reverse = buffer.direction.is_backward();
if !seen_cross_stream && subtable.has_cross_stream {
seen_cross_stream = true;
// Attach all glyphs into a chain.
for pos in &mut buffer.pos {
pos.set_attach_type(attach_type::CURSIVE);
pos.set_attach_chain(if buffer.direction.is_forward() { -1 } else { 1 });
// We intentionally don't set BufferScratchFlags::HAS_GPOS_ATTACHMENT,
// since there needs to be a non-zero attachment for post-positioning to
// be needed.
}
}
if reverse {
buffer.reverse();
}
if subtable.has_state_machine {
apply_state_machine_kerning(&subtable, plan.kern_mask, buffer);
} else {
if !plan.requested_kerning {
continue;
}
apply_simple_kerning(&subtable, face, plan.kern_mask, buffer);
}
if reverse {
buffer.reverse();
}
}
}
// TODO: remove
fn machine_kern(
face: &hb_font_t,
buffer: &mut hb_buffer_t,
kern_mask: hb_mask_t,
cross_stream: bool,
get_kerning: impl Fn(u32, u32) -> i32,
) {
buffer.unsafe_to_concat(None, None);
let mut ctx = hb_ot_apply_context_t::new(TableIndex::GPOS, face, buffer);
ctx.lookup_mask = kern_mask;
ctx.lookup_props = u32::from(lookup_flags::IGNORE_MARKS);
let horizontal = ctx.buffer.direction.is_horizontal();
let mut i = 0;
while i < ctx.buffer.len {
if (ctx.buffer.info[i].mask & kern_mask) == 0 {
i += 1;
continue;
}
let mut iter = skipping_iterator_t::new(&ctx, i, 1, false);
let mut unsafe_to = 0;
if !iter.next(Some(&mut unsafe_to)) {
i += 1;
continue;
}
let j = iter.index();
let info = &ctx.buffer.info;
let kern = get_kerning(info[i].glyph_id, info[j].glyph_id);
let pos = &mut ctx.buffer.pos;
if kern != 0 {
if horizontal {
if cross_stream {
pos[j].y_offset = kern;
ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
} else {
let kern1 = kern >> 1;
let kern2 = kern - kern1;
pos[i].x_advance += kern1;
pos[j].x_advance += kern2;
pos[j].x_offset += kern2;
}
} else {
if cross_stream {
pos[j].x_offset = kern;
ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
} else {
let kern1 = kern >> 1;
let kern2 = kern - kern1;
pos[i].y_advance += kern1;
pos[j].y_advance += kern2;
pos[j].y_offset += kern2;
}
}
ctx.buffer.unsafe_to_break(Some(i), Some(j + 1))
}
i = j;
}
}
fn apply_simple_kerning(
subtable: &kern::Subtable,
face: &hb_font_t,
kern_mask: hb_mask_t,
buffer: &mut hb_buffer_t,
) {
machine_kern(
face,
buffer,
kern_mask,
subtable.has_cross_stream,
|left, right| {
subtable
.glyphs_kerning(GlyphId(left as u16), GlyphId(right as u16))
.map(i32::from)
.unwrap_or(0)
},
);
}
struct StateMachineDriver {
stack: [usize; 8],
depth: usize,
}
fn apply_state_machine_kerning(
subtable: &kern::Subtable,
kern_mask: hb_mask_t,
buffer: &mut hb_buffer_t,
) {
let state_table = match subtable.format {
kern::Format::Format1(ref state_table) => state_table,
_ => return,
};
let mut driver = StateMachineDriver {
stack: [0; 8],
depth: 0,
};
let mut state = apple_layout::state::START_OF_TEXT;
buffer.idx = 0;
loop {
let class = if buffer.idx < buffer.len {
state_table
.class(buffer.info[buffer.idx].as_glyph())
.unwrap_or(1)
} else {
apple_layout::class::END_OF_TEXT as u8
};
let entry = match state_table.entry(state, class) {
Some(v) => v,
None => break,
};
// Unsafe-to-break before this if not in state 0, as things might
// go differently if we start from state 0 here.
if state != apple_layout::state::START_OF_TEXT
&& buffer.backtrack_len() != 0
&& buffer.idx < buffer.len
{
// If there's no value and we're just epsilon-transitioning to state 0, safe to break.
if entry.has_offset()
|| !(entry.new_state == apple_layout::state::START_OF_TEXT && !entry.has_advance())
{
buffer.unsafe_to_break_from_outbuffer(
Some(buffer.backtrack_len() - 1),
Some(buffer.idx + 1),
);
}
}
// Unsafe-to-break if end-of-text would kick in here.
if buffer.idx + 2 <= buffer.len {
let end_entry = match state_table.entry(state, apple_layout::class::END_OF_TEXT) {
Some(v) => v,
None => break,
};
if end_entry.has_offset() {
buffer.unsafe_to_break(Some(buffer.idx), Some(buffer.idx + 2));
}
}
state_machine_transition(
entry,
subtable.has_cross_stream,
kern_mask,
state_table,
&mut driver,
buffer,
);
state = state_table.new_state(entry.new_state);
if buffer.idx >= buffer.len {
break;
}
buffer.max_ops -= 1;
if entry.has_advance() || buffer.max_ops <= 0 {
buffer.next_glyph();
}
}
}
fn state_machine_transition(
entry: apple_layout::StateEntry,
has_cross_stream: bool,
kern_mask: hb_mask_t,
state_table: &apple_layout::StateTable,
driver: &mut StateMachineDriver,
buffer: &mut hb_buffer_t,
) {
if entry.has_push() {
if driver.depth < driver.stack.len() {
driver.stack[driver.depth] = buffer.idx;
driver.depth += 1;
} else {
driver.depth = 0; // Probably not what CoreText does, but better?
}
}
if entry.has_offset() && driver.depth != 0 {
let mut value_offset = entry.value_offset();
let mut value = match state_table.kerning(value_offset) {
Some(v) => v,
None => {
driver.depth = 0;
return;
}
};
// From Apple 'kern' spec:
// "Each pops one glyph from the kerning stack and applies the kerning value to it.
// The end of the list is marked by an odd value...
let mut last = false;
while !last && driver.depth != 0 {
driver.depth -= 1;
let idx = driver.stack[driver.depth];
let mut v = value as i32;
value_offset = value_offset.next();
value = state_table.kerning(value_offset).unwrap_or(0);
if idx >= buffer.len {
continue;
}
// "The end of the list is marked by an odd value..."
last = v & 1 != 0;
v &= !1;
// Testing shows that CoreText only applies kern (cross-stream or not)
// if none has been applied by previous subtables. That is, it does
// NOT seem to accumulate as otherwise implied by specs.
let mut has_gpos_attachment = false;
let glyph_mask = buffer.info[idx].mask;
let pos = &mut buffer.pos[idx];
if buffer.direction.is_horizontal() {
if has_cross_stream {
// The following flag is undocumented in the spec, but described
// in the 'kern' table example.
if v == -0x8000 {
pos.set_attach_type(0);
pos.set_attach_chain(0);
pos.y_offset = 0;
} else if pos.attach_type() != 0 {
pos.y_offset += v;
has_gpos_attachment = true;
}
} else if glyph_mask & kern_mask != 0 {
pos.x_advance += v;
pos.x_offset += v;
}
} else {
if has_cross_stream {
// CoreText doesn't do crossStream kerning in vertical. We do.
if v == -0x8000 {
pos.set_attach_type(0);
pos.set_attach_chain(0);
pos.x_offset = 0;
} else if pos.attach_type() != 0 {
pos.x_offset += v;
has_gpos_attachment = true;
}
} else if glyph_mask & kern_mask != 0 {
if pos.y_offset == 0 {
pos.y_advance += v;
pos.y_offset += v;
}
}
}
if has_gpos_attachment {
buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
}
}
}
}

View File

@@ -0,0 +1,120 @@
use core::ops::{Add, AddAssign, Sub, SubAssign};
// Similar to machine_index_t in harfbuzz, but Rust specific.
#[derive(Debug)]
pub struct MachineCursor<'a, T, F> {
data: &'a [T],
pred: F,
pos: usize,
}
impl<'a, T, F> MachineCursor<'a, T, F>
where
F: Fn(&[T], usize) -> bool,
{
pub fn new(data: &'a [T], pred: F) -> Self {
let pos = (0..data.len())
.find(|i| pred(data, *i))
.unwrap_or(data.len());
Self { data, pred, pos }
}
fn advance1(&mut self) {
self.pos = (self.pos + 1..self.data.len())
.find(|q| (self.pred)(self.data, *q))
.unwrap_or(self.data.len());
}
fn recede1(&mut self) {
self.pos = (0..self.pos)
.rev()
.find(|q| (self.pred)(self.data, *q))
.unwrap_or(0);
}
pub fn index(&self) -> usize {
self.pos
}
pub fn end(&self) -> Self
where
F: Clone,
{
Self {
data: self.data,
pred: self.pred.clone(),
pos: self.data.len(),
}
}
}
impl<'a, T, F> Add<usize> for MachineCursor<'a, T, F>
where
F: Fn(&[T], usize) -> bool,
{
type Output = Self;
fn add(mut self, rhs: usize) -> Self::Output {
for _ in 0..rhs {
self.advance1();
}
self
}
}
impl<'a, T, F> Sub<usize> for MachineCursor<'a, T, F>
where
F: Fn(&[T], usize) -> bool,
{
type Output = Self;
fn sub(mut self, rhs: usize) -> Self::Output {
for _ in 0..rhs {
self.recede1();
}
self
}
}
impl<'a, T, F> AddAssign<usize> for MachineCursor<'a, T, F>
where
F: Fn(&[T], usize) -> bool,
{
fn add_assign(&mut self, rhs: usize) {
for _ in 0..rhs {
self.advance1();
}
}
}
impl<'a, T, F> SubAssign<usize> for MachineCursor<'a, T, F>
where
F: Fn(&[T], usize) -> bool,
{
fn sub_assign(&mut self, rhs: usize) {
for _ in 0..rhs {
self.recede1();
}
}
}
impl<'a, T, F> PartialEq for MachineCursor<'a, T, F> {
fn eq(&self, other: &Self) -> bool {
self.pos == other.pos
}
}
impl<'a, T, F> Clone for MachineCursor<'a, T, F>
where
F: Clone,
{
fn clone(&self) -> Self {
Self {
data: self.data,
pred: self.pred.clone(),
pos: self.pos,
}
}
}
impl<'a, T, F> Copy for MachineCursor<'a, T, F> where F: Copy {}

73
vendor/rustybuzz/src/hb/mod.rs vendored Normal file
View File

@@ -0,0 +1,73 @@
// Match harfbuzz code style.
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(non_snake_case)]
mod algs;
#[macro_use]
pub mod buffer;
mod aat_layout;
mod aat_layout_kerx_table;
mod aat_layout_morx_table;
mod aat_layout_trak_table;
mod aat_map;
pub mod common;
pub mod face;
mod glyph_set;
mod kerning;
mod machine_cursor;
mod ot;
mod ot_layout;
mod ot_layout_common;
mod ot_layout_gpos_table;
mod ot_layout_gsub_table;
mod ot_layout_gsubgpos;
mod ot_map;
mod ot_shape;
mod ot_shape_complex;
mod ot_shape_complex_arabic;
mod ot_shape_complex_arabic_table;
mod ot_shape_complex_hangul;
mod ot_shape_complex_hebrew;
mod ot_shape_complex_indic;
mod ot_shape_complex_indic_machine;
mod ot_shape_complex_indic_table;
mod ot_shape_complex_khmer;
mod ot_shape_complex_khmer_machine;
mod ot_shape_complex_myanmar;
mod ot_shape_complex_myanmar_machine;
mod ot_shape_complex_syllabic;
mod ot_shape_complex_thai;
mod ot_shape_complex_use;
mod ot_shape_complex_use_machine;
mod ot_shape_complex_use_table;
mod ot_shape_complex_vowel_constraints;
mod ot_shape_fallback;
mod ot_shape_normalize;
pub mod ot_shape_plan;
pub mod shape;
mod tag;
mod tag_table;
mod text_parser;
mod unicode;
mod unicode_norm;
use ttf_parser::Tag as hb_tag_t;
use self::buffer::hb_glyph_info_t;
use self::face::hb_font_t;
type hb_mask_t = u32;
use self::common::{script, Direction, Feature, Language, Script};
fn round(x: f32) -> f32 {
#[cfg(feature = "std")]
{
x.round()
}
#[cfg(not(feature = "std"))]
{
libm::roundf(x)
}
}

View File

@@ -0,0 +1,33 @@
use crate::hb::ot_layout_gsubgpos::Apply;
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
use crate::hb::ot_map::hb_ot_map_t;
use core::convert::TryFrom;
use ttf_parser::gsub::AlternateSet;
impl Apply for AlternateSet<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let len = self.alternates.len();
if len == 0 {
return None;
}
let glyph_mask = ctx.buffer.cur(0).mask;
// Note: This breaks badly if two features enabled this lookup together.
let shift = ctx.lookup_mask.trailing_zeros();
let mut alt_index = (ctx.lookup_mask & glyph_mask) >> shift;
// If alt_index is MAX_VALUE, randomize feature if it is the rand feature.
if alt_index == hb_ot_map_t::MAX_VALUE && ctx.random {
// Maybe we can do better than unsafe-to-break all; but since we are
// changing random state, it would be hard to track that. Good 'nough.
ctx.buffer.unsafe_to_break(Some(0), Some(ctx.buffer.len));
alt_index = ctx.random_number() % u32::from(len) + 1;
}
let idx = u16::try_from(alt_index).ok()?.checked_sub(1)?;
ctx.replace_glyph(self.alternates.get(idx)?);
Some(())
}
}

View File

@@ -0,0 +1,20 @@
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
use crate::hb::ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext};
use ttf_parser::gsub::AlternateSubstitution;
// AlternateSubstFormat1::would_apply
impl WouldApply for AlternateSubstitution<'_> {
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
ctx.glyphs.len() == 1 && self.coverage.get(ctx.glyphs[0]).is_some()
}
}
// AlternateSubstFormat1::apply
impl Apply for AlternateSubstitution<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let glyph = ctx.buffer.cur(0).as_glyph();
let index = self.coverage.get(glyph)?;
let set = self.alternate_sets.get(index)?;
set.apply(ctx)
}
}

View File

@@ -0,0 +1,62 @@
use crate::hb::ot_layout::MAX_CONTEXT_LENGTH;
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
use crate::hb::ot_layout_gsubgpos::{
ligate_input, match_glyph, match_input, Apply, WouldApply, WouldApplyContext,
};
use ttf_parser::gsub::Ligature;
impl WouldApply for Ligature<'_> {
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
ctx.glyphs.len() == usize::from(self.components.len()) + 1
&& self
.components
.into_iter()
.enumerate()
.all(|(i, comp)| ctx.glyphs[i + 1] == comp)
}
}
impl Apply for Ligature<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
// Special-case to make it in-place and not consider this
// as a "ligated" substitution.
if self.components.is_empty() {
ctx.replace_glyph(self.glyph);
Some(())
} else {
let f = |glyph, num_items| {
let index = self.components.len() - num_items;
let value = self.components.get(index).unwrap();
match_glyph(glyph, value.0)
};
let mut match_end = 0;
let mut match_positions = [0; MAX_CONTEXT_LENGTH];
let mut total_component_count = 0;
if !match_input(
ctx,
self.components.len(),
&f,
&mut match_end,
&mut match_positions,
Some(&mut total_component_count),
) {
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(match_end));
return None;
}
let count = usize::from(self.components.len()) + 1;
ligate_input(
ctx,
count,
&match_positions,
match_end,
total_component_count,
self.glyph,
);
return Some(());
}
}
}

View File

@@ -0,0 +1,20 @@
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
use crate::hb::ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext};
use ttf_parser::gsub::LigatureSet;
impl WouldApply for LigatureSet<'_> {
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
self.into_iter().any(|lig| lig.would_apply(ctx))
}
}
impl Apply for LigatureSet<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
for lig in self.into_iter() {
if lig.apply(ctx).is_some() {
return Some(());
}
}
None
}
}

View File

@@ -0,0 +1,24 @@
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
use crate::hb::ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext};
use ttf_parser::gsub::LigatureSubstitution;
// LigatureSubstFormat1::would_apply
impl WouldApply for LigatureSubstitution<'_> {
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
self.coverage
.get(ctx.glyphs[0])
.and_then(|index| self.ligature_sets.get(index))
.map_or(false, |set| set.would_apply(ctx))
}
}
// LigatureSubstFormat1::apply
impl Apply for LigatureSubstitution<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let glyph = ctx.buffer.cur(0).as_glyph();
self.coverage
.get(glyph)
.and_then(|index| self.ligature_sets.get(index))
.and_then(|set| set.apply(ctx))
}
}

View File

@@ -0,0 +1,9 @@
mod alternate_set;
mod alternate_subst;
mod ligature;
mod ligature_set;
mod ligature_subst;
mod multi_subst;
mod reverse_chain_single_subst;
mod sequence;
mod single_subst;

View File

@@ -0,0 +1,20 @@
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
use crate::hb::ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext};
use ttf_parser::gsub::MultipleSubstitution;
// MultipleSubstFormat1::would_apply
impl WouldApply for MultipleSubstitution<'_> {
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
ctx.glyphs.len() == 1 && self.coverage.get(ctx.glyphs[0]).is_some()
}
}
// MultipleSubstFormat1::apply
impl Apply for MultipleSubstitution<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let glyph = ctx.buffer.cur(0).as_glyph();
let index = self.coverage.get(glyph)?;
let seq = self.sequences.get(index)?;
seq.apply(ctx)
}
}

View File

@@ -0,0 +1,69 @@
use crate::hb::ot_layout::MAX_NESTING_LEVEL;
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
use crate::hb::ot_layout_gsubgpos::{
match_backtrack, match_lookahead, Apply, WouldApply, WouldApplyContext,
};
use ttf_parser::gsub::ReverseChainSingleSubstitution;
// ReverseChainSingleSubstFormat1::would_apply
impl WouldApply for ReverseChainSingleSubstitution<'_> {
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
ctx.glyphs.len() == 1 && self.coverage.get(ctx.glyphs[0]).is_some()
}
}
// ReverseChainSingleSubstFormat1::apply
impl Apply for ReverseChainSingleSubstitution<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
// No chaining to this type.
if ctx.nesting_level_left != MAX_NESTING_LEVEL {
return None;
}
let glyph = ctx.buffer.cur(0).as_glyph();
let index = self.coverage.get(glyph)?;
if index >= self.substitutes.len() {
return None;
}
let subst = self.substitutes.get(index)?;
let f1 = |glyph, num_items| {
let index = self.backtrack_coverages.len() - num_items;
let value = self.backtrack_coverages.get(index).unwrap();
value.contains(glyph)
};
let f2 = |glyph, num_items| {
let index = self.lookahead_coverages.len() - num_items;
let value = self.lookahead_coverages.get(index).unwrap();
value.contains(glyph)
};
let mut start_index = 0;
let mut end_index = 0;
if match_backtrack(ctx, self.backtrack_coverages.len(), &f1, &mut start_index) {
if match_lookahead(
ctx,
self.lookahead_coverages.len(),
&f2,
ctx.buffer.idx + 1,
&mut end_index,
) {
ctx.buffer
.unsafe_to_break_from_outbuffer(Some(start_index), Some(end_index));
ctx.replace_glyph_inplace(subst);
// Note: We DON'T decrease buffer.idx. The main loop does it
// for us. This is useful for preventing surprises if someone
// calls us through a Context lookup.
return Some(());
}
}
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(start_index), Some(end_index));
return None;
}
}

View File

@@ -0,0 +1,44 @@
use crate::hb::buffer::GlyphPropsFlags;
use crate::hb::ot_layout::{
_hb_glyph_info_get_lig_id, _hb_glyph_info_is_ligature,
_hb_glyph_info_set_lig_props_for_component,
};
use crate::hb::ot_layout_gsubgpos::Apply;
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
use ttf_parser::gsub::Sequence;
impl Apply for Sequence<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
match self.substitutes.len() {
// Spec disallows this, but Uniscribe allows it.
// https://github.com/harfbuzz/harfbuzz/issues/253
0 => ctx.buffer.delete_glyph(),
// Special-case to make it in-place and not consider this
// as a "multiplied" substitution.
1 => ctx.replace_glyph(self.substitutes.get(0)?),
_ => {
let class = if _hb_glyph_info_is_ligature(ctx.buffer.cur(0)) {
GlyphPropsFlags::BASE_GLYPH
} else {
GlyphPropsFlags::empty()
};
let lig_id = _hb_glyph_info_get_lig_id(ctx.buffer.cur(0));
for (i, subst) in self.substitutes.into_iter().enumerate() {
// If is attached to a ligature, don't disturb that.
// https://github.com/harfbuzz/harfbuzz/issues/3069
if lig_id == 0 {
// Index is truncated to 4 bits anway, so we can safely cast to u8.
_hb_glyph_info_set_lig_props_for_component(ctx.buffer.cur_mut(0), i as u8);
}
ctx.output_glyph_for_component(subst, class);
}
ctx.buffer.skip_glyph();
}
}
Some(())
}
}

View File

@@ -0,0 +1,38 @@
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
use crate::hb::ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext};
use ttf_parser::gsub::SingleSubstitution;
use ttf_parser::GlyphId;
// SingleSubstFormat1::would_apply
// SingleSubstFormat2::would_apply
impl WouldApply for SingleSubstitution<'_> {
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
ctx.glyphs.len() == 1 && self.coverage().get(ctx.glyphs[0]).is_some()
}
}
// SingleSubstFormat1::apply
// SingleSubstFormat2::apply
impl Apply for SingleSubstitution<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let glyph = ctx.buffer.cur(0).as_glyph();
let subst = match *self {
Self::Format1 { coverage, delta } => {
coverage.get(glyph)?;
// According to the Adobe Annotated OpenType Suite, result is always
// limited to 16bit, so we explicitly want to truncate.
GlyphId((i32::from(glyph.0) + i32::from(delta)) as u16)
}
Self::Format2 {
coverage,
substitutes,
} => {
let index = coverage.get(glyph)?;
substitutes.get(index)?
}
};
ctx.replace_glyph(subst);
Some(())
}
}

View File

@@ -0,0 +1,43 @@
use crate::hb::ot_layout::LayoutLookup;
use crate::hb::ot_layout_common::SubstLookup;
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
use crate::hb::ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext};
use ttf_parser::GlyphId;
impl LayoutLookup for SubstLookup<'_> {
fn props(&self) -> u32 {
self.props
}
fn is_reverse(&self) -> bool {
self.reverse
}
fn covers(&self, glyph: GlyphId) -> bool {
self.coverage.contains(glyph)
}
}
impl WouldApply for SubstLookup<'_> {
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
self.covers(ctx.glyphs[0])
&& self
.subtables
.iter()
.any(|subtable| subtable.would_apply(ctx))
}
}
impl Apply for SubstLookup<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
if self.covers(ctx.buffer.cur(0).as_glyph()) {
for subtable in &self.subtables {
if subtable.apply(ctx).is_some() {
return Some(());
}
}
}
None
}
}

View File

@@ -0,0 +1,31 @@
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
use crate::hb::ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext};
use ttf_parser::gsub::SubstitutionSubtable;
impl WouldApply for SubstitutionSubtable<'_> {
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
match self {
Self::Single(t) => t.would_apply(ctx),
Self::Multiple(t) => t.would_apply(ctx),
Self::Alternate(t) => t.would_apply(ctx),
Self::Ligature(t) => t.would_apply(ctx),
Self::Context(t) => t.would_apply(ctx),
Self::ChainContext(t) => t.would_apply(ctx),
Self::ReverseChainSingle(t) => t.would_apply(ctx),
}
}
}
impl Apply for SubstitutionSubtable<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
match self {
Self::Single(t) => t.apply(ctx),
Self::Multiple(t) => t.apply(ctx),
Self::Alternate(t) => t.apply(ctx),
Self::Ligature(t) => t.apply(ctx),
Self::Context(t) => t.apply(ctx),
Self::ChainContext(t) => t.apply(ctx),
Self::ReverseChainSingle(t) => t.apply(ctx),
}
}
}

View File

@@ -0,0 +1 @@
mod GSUB;

1
vendor/rustybuzz/src/hb/ot/mod.rs vendored Normal file
View File

@@ -0,0 +1 @@
mod layout;

723
vendor/rustybuzz/src/hb/ot_layout.rs vendored Normal file
View File

@@ -0,0 +1,723 @@
//! OpenType layout.
use core::ops::{Index, IndexMut};
use ttf_parser::opentype_layout::{FeatureIndex, LanguageIndex, LookupIndex, ScriptIndex};
use ttf_parser::GlyphId;
use super::buffer::*;
use super::common::TagExt;
use super::ot_layout_gsubgpos::{Apply, OT};
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::unicode::{hb_unicode_funcs_t, hb_unicode_general_category_t, GeneralCategoryExt};
use super::{hb_font_t, hb_glyph_info_t, hb_tag_t};
pub const MAX_NESTING_LEVEL: usize = 64;
pub const MAX_CONTEXT_LENGTH: usize = 64;
pub fn hb_ot_layout_has_kerning(face: &hb_font_t) -> bool {
face.tables().kern.is_some()
}
pub fn hb_ot_layout_has_machine_kerning(face: &hb_font_t) -> bool {
match face.tables().kern {
Some(ref kern) => kern.subtables.into_iter().any(|s| s.has_state_machine),
None => false,
}
}
pub fn hb_ot_layout_has_cross_kerning(face: &hb_font_t) -> bool {
match face.tables().kern {
Some(ref kern) => kern.subtables.into_iter().any(|s| s.has_cross_stream),
None => false,
}
}
// hb_ot_layout_kern
// OT::GDEF::is_blocklisted unsupported
pub fn _hb_ot_layout_set_glyph_props(face: &hb_font_t, buffer: &mut hb_buffer_t) {
let len = buffer.len;
for info in &mut buffer.info[..len] {
info.set_glyph_props(face.glyph_props(info.as_glyph()));
info.set_lig_props(0);
info.set_syllable(0);
}
}
pub fn hb_ot_layout_has_glyph_classes(face: &hb_font_t) -> bool {
face.tables()
.gdef
.map_or(false, |table| table.has_glyph_classes())
}
// get_gsubgpos_table
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TableIndex {
GSUB = 0,
GPOS = 1,
}
impl TableIndex {
pub fn iter() -> impl Iterator<Item = TableIndex> {
[Self::GSUB, Self::GPOS].iter().copied()
}
}
impl<T> Index<TableIndex> for [T] {
type Output = T;
fn index(&self, table_index: TableIndex) -> &Self::Output {
&self[table_index as usize]
}
}
impl<T> IndexMut<TableIndex> for [T] {
fn index_mut(&mut self, table_index: TableIndex) -> &mut Self::Output {
&mut self[table_index as usize]
}
}
/// A lookup-based layout table (GSUB or GPOS).
pub trait LayoutTable {
/// The index of this table.
const INDEX: TableIndex;
/// Whether lookups in this table can be applied to the buffer in-place.
const IN_PLACE: bool;
/// The kind of lookup stored in this table.
type Lookup: LayoutLookup;
/// Get the lookup at the specified index.
fn get_lookup(&self, index: LookupIndex) -> Option<&Self::Lookup>;
}
/// A lookup in a layout table.
pub trait LayoutLookup: Apply {
/// The lookup's lookup_props.
fn props(&self) -> u32;
/// Whether the lookup has to be applied backwards.
fn is_reverse(&self) -> bool;
/// Whether any subtable of the lookup could apply at a specific glyph.
fn covers(&self, glyph: GlyphId) -> bool;
}
pub trait LayoutTableExt {
fn select_script(&self, script_tags: &[hb_tag_t]) -> Option<(bool, ScriptIndex, hb_tag_t)>;
fn select_script_language(
&self,
script_index: ScriptIndex,
lang_tags: &[hb_tag_t],
) -> Option<LanguageIndex>;
fn get_required_language_feature(
&self,
script_index: ScriptIndex,
lang_index: Option<LanguageIndex>,
) -> Option<(FeatureIndex, hb_tag_t)>;
fn find_language_feature(
&self,
script_index: ScriptIndex,
lang_index: Option<LanguageIndex>,
feature_tag: hb_tag_t,
) -> Option<FeatureIndex>;
}
impl LayoutTableExt for ttf_parser::opentype_layout::LayoutTable<'_> {
// hb_ot_layout_table_select_script
/// Returns true + index and tag of the first found script tag in the given GSUB or GPOS table
/// or false + index and tag if falling back to a default script.
fn select_script(&self, script_tags: &[hb_tag_t]) -> Option<(bool, ScriptIndex, hb_tag_t)> {
for &tag in script_tags {
if let Some(index) = self.scripts.index(tag) {
return Some((true, index, tag));
}
}
for &tag in &[
// try finding 'DFLT'
hb_tag_t::default_script(),
// try with 'dflt'; MS site has had typos and many fonts use it now :(
hb_tag_t::default_language(),
// try with 'latn'; some old fonts put their features there even though
// they're really trying to support Thai, for example :(
hb_tag_t::from_bytes(b"latn"),
] {
if let Some(index) = self.scripts.index(tag) {
return Some((false, index, tag));
}
}
None
}
// hb_ot_layout_script_select_language
/// Returns the index of the first found language tag in the given GSUB or GPOS table,
/// underneath the specified script index.
fn select_script_language(
&self,
script_index: ScriptIndex,
lang_tags: &[hb_tag_t],
) -> Option<LanguageIndex> {
let script = self.scripts.get(script_index)?;
for &tag in lang_tags {
if let Some(index) = script.languages.index(tag) {
return Some(index);
}
}
// try finding 'dflt'
if let Some(index) = script.languages.index(hb_tag_t::default_language()) {
return Some(index);
}
None
}
// hb_ot_layout_language_get_required_feature
/// Returns the index and tag of a required feature in the given GSUB or GPOS table,
/// underneath the specified script and language.
fn get_required_language_feature(
&self,
script_index: ScriptIndex,
lang_index: Option<LanguageIndex>,
) -> Option<(FeatureIndex, hb_tag_t)> {
let script = self.scripts.get(script_index)?;
let sys = match lang_index {
Some(index) => script.languages.get(index)?,
None => script.default_language?,
};
let idx = sys.required_feature?;
let tag = self.features.get(idx)?.tag;
Some((idx, tag))
}
// hb_ot_layout_language_find_feature
/// Returns the index of a given feature tag in the given GSUB or GPOS table,
/// underneath the specified script and language.
fn find_language_feature(
&self,
script_index: ScriptIndex,
lang_index: Option<LanguageIndex>,
feature_tag: hb_tag_t,
) -> Option<FeatureIndex> {
let script = self.scripts.get(script_index)?;
let sys = match lang_index {
Some(index) => script.languages.get(index)?,
None => script.default_language?,
};
for i in 0..sys.feature_indices.len() {
if let Some(index) = sys.feature_indices.get(i) {
if self.features.get(index).map(|v| v.tag) == Some(feature_tag) {
return Some(index);
}
}
}
None
}
}
/// Called before substitution lookups are performed, to ensure that glyph
/// class and other properties are set on the glyphs in the buffer.
pub fn hb_ot_layout_substitute_start(face: &hb_font_t, buffer: &mut hb_buffer_t) {
_hb_ot_layout_set_glyph_props(face, buffer)
}
/// Applies the lookups in the given GSUB or GPOS table.
pub fn apply_layout_table<T: LayoutTable>(
plan: &hb_ot_shape_plan_t,
face: &hb_font_t,
buffer: &mut hb_buffer_t,
table: Option<&T>,
) {
let mut ctx = OT::hb_ot_apply_context_t::new(T::INDEX, face, buffer);
for (stage_index, stage) in plan.ot_map.stages(T::INDEX).iter().enumerate() {
for lookup in plan.ot_map.stage_lookups(T::INDEX, stage_index) {
ctx.lookup_index = lookup.index;
ctx.lookup_mask = lookup.mask;
ctx.auto_zwj = lookup.auto_zwj;
ctx.auto_zwnj = lookup.auto_zwnj;
ctx.random = lookup.random;
ctx.per_syllable = lookup.per_syllable;
if let Some(table) = &table {
if let Some(lookup) = table.get_lookup(lookup.index) {
apply_string::<T>(&mut ctx, lookup);
}
}
}
if let Some(func) = stage.pause_func {
func(plan, face, ctx.buffer);
}
}
}
fn apply_string<T: LayoutTable>(ctx: &mut OT::hb_ot_apply_context_t, lookup: &T::Lookup) {
if ctx.buffer.is_empty() || ctx.lookup_mask == 0 {
return;
}
ctx.lookup_props = lookup.props();
if !lookup.is_reverse() {
// in/out forward substitution/positioning
if !T::IN_PLACE {
ctx.buffer.clear_output();
}
ctx.buffer.idx = 0;
apply_forward(ctx, lookup);
if !T::IN_PLACE {
ctx.buffer.sync();
}
} else {
// in-place backward substitution/positioning
assert!(!ctx.buffer.have_output);
ctx.buffer.idx = ctx.buffer.len - 1;
apply_backward(ctx, lookup);
}
}
fn apply_forward(ctx: &mut OT::hb_ot_apply_context_t, lookup: &impl Apply) -> bool {
let mut ret = false;
while ctx.buffer.idx < ctx.buffer.len && ctx.buffer.successful {
let cur = ctx.buffer.cur(0);
if (cur.mask & ctx.lookup_mask) != 0
&& ctx.check_glyph_property(cur, ctx.lookup_props)
&& lookup.apply(ctx).is_some()
{
ret = true;
} else {
ctx.buffer.next_glyph();
}
}
ret
}
fn apply_backward(ctx: &mut OT::hb_ot_apply_context_t, lookup: &impl Apply) -> bool {
let mut ret = false;
loop {
let cur = ctx.buffer.cur(0);
ret |= (cur.mask & ctx.lookup_mask) != 0
&& ctx.check_glyph_property(cur, ctx.lookup_props)
&& lookup.apply(ctx).is_some();
if ctx.buffer.idx == 0 {
break;
}
ctx.buffer.idx -= 1;
}
ret
}
/* unicode_props */
/* Design:
* unicode_props() is a two-byte number. The low byte includes:
* - General_Category: 5 bits.
* - A bit each for:
* * Is it Default_Ignorable(); we have a modified Default_Ignorable().
* * Whether it's one of the four Mongolian Free Variation Selectors,
* CGJ, or other characters that are hidden but should not be ignored
* like most other Default_Ignorable()s do during matching.
* * Whether it's a grapheme continuation.
*
* The high-byte has different meanings, switched by the Gen-Cat:
* - For Mn,Mc,Me: the modified Combining_Class.
* - For Cf: whether it's ZWJ, ZWNJ, or something else.
* - For Ws: index of which space character this is, if space fallback
* is needed, ie. we don't set this by default, only if asked to.
*/
// enum hb_unicode_props_flags_t {
// UPROPS_MASK_GEN_CAT = 0x001Fu,
// UPROPS_MASK_IGNORABLE = 0x0020u,
// UPROPS_MASK_HIDDEN = 0x0040u, /* MONGOLIAN FREE VARIATION SELECTOR 1..4, or TAG characters */
// UPROPS_MASK_CONTINUATION=0x0080u,
// /* If GEN_CAT=FORMAT, top byte masks: */
// UPROPS_MASK_Cf_ZWJ = 0x0100u,
// UPROPS_MASK_Cf_ZWNJ = 0x0200u
// };
// HB_MARK_AS_FLAG_T (hb_unicode_props_flags_t);
// static inline void
// _hb_glyph_info_set_unicode_props (hb_glyph_info_t *info, hb_buffer_t *buffer)
// {
// hb_unicode_funcs_t *unicode = buffer->unicode;
// unsigned int u = info->codepoint;
// unsigned int gen_cat = (unsigned int) unicode->general_category (u);
// unsigned int props = gen_cat;
// if (u >= 0x80u)
// {
// buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_NON_ASCII;
// if (unlikely (unicode->is_default_ignorable (u)))
// {
// buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES;
// props |= UPROPS_MASK_IGNORABLE;
// if (u == 0x200Cu) props |= UPROPS_MASK_Cf_ZWNJ;
// else if (u == 0x200Du) props |= UPROPS_MASK_Cf_ZWJ;
// /* Mongolian Free Variation Selectors need to be remembered
// * because although we need to hide them like default-ignorables,
// * they need to non-ignorable during shaping. This is similar to
// * what we do for joiners in Indic-like shapers, but since the
// * FVSes are GC=Mn, we have use a separate bit to remember them.
// * Fixes:
// * https://github.com/harfbuzz/harfbuzz/issues/234 */
// else if (unlikely (hb_in_ranges<hb_codepoint_t> (u, 0x180Bu, 0x180Du, 0x180Fu, 0x180Fu))) props |= UPROPS_MASK_HIDDEN;
// /* TAG characters need similar treatment. Fixes:
// * https://github.com/harfbuzz/harfbuzz/issues/463 */
// else if (unlikely (hb_in_range<hb_codepoint_t> (u, 0xE0020u, 0xE007Fu))) props |= UPROPS_MASK_HIDDEN;
// /* COMBINING GRAPHEME JOINER should not be skipped; at least some times.
// * https://github.com/harfbuzz/harfbuzz/issues/554 */
// else if (unlikely (u == 0x034Fu))
// {
// buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_CGJ;
// props |= UPROPS_MASK_HIDDEN;
// }
// }
// if (unlikely (HB_UNICODE_GENERAL_CATEGORY_IS_MARK (gen_cat)))
// {
// props |= UPROPS_MASK_CONTINUATION;
// props |= unicode->modified_combining_class (u)<<8;
// }
// }
// info->unicode_props() = props;
// }
#[inline]
pub fn _hb_glyph_info_set_general_category(
info: &mut hb_glyph_info_t,
gen_cat: hb_unicode_general_category_t,
) {
/* Clears top-byte. */
let gen_cat = gen_cat.to_rb();
let n =
(gen_cat as u16) | (info.unicode_props() & (0xFF & !UnicodeProps::GENERAL_CATEGORY.bits()));
info.set_unicode_props(n);
}
#[inline]
pub fn _hb_glyph_info_get_general_category(
info: &hb_glyph_info_t,
) -> hb_unicode_general_category_t {
let n = info.unicode_props() & UnicodeProps::GENERAL_CATEGORY.bits();
hb_unicode_general_category_t::from_rb(n as u32)
}
#[inline]
pub fn _hb_glyph_info_is_unicode_mark(info: &hb_glyph_info_t) -> bool {
_hb_glyph_info_get_general_category(info).is_mark()
}
#[inline]
pub(crate) fn _hb_glyph_info_set_modified_combining_class(
info: &mut hb_glyph_info_t,
modified_class: u8,
) {
if !_hb_glyph_info_is_unicode_mark(info) {
return;
}
let n = ((modified_class as u16) << 8) | (info.unicode_props() & 0xFF);
info.set_unicode_props(n);
}
#[inline]
pub fn _hb_glyph_info_get_modified_combining_class(info: &hb_glyph_info_t) -> u8 {
if _hb_glyph_info_is_unicode_mark(info) {
(info.unicode_props() >> 8) as u8
} else {
0
}
}
// TODO: use
// #[inline]
// pub fn info_cc(info: &hb_glyph_info_t) -> u8 {
// _hb_glyph_info_get_modified_combining_class(info)
// }
#[inline]
pub(crate) fn _hb_glyph_info_is_unicode_space(info: &hb_glyph_info_t) -> bool {
_hb_glyph_info_get_general_category(info) == hb_unicode_general_category_t::SpaceSeparator
}
#[inline]
pub(crate) fn _hb_glyph_info_set_unicode_space_fallback_type(
info: &mut hb_glyph_info_t,
s: hb_unicode_funcs_t::space_t,
) {
if !_hb_glyph_info_is_unicode_space(info) {
return;
}
let n = ((s as u16) << 8) | (info.unicode_props() & 0xFF);
info.set_unicode_props(n);
}
#[inline]
pub(crate) fn _hb_glyph_info_get_unicode_space_fallback_type(
info: &hb_glyph_info_t,
) -> hb_unicode_funcs_t::space_t {
if _hb_glyph_info_is_unicode_space(info) {
(info.unicode_props() >> 8) as u8
} else {
hb_unicode_funcs_t::NOT_SPACE
}
}
#[inline]
pub(crate) fn _hb_glyph_info_is_default_ignorable(info: &hb_glyph_info_t) -> bool {
let n = info.unicode_props() & UnicodeProps::IGNORABLE.bits();
n != 0 && !_hb_glyph_info_substituted(info)
}
// static inline bool
// _hb_glyph_info_is_default_ignorable_and_not_hidden (const hb_glyph_info_t *info)
// {
// return ((info->unicode_props() & (UPROPS_MASK_IGNORABLE|UPROPS_MASK_HIDDEN))
// == UPROPS_MASK_IGNORABLE) &&
// !_hb_glyph_info_substituted (info);
// }
// static inline void
// _hb_glyph_info_unhide (hb_glyph_info_t *info)
// {
// info->unicode_props() &= ~ UPROPS_MASK_HIDDEN;
// }
#[inline]
pub(crate) fn _hb_glyph_info_set_continuation(info: &mut hb_glyph_info_t) {
let mut n = info.unicode_props();
n |= UnicodeProps::CONTINUATION.bits();
info.set_unicode_props(n);
}
#[inline]
pub(crate) fn _hb_glyph_info_reset_continuation(info: &mut hb_glyph_info_t) {
let mut n = info.unicode_props();
n &= !UnicodeProps::CONTINUATION.bits();
info.set_unicode_props(n);
}
#[inline]
pub(crate) fn _hb_glyph_info_is_continuation(info: &hb_glyph_info_t) -> bool {
info.unicode_props() & UnicodeProps::CONTINUATION.bits() != 0
}
pub(crate) fn _hb_grapheme_group_func(_: &hb_glyph_info_t, b: &hb_glyph_info_t) -> bool {
_hb_glyph_info_is_continuation(b)
}
pub fn _hb_ot_layout_reverse_graphemes(buffer: &mut hb_buffer_t) {
buffer.reverse_groups(
_hb_grapheme_group_func,
buffer.cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS,
)
}
#[inline]
pub(crate) fn _hb_glyph_info_is_unicode_format(info: &hb_glyph_info_t) -> bool {
_hb_glyph_info_get_general_category(info) == hb_unicode_general_category_t::Format
}
#[inline]
pub(crate) fn _hb_glyph_info_is_zwnj(info: &hb_glyph_info_t) -> bool {
_hb_glyph_info_is_unicode_format(info)
&& (info.unicode_props() & UnicodeProps::CF_ZWNJ.bits() != 0)
}
#[inline]
pub(crate) fn _hb_glyph_info_is_zwj(info: &hb_glyph_info_t) -> bool {
_hb_glyph_info_is_unicode_format(info)
&& (info.unicode_props() & UnicodeProps::CF_ZWJ.bits() != 0)
}
// static inline bool
// _hb_glyph_info_is_joiner (const hb_glyph_info_t *info)
// {
// return _hb_glyph_info_is_unicode_format (info) && (info->unicode_props() & (UPROPS_MASK_Cf_ZWNJ|UPROPS_MASK_Cf_ZWJ));
// }
// static inline void
// _hb_glyph_info_flip_joiners (hb_glyph_info_t *info)
// {
// if (!_hb_glyph_info_is_unicode_format (info))
// return;
// info->unicode_props() ^= UPROPS_MASK_Cf_ZWNJ | UPROPS_MASK_Cf_ZWJ;
// }
// /* lig_props: aka lig_id / lig_comp
// *
// * When a ligature is formed:
// *
// * - The ligature glyph and any marks in between all the same newly allocated
// * lig_id,
// * - The ligature glyph will get lig_num_comps set to the number of components
// * - The marks get lig_comp > 0, reflecting which component of the ligature
// * they were applied to.
// * - This is used in GPOS to attach marks to the right component of a ligature
// * in MarkLigPos,
// * - Note that when marks are ligated together, much of the above is skipped
// * and the current lig_id reused.
// *
// * When a multiple-substitution is done:
// *
// * - All resulting glyphs will have lig_id = 0,
// * - The resulting glyphs will have lig_comp = 0, 1, 2, ... respectively.
// * - This is used in GPOS to attach marks to the first component of a
// * multiple substitution in MarkBasePos.
// *
// * The numbers are also used in GPOS to do mark-to-mark positioning only
// * to marks that belong to the same component of the same ligature.
// */
// static inline void
// _hb_glyph_info_clear_lig_props (hb_glyph_info_t *info)
// {
// info->lig_props() = 0;
// }
const IS_LIG_BASE: u8 = 0x10;
#[inline]
pub(crate) fn _hb_glyph_info_set_lig_props_for_ligature(
info: &mut hb_glyph_info_t,
lig_id: u8,
lig_num_comps: u8,
) {
info.set_lig_props((lig_id << 5) | IS_LIG_BASE | (lig_num_comps & 0x0F));
}
#[inline]
pub(crate) fn _hb_glyph_info_set_lig_props_for_mark(
info: &mut hb_glyph_info_t,
lig_id: u8,
lig_comp: u8,
) {
info.set_lig_props((lig_id << 5) | (lig_comp & 0x0F));
}
#[inline]
pub(crate) fn _hb_glyph_info_set_lig_props_for_component(info: &mut hb_glyph_info_t, comp: u8) {
_hb_glyph_info_set_lig_props_for_mark(info, 0, comp);
}
#[inline]
pub(crate) fn _hb_glyph_info_get_lig_id(info: &hb_glyph_info_t) -> u8 {
info.lig_props() >> 5
}
#[inline]
pub(crate) fn _hb_glyph_info_ligated_internal(info: &hb_glyph_info_t) -> bool {
info.lig_props() & IS_LIG_BASE != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_get_lig_comp(info: &hb_glyph_info_t) -> u8 {
if _hb_glyph_info_ligated_internal(info) {
0
} else {
info.lig_props() & 0x0F
}
}
#[inline]
pub(crate) fn _hb_glyph_info_get_lig_num_comps(info: &hb_glyph_info_t) -> u8 {
if info.glyph_props() & GlyphPropsFlags::LIGATURE.bits() != 0
&& _hb_glyph_info_ligated_internal(info)
{
info.lig_props() & 0x0F
} else {
1
}
}
// /* glyph_props: */
// static inline void
// _hb_glyph_info_set_glyph_props (hb_glyph_info_t *info, unsigned int props)
// {
// info->glyph_props() = props;
// }
// static inline unsigned int
// _hb_glyph_info_get_glyph_props (const hb_glyph_info_t *info)
// {
// return info->glyph_props();
// }
#[inline]
pub(crate) fn _hb_glyph_info_is_base_glyph(info: &hb_glyph_info_t) -> bool {
info.glyph_props() & GlyphPropsFlags::BASE_GLYPH.bits() != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_is_ligature(info: &hb_glyph_info_t) -> bool {
info.glyph_props() & GlyphPropsFlags::LIGATURE.bits() != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_is_mark(info: &hb_glyph_info_t) -> bool {
info.glyph_props() & GlyphPropsFlags::MARK.bits() != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_substituted(info: &hb_glyph_info_t) -> bool {
info.glyph_props() & GlyphPropsFlags::SUBSTITUTED.bits() != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_ligated(info: &hb_glyph_info_t) -> bool {
info.glyph_props() & GlyphPropsFlags::LIGATED.bits() != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_multiplied(info: &hb_glyph_info_t) -> bool {
info.glyph_props() & GlyphPropsFlags::MULTIPLIED.bits() != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_ligated_and_didnt_multiply(info: &hb_glyph_info_t) -> bool {
_hb_glyph_info_ligated(info) && !_hb_glyph_info_multiplied(info)
}
#[inline]
pub(crate) fn _hb_glyph_info_clear_ligated_and_multiplied(info: &mut hb_glyph_info_t) {
let mut n = info.glyph_props();
n &= !(GlyphPropsFlags::LIGATED | GlyphPropsFlags::MULTIPLIED).bits();
info.set_glyph_props(n);
}
#[inline]
pub(crate) fn _hb_glyph_info_clear_substituted(info: &mut hb_glyph_info_t) {
let mut n = info.glyph_props();
n &= !GlyphPropsFlags::SUBSTITUTED.bits();
info.set_glyph_props(n);
}
pub fn _hb_clear_substitution_flags(
_: &hb_ot_shape_plan_t,
_: &hb_font_t,
buffer: &mut hb_buffer_t,
) {
let len = buffer.len;
for info in &mut buffer.info[..len] {
_hb_glyph_info_clear_substituted(info);
}
}

View File

@@ -0,0 +1,142 @@
use alloc::vec::Vec;
use ttf_parser::gpos::PositioningSubtable;
use ttf_parser::gsub::SubstitutionSubtable;
use ttf_parser::opentype_layout::{Coverage, Lookup};
use super::glyph_set::{GlyphSet, GlyphSetBuilder};
#[allow(dead_code)]
pub mod lookup_flags {
pub const RIGHT_TO_LEFT: u16 = 0x0001;
pub const IGNORE_BASE_GLYPHS: u16 = 0x0002;
pub const IGNORE_LIGATURES: u16 = 0x0004;
pub const IGNORE_MARKS: u16 = 0x0008;
pub const IGNORE_FLAGS: u16 = 0x000E;
pub const USE_MARK_FILTERING_SET: u16 = 0x0010;
pub const MARK_ATTACHMENT_TYPE_MASK: u16 = 0xFF00;
}
#[derive(Clone)]
pub struct PositioningTable<'a> {
pub inner: ttf_parser::opentype_layout::LayoutTable<'a>,
pub lookups: Vec<PositioningLookup<'a>>,
}
impl<'a> PositioningTable<'a> {
pub fn new(inner: ttf_parser::opentype_layout::LayoutTable<'a>) -> Self {
let lookups = inner
.lookups
.into_iter()
.map(PositioningLookup::parse)
.collect();
Self { inner, lookups }
}
}
pub trait CoverageExt {
fn collect(&self, set: &mut GlyphSetBuilder);
}
impl CoverageExt for Coverage<'_> {
/// Collect this coverage table into a glyph set.
fn collect(&self, set: &mut GlyphSetBuilder) {
match *self {
Self::Format1 { glyphs } => {
for glyph in glyphs {
set.insert(glyph);
}
}
Self::Format2 { records } => {
for record in records {
set.insert_range(record.start..=record.end);
}
}
}
}
}
#[derive(Clone)]
pub struct PositioningLookup<'a> {
pub subtables: Vec<PositioningSubtable<'a>>,
pub coverage: GlyphSet,
pub props: u32,
}
impl<'a> PositioningLookup<'a> {
pub fn parse(lookup: Lookup<'a>) -> Self {
let subtables: Vec<_> = lookup
.subtables
.into_iter::<PositioningSubtable>()
.collect();
let mut coverage = GlyphSet::builder();
for subtable in &subtables {
subtable.coverage().collect(&mut coverage);
}
Self {
subtables,
coverage: coverage.finish(),
props: lookup_props(lookup),
}
}
}
#[derive(Clone)]
pub struct SubstitutionTable<'a> {
pub inner: ttf_parser::opentype_layout::LayoutTable<'a>,
pub lookups: Vec<SubstLookup<'a>>,
}
impl<'a> SubstitutionTable<'a> {
pub fn new(inner: ttf_parser::opentype_layout::LayoutTable<'a>) -> Self {
let lookups = inner.lookups.into_iter().map(SubstLookup::parse).collect();
Self { inner, lookups }
}
}
#[derive(Clone)]
pub struct SubstLookup<'a> {
pub subtables: Vec<SubstitutionSubtable<'a>>,
pub coverage: GlyphSet,
pub reverse: bool,
pub props: u32,
}
impl<'a> SubstLookup<'a> {
pub fn parse(lookup: Lookup<'a>) -> Self {
let subtables: Vec<_> = lookup
.subtables
.into_iter::<SubstitutionSubtable>()
.collect();
let mut coverage = GlyphSet::builder();
let mut reverse = !subtables.is_empty();
for subtable in &subtables {
subtable.coverage().collect(&mut coverage);
reverse &= subtable.is_reverse();
}
Self {
subtables,
coverage: coverage.finish(),
reverse,
props: lookup_props(lookup),
}
}
}
// lookup_props is a 32-bit integer where the lower 16-bit is LookupFlag and
// higher 16-bit is mark-filtering-set if the lookup uses one.
// Not to be confused with glyph_props which is very similar. */
fn lookup_props(lookup: Lookup) -> u32 {
let mut props = u32::from(lookup.flags.0);
if let Some(set) = lookup.mark_filtering_set {
props |= u32::from(set) << 16;
}
props
}

View File

@@ -0,0 +1,780 @@
use ttf_parser::gpos::*;
use ttf_parser::opentype_layout::LookupIndex;
use ttf_parser::GlyphId;
use super::buffer::*;
use super::hb_font_t;
use super::ot_layout::*;
use super::ot_layout_common::{lookup_flags, PositioningLookup, PositioningTable};
use super::ot_layout_gsubgpos::{skipping_iterator_t, Apply, OT::hb_ot_apply_context_t};
use super::ot_shape_plan::hb_ot_shape_plan_t;
use crate::Direction;
pub fn position(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
apply_layout_table(plan, face, buffer, face.gpos.as_ref());
}
trait ValueRecordExt {
fn is_empty(&self) -> bool;
fn apply(&self, ctx: &mut hb_ot_apply_context_t, idx: usize) -> bool;
fn apply_to_pos(&self, ctx: &mut hb_ot_apply_context_t, pos: &mut GlyphPosition) -> bool;
}
impl ValueRecordExt for ValueRecord<'_> {
fn is_empty(&self) -> bool {
self.x_placement == 0
&& self.y_placement == 0
&& self.x_advance == 0
&& self.y_advance == 0
&& self.x_placement_device.is_none()
&& self.y_placement_device.is_none()
&& self.x_advance_device.is_none()
&& self.y_advance_device.is_none()
}
fn apply(&self, ctx: &mut hb_ot_apply_context_t, idx: usize) -> bool {
let mut pos = ctx.buffer.pos[idx];
let worked = self.apply_to_pos(ctx, &mut pos);
ctx.buffer.pos[idx] = pos;
worked
}
fn apply_to_pos(&self, ctx: &mut hb_ot_apply_context_t, pos: &mut GlyphPosition) -> bool {
let horizontal = ctx.buffer.direction.is_horizontal();
let mut worked = false;
if self.x_placement != 0 {
pos.x_offset += i32::from(self.x_placement);
worked = true;
}
if self.y_placement != 0 {
pos.y_offset += i32::from(self.y_placement);
worked = true;
}
if self.x_advance != 0 && horizontal {
pos.x_advance += i32::from(self.x_advance);
worked = true;
}
if self.y_advance != 0 && !horizontal {
// y_advance values grow downward but font-space grows upward, hence negation
pos.y_advance -= i32::from(self.y_advance);
worked = true;
}
{
let (ppem_x, ppem_y) = ctx.face.pixels_per_em().unwrap_or((0, 0));
let coords = ctx.face.ttfp_face.variation_coordinates().len();
let use_x_device = ppem_x != 0 || coords != 0;
let use_y_device = ppem_y != 0 || coords != 0;
if use_x_device {
if let Some(device) = self.x_placement_device {
pos.x_offset += device.get_x_delta(ctx.face).unwrap_or(0);
worked = true; // TODO: even when 0?
}
}
if use_y_device {
if let Some(device) = self.y_placement_device {
pos.y_offset += device.get_y_delta(ctx.face).unwrap_or(0);
worked = true;
}
}
if horizontal && use_x_device {
if let Some(device) = self.x_advance_device {
pos.x_advance += device.get_x_delta(ctx.face).unwrap_or(0);
worked = true;
}
}
if !horizontal && use_y_device {
if let Some(device) = self.y_advance_device {
// y_advance values grow downward but face-space grows upward, hence negation
pos.y_advance -= device.get_y_delta(ctx.face).unwrap_or(0);
worked = true;
}
}
}
worked
}
}
trait AnchorExt {
fn get(&self, face: &hb_font_t) -> (i32, i32);
}
impl AnchorExt for Anchor<'_> {
fn get(&self, face: &hb_font_t) -> (i32, i32) {
let mut x = i32::from(self.x);
let mut y = i32::from(self.y);
if self.x_device.is_some() || self.y_device.is_some() {
let (ppem_x, ppem_y) = face.pixels_per_em().unwrap_or((0, 0));
let coords = face.ttfp_face.variation_coordinates().len();
if let Some(device) = self.x_device {
if ppem_x != 0 || coords != 0 {
x += device.get_x_delta(face).unwrap_or(0);
}
}
if let Some(device) = self.y_device {
if ppem_y != 0 || coords != 0 {
y += device.get_y_delta(face).unwrap_or(0);
}
}
}
(x, y)
}
}
impl<'a> LayoutTable for PositioningTable<'a> {
const INDEX: TableIndex = TableIndex::GPOS;
const IN_PLACE: bool = true;
type Lookup = PositioningLookup<'a>;
fn get_lookup(&self, index: LookupIndex) -> Option<&Self::Lookup> {
self.lookups.get(usize::from(index))
}
}
impl LayoutLookup for PositioningLookup<'_> {
fn props(&self) -> u32 {
self.props
}
fn is_reverse(&self) -> bool {
false
}
fn covers(&self, glyph: GlyphId) -> bool {
self.coverage.contains(glyph)
}
}
impl Apply for PositioningLookup<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
if self.covers(ctx.buffer.cur(0).as_glyph()) {
for subtable in &self.subtables {
if subtable.apply(ctx).is_some() {
return Some(());
}
}
}
None
}
}
impl Apply for SingleAdjustment<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let glyph = ctx.buffer.cur(0).as_glyph();
let record = match self {
Self::Format1 { coverage, value } => {
coverage.get(glyph)?;
*value
}
Self::Format2 { coverage, values } => {
let index = coverage.get(glyph)?;
values.get(index)?
}
};
record.apply(ctx, ctx.buffer.idx);
ctx.buffer.idx += 1;
Some(())
}
}
impl Apply for PairAdjustment<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let first_glyph = ctx.buffer.cur(0).as_glyph();
let first_glyph_coverage_index = self.coverage().get(first_glyph)?;
let mut iter = skipping_iterator_t::new(ctx, ctx.buffer.idx, 1, false);
let mut unsafe_to = 0;
if !iter.next(Some(&mut unsafe_to)) {
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to));
return None;
}
let second_glyph_index = iter.index();
let second_glyph = ctx.buffer.info[second_glyph_index].as_glyph();
let finish = |ctx: &mut hb_ot_apply_context_t, has_record2| {
ctx.buffer.idx = second_glyph_index;
if has_record2 {
ctx.buffer.idx += 1;
}
Some(())
};
let boring = |ctx: &mut hb_ot_apply_context_t, has_record2| {
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
finish(ctx, has_record2)
};
let success = |ctx: &mut hb_ot_apply_context_t, flag1, flag2, has_record2| {
if flag1 || flag2 {
ctx.buffer
.unsafe_to_break(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
finish(ctx, has_record2)
} else {
boring(ctx, has_record2)
}
};
let bail = |ctx: &mut hb_ot_apply_context_t, records: (ValueRecord, ValueRecord)| {
let flag1 = records.0.apply(ctx, ctx.buffer.idx);
let flag2 = records.1.apply(ctx, second_glyph_index);
let has_record2 = !records.1.is_empty();
success(ctx, flag1, flag2, has_record2)
};
let records = match self {
Self::Format1 { sets, .. } => {
sets.get(first_glyph_coverage_index)?.get(second_glyph)?
}
Self::Format2 {
classes, matrix, ..
} => {
let classes = (classes.0.get(first_glyph), classes.1.get(second_glyph));
let records = match matrix.get(classes) {
Some(v) => v,
None => {
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(iter.index() + 1));
return None;
}
};
return bail(ctx, records);
}
};
bail(ctx, records)
}
}
impl Apply for CursiveAdjustment<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let this = ctx.buffer.cur(0).as_glyph();
let index_this = self.coverage.get(this)?;
let entry_this = self.sets.entry(index_this)?;
let mut iter = skipping_iterator_t::new(ctx, ctx.buffer.idx, 1, false);
let mut unsafe_from = 0;
if !iter.prev(Some(&mut unsafe_from)) {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1));
return None;
}
let i = iter.index();
let prev = ctx.buffer.info[i].as_glyph();
let index_prev = self.coverage.get(prev)?;
let Some(exit_prev) = self.sets.exit(index_prev) else {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(iter.index()), Some(ctx.buffer.idx + 1));
return None;
};
let (exit_x, exit_y) = exit_prev.get(ctx.face);
let (entry_x, entry_y) = entry_this.get(ctx.face);
let direction = ctx.buffer.direction;
let j = ctx.buffer.idx;
ctx.buffer.unsafe_to_break(Some(i), Some(j));
let pos = &mut ctx.buffer.pos;
match direction {
Direction::LeftToRight => {
pos[i].x_advance = exit_x + pos[i].x_offset;
let d = entry_x + pos[j].x_offset;
pos[j].x_advance -= d;
pos[j].x_offset -= d;
}
Direction::RightToLeft => {
let d = exit_x + pos[i].x_offset;
pos[i].x_advance -= d;
pos[i].x_offset -= d;
pos[j].x_advance = entry_x + pos[j].x_offset;
}
Direction::TopToBottom => {
pos[i].y_advance = exit_y + pos[i].y_offset;
let d = entry_y + pos[j].y_offset;
pos[j].y_advance -= d;
pos[j].y_offset -= d;
}
Direction::BottomToTop => {
let d = exit_y + pos[i].y_offset;
pos[i].y_advance -= d;
pos[i].y_offset -= d;
pos[j].y_advance = entry_y;
}
Direction::Invalid => {}
}
// Cross-direction adjustment
// We attach child to parent (think graph theory and rooted trees whereas
// the root stays on baseline and each node aligns itself against its
// parent.
//
// Optimize things for the case of RightToLeft, as that's most common in
// Arabic.
let mut child = i;
let mut parent = j;
let mut x_offset = entry_x - exit_x;
let mut y_offset = entry_y - exit_y;
// Low bits are lookup flags, so we want to truncate.
if ctx.lookup_props as u16 & lookup_flags::RIGHT_TO_LEFT == 0 {
core::mem::swap(&mut child, &mut parent);
x_offset = -x_offset;
y_offset = -y_offset;
}
// If child was already connected to someone else, walk through its old
// chain and reverse the link direction, such that the whole tree of its
// previous connection now attaches to new parent. Watch out for case
// where new parent is on the path from old chain...
reverse_cursive_minor_offset(pos, child, direction, parent);
pos[child].set_attach_type(attach_type::CURSIVE);
pos[child].set_attach_chain((parent as isize - child as isize) as i16);
ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
if direction.is_horizontal() {
pos[child].y_offset = y_offset;
} else {
pos[child].x_offset = x_offset;
}
// If parent was attached to child, separate them.
// https://github.com/harfbuzz/harfbuzz/issues/2469
if pos[parent].attach_chain() == -pos[child].attach_chain() {
pos[parent].set_attach_chain(0);
}
ctx.buffer.idx += 1;
Some(())
}
}
impl Apply for MarkToBaseAdjustment<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let buffer = &ctx.buffer;
let mark_glyph = ctx.buffer.cur(0).as_glyph();
let mark_index = self.mark_coverage.get(mark_glyph)?;
// Now we search backwards for a non-mark glyph
let mut iter = skipping_iterator_t::new(ctx, buffer.idx, 1, false);
iter.set_lookup_props(u32::from(lookup_flags::IGNORE_MARKS));
let info = &buffer.info;
loop {
let mut unsafe_from = 0;
if !iter.prev(Some(&mut unsafe_from)) {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1));
return None;
}
// We only want to attach to the first of a MultipleSubst sequence.
// https://github.com/harfbuzz/harfbuzz/issues/740
// Reject others...
// ...but stop if we find a mark in the MultipleSubst sequence:
// https://github.com/harfbuzz/harfbuzz/issues/1020
let idx = iter.index();
if !_hb_glyph_info_multiplied(&info[idx])
|| _hb_glyph_info_get_lig_comp(&info[idx]) == 0
|| idx == 0
|| _hb_glyph_info_is_mark(&info[idx - 1])
|| _hb_glyph_info_get_lig_id(&info[idx])
!= _hb_glyph_info_get_lig_id(&info[idx - 1])
|| _hb_glyph_info_get_lig_comp(&info[idx])
!= _hb_glyph_info_get_lig_comp(&info[idx - 1]) + 1
{
break;
}
iter.reject();
}
// Checking that matched glyph is actually a base glyph by GDEF is too strong; disabled
let iter_idx = iter.index();
let base_glyph = info[iter_idx].as_glyph();
let Some(base_index) = self.base_coverage.get(base_glyph) else {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1));
return None;
};
self.marks
.apply(ctx, self.anchors, mark_index, base_index, iter_idx)
}
}
impl Apply for MarkToLigatureAdjustment<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let buffer = &ctx.buffer;
let mark_glyph = ctx.buffer.cur(0).as_glyph();
let mark_index = self.mark_coverage.get(mark_glyph)?;
// Now we search backwards for a non-mark glyph
let mut iter = skipping_iterator_t::new(ctx, buffer.idx, 1, false);
iter.set_lookup_props(u32::from(lookup_flags::IGNORE_MARKS));
let mut unsafe_from = 0;
if !iter.prev(Some(&mut unsafe_from)) {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1));
return None;
}
// Checking that matched glyph is actually a ligature by GDEF is too strong; disabled
let iter_idx = iter.index();
let lig_glyph = buffer.info[iter_idx].as_glyph();
let Some(lig_index) = self.ligature_coverage.get(lig_glyph) else {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1));
return None;
};
let lig_attach = self.ligature_array.get(lig_index)?;
// Find component to attach to
let comp_count = lig_attach.rows;
if comp_count == 0 {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1));
return None;
}
// We must now check whether the ligature ID of the current mark glyph
// is identical to the ligature ID of the found ligature. If yes, we
// can directly use the component index. If not, we attach the mark
// glyph to the last component of the ligature.
let lig_id = _hb_glyph_info_get_lig_id(&buffer.info[iter_idx]);
let mark_id = _hb_glyph_info_get_lig_id(&buffer.cur(0));
let mark_comp = u16::from(_hb_glyph_info_get_lig_comp(buffer.cur(0)));
let matches = lig_id != 0 && lig_id == mark_id && mark_comp > 0;
let comp_index = if matches {
mark_comp.min(comp_count)
} else {
comp_count
} - 1;
self.marks
.apply(ctx, lig_attach, mark_index, comp_index, iter_idx)
}
}
impl Apply for MarkToMarkAdjustment<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let buffer = &ctx.buffer;
let mark1_glyph = ctx.buffer.cur(0).as_glyph();
let mark1_index = self.mark1_coverage.get(mark1_glyph)?;
// Now we search backwards for a suitable mark glyph until a non-mark glyph
let mut iter = skipping_iterator_t::new(ctx, buffer.idx, 1, false);
iter.set_lookup_props(ctx.lookup_props & !u32::from(lookup_flags::IGNORE_FLAGS));
let mut unsafe_from = 0;
if !iter.prev(Some(&mut unsafe_from)) {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1));
return None;
}
let iter_idx = iter.index();
if !_hb_glyph_info_is_mark(&buffer.info[iter_idx]) {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1));
return None;
}
let id1 = _hb_glyph_info_get_lig_id(buffer.cur(0));
let id2 = _hb_glyph_info_get_lig_id(&buffer.info[iter_idx]);
let comp1 = _hb_glyph_info_get_lig_comp(buffer.cur(0));
let comp2 = _hb_glyph_info_get_lig_comp(&buffer.info[iter_idx]);
let matches = if id1 == id2 {
// Marks belonging to the same base
// or marks belonging to the same ligature component.
id1 == 0 || comp1 == comp2
} else {
// If ligature ids don't match, it may be the case that one of the marks
// itself is a ligature. In which case match.
(id1 > 0 && comp1 == 0) || (id2 > 0 && comp2 == 0)
};
if !matches {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1));
return None;
}
let mark2_glyph = buffer.info[iter_idx].as_glyph();
let mark2_index = self.mark2_coverage.get(mark2_glyph)?;
self.marks
.apply(ctx, self.mark2_matrix, mark1_index, mark2_index, iter_idx)
}
}
trait MarkArrayExt {
fn apply(
&self,
ctx: &mut hb_ot_apply_context_t,
anchors: AnchorMatrix,
mark_index: u16,
glyph_index: u16,
glyph_pos: usize,
) -> Option<()>;
}
impl MarkArrayExt for MarkArray<'_> {
fn apply(
&self,
ctx: &mut hb_ot_apply_context_t,
anchors: AnchorMatrix,
mark_index: u16,
glyph_index: u16,
glyph_pos: usize,
) -> Option<()> {
// If this subtable doesn't have an anchor for this base and this class
// return `None` such that the subsequent subtables have a chance at it.
let (mark_class, mark_anchor) = self.get(mark_index)?;
let base_anchor = anchors.get(glyph_index, mark_class)?;
let (mark_x, mark_y) = mark_anchor.get(ctx.face);
let (base_x, base_y) = base_anchor.get(ctx.face);
ctx.buffer
.unsafe_to_break(Some(glyph_pos), Some(ctx.buffer.idx + 1));
let idx = ctx.buffer.idx;
let pos = ctx.buffer.cur_pos_mut();
pos.x_offset = base_x - mark_x;
pos.y_offset = base_y - mark_y;
pos.set_attach_type(attach_type::MARK);
pos.set_attach_chain((glyph_pos as isize - idx as isize) as i16);
ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
ctx.buffer.idx += 1;
Some(())
}
}
pub mod attach_type {
pub const MARK: u8 = 1;
pub const CURSIVE: u8 = 2;
}
/// Just like TryFrom<N>, but for numeric types not supported by the Rust's std.
pub trait TryNumFrom<T>: Sized {
/// Casts between numeric types.
fn try_num_from(_: T) -> Option<Self>;
}
impl TryNumFrom<f32> for i32 {
#[inline]
fn try_num_from(v: f32) -> Option<Self> {
// Based on https://github.com/rust-num/num-traits/blob/master/src/cast.rs
// Float as int truncates toward zero, so we want to allow values
// in the exclusive range `(MIN-1, MAX+1)`.
// We can't represent `MIN-1` exactly, but there's no fractional part
// at this magnitude, so we can just use a `MIN` inclusive boundary.
const MIN: f32 = core::i32::MIN as f32;
// We can't represent `MAX` exactly, but it will round up to exactly
// `MAX+1` (a power of two) when we cast it.
const MAX_P1: f32 = core::i32::MAX as f32;
if v >= MIN && v < MAX_P1 {
Some(v as i32)
} else {
None
}
}
}
trait DeviceExt {
fn get_x_delta(&self, face: &hb_font_t) -> Option<i32>;
fn get_y_delta(&self, face: &hb_font_t) -> Option<i32>;
}
impl DeviceExt for Device<'_> {
fn get_x_delta(&self, face: &hb_font_t) -> Option<i32> {
match self {
Device::Hinting(hinting) => hinting.x_delta(face.units_per_em, face.pixels_per_em()),
Device::Variation(variation) => face
.tables()
.gdef?
.glyph_variation_delta(
variation.outer_index,
variation.inner_index,
face.variation_coordinates(),
)
.and_then(|float| i32::try_num_from(super::round(float))),
}
}
fn get_y_delta(&self, face: &hb_font_t) -> Option<i32> {
match self {
Device::Hinting(hinting) => hinting.y_delta(face.units_per_em, face.pixels_per_em()),
Device::Variation(variation) => face
.tables()
.gdef?
.glyph_variation_delta(
variation.outer_index,
variation.inner_index,
face.variation_coordinates(),
)
.and_then(|float| i32::try_num_from(super::round(float))),
}
}
}
impl Apply for PositioningSubtable<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
match self {
Self::Single(t) => t.apply(ctx),
Self::Pair(t) => t.apply(ctx),
Self::Cursive(t) => t.apply(ctx),
Self::MarkToBase(t) => t.apply(ctx),
Self::MarkToLigature(t) => t.apply(ctx),
Self::MarkToMark(t) => t.apply(ctx),
Self::Context(t) => t.apply(ctx),
Self::ChainContext(t) => t.apply(ctx),
}
}
}
fn reverse_cursive_minor_offset(
pos: &mut [GlyphPosition],
i: usize,
direction: Direction,
new_parent: usize,
) {
let chain = pos[i].attach_chain();
let attach_type = pos[i].attach_type();
if chain == 0 || attach_type & attach_type::CURSIVE == 0 {
return;
}
pos[i].set_attach_chain(0);
// Stop if we see new parent in the chain.
let j = (i as isize + isize::from(chain)) as _;
if j == new_parent {
return;
}
reverse_cursive_minor_offset(pos, j, direction, new_parent);
if direction.is_horizontal() {
pos[j].y_offset = -pos[i].y_offset;
} else {
pos[j].x_offset = -pos[i].x_offset;
}
pos[j].set_attach_chain(-chain);
pos[j].set_attach_type(attach_type);
}
fn propagate_attachment_offsets(
pos: &mut [GlyphPosition],
len: usize,
i: usize,
direction: Direction,
) {
// Adjusts offsets of attached glyphs (both cursive and mark) to accumulate
// offset of glyph they are attached to.
let chain = pos[i].attach_chain();
let kind = pos[i].attach_type();
if chain == 0 {
return;
}
pos[i].set_attach_chain(0);
let j = (i as isize + isize::from(chain)) as _;
if j >= len {
return;
}
propagate_attachment_offsets(pos, len, j, direction);
match kind {
attach_type::MARK => {
pos[i].x_offset += pos[j].x_offset;
pos[i].y_offset += pos[j].y_offset;
assert!(j < i);
if direction.is_forward() {
for k in j..i {
pos[i].x_offset -= pos[k].x_advance;
pos[i].y_offset -= pos[k].y_advance;
}
} else {
for k in j + 1..i + 1 {
pos[i].x_offset += pos[k].x_advance;
pos[i].y_offset += pos[k].y_advance;
}
}
}
attach_type::CURSIVE => {
if direction.is_horizontal() {
pos[i].y_offset += pos[j].y_offset;
} else {
pos[i].x_offset += pos[j].x_offset;
}
}
_ => {}
}
}
pub mod GPOS {
use super::*;
pub fn position_start(_: &hb_font_t, buffer: &mut hb_buffer_t) {
let len = buffer.len;
for pos in &mut buffer.pos[..len] {
pos.set_attach_chain(0);
pos.set_attach_type(0);
}
}
pub fn position_finish_advances(_: &hb_font_t, _: &mut hb_buffer_t) {}
pub fn position_finish_offsets(_: &hb_font_t, buffer: &mut hb_buffer_t) {
let len = buffer.len;
let direction = buffer.direction;
// Handle attachments
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT != 0 {
for i in 0..len {
propagate_attachment_offsets(&mut buffer.pos, len, i, direction);
}
}
}
}

View File

@@ -0,0 +1,92 @@
use ttf_parser::gsub::*;
use ttf_parser::opentype_layout::LookupIndex;
use ttf_parser::GlyphId;
use super::buffer::hb_buffer_t;
use super::hb_font_t;
use super::ot_layout::*;
use super::ot_layout_common::{SubstLookup, SubstitutionTable};
use super::ot_layout_gsubgpos::*;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use OT::hb_ot_apply_context_t;
pub fn substitute(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
apply_layout_table(plan, face, buffer, face.gsub.as_ref());
}
impl<'a> LayoutTable for SubstitutionTable<'a> {
const INDEX: TableIndex = TableIndex::GSUB;
const IN_PLACE: bool = false;
type Lookup = SubstLookup<'a>;
fn get_lookup(&self, index: LookupIndex) -> Option<&Self::Lookup> {
self.lookups.get(usize::from(index))
}
}
impl LayoutLookup for SubstLookup<'_> {
fn props(&self) -> u32 {
self.props
}
fn is_reverse(&self) -> bool {
self.reverse
}
fn covers(&self, glyph: GlyphId) -> bool {
self.coverage.contains(glyph)
}
}
impl WouldApply for SubstLookup<'_> {
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
self.covers(ctx.glyphs[0])
&& self
.subtables
.iter()
.any(|subtable| subtable.would_apply(ctx))
}
}
impl Apply for SubstLookup<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
if self.covers(ctx.buffer.cur(0).as_glyph()) {
for subtable in &self.subtables {
if subtable.apply(ctx).is_some() {
return Some(());
}
}
}
None
}
}
impl WouldApply for SubstitutionSubtable<'_> {
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
match self {
Self::Single(t) => t.would_apply(ctx),
Self::Multiple(t) => t.would_apply(ctx),
Self::Alternate(t) => t.would_apply(ctx),
Self::Ligature(t) => t.would_apply(ctx),
Self::Context(t) => t.would_apply(ctx),
Self::ChainContext(t) => t.would_apply(ctx),
Self::ReverseChainSingle(t) => t.would_apply(ctx),
}
}
}
impl Apply for SubstitutionSubtable<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
match self {
Self::Single(t) => t.apply(ctx),
Self::Multiple(t) => t.apply(ctx),
Self::Alternate(t) => t.apply(ctx),
Self::Ligature(t) => t.apply(ctx),
Self::Context(t) => t.apply(ctx),
Self::ChainContext(t) => t.apply(ctx),
Self::ReverseChainSingle(t) => t.apply(ctx),
}
}
}

File diff suppressed because it is too large Load Diff

574
vendor/rustybuzz/src/hb/ot_map.rs vendored Normal file
View File

@@ -0,0 +1,574 @@
use alloc::vec::Vec;
use core::ops::Range;
use ttf_parser::opentype_layout::{
FeatureIndex, LanguageIndex, LookupIndex, ScriptIndex, VariationIndex,
};
use super::buffer::{glyph_flag, hb_buffer_t};
use super::ot_layout::{LayoutTableExt, TableIndex};
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::{hb_font_t, hb_mask_t, hb_tag_t, tag, Language, Script};
pub struct hb_ot_map_t {
found_script: [bool; 2],
chosen_script: [Option<hb_tag_t>; 2],
global_mask: hb_mask_t,
features: Vec<feature_map_t>,
lookups: [Vec<lookup_map_t>; 2],
stages: [Vec<StageMap>; 2],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct feature_map_t {
tag: hb_tag_t,
// GSUB/GPOS
index: [Option<FeatureIndex>; 2],
stage: [usize; 2],
shift: u32,
mask: hb_mask_t,
// mask for value=1, for quick access
one_mask: hb_mask_t,
auto_zwnj: bool,
auto_zwj: bool,
random: bool,
per_syllable: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct lookup_map_t {
pub index: LookupIndex,
// TODO: to bitflags
pub auto_zwnj: bool,
pub auto_zwj: bool,
pub random: bool,
pub mask: hb_mask_t,
pub per_syllable: bool,
}
#[derive(Clone, Copy)]
pub struct StageMap {
// Cumulative
pub last_lookup: usize,
pub pause_func: Option<pause_func_t>,
}
pub type pause_func_t = fn(&hb_ot_shape_plan_t, &hb_font_t, &mut hb_buffer_t);
impl hb_ot_map_t {
pub const MAX_BITS: u32 = 8;
pub const MAX_VALUE: u32 = (1 << Self::MAX_BITS) - 1;
#[inline]
pub fn found_script(&self, table_index: TableIndex) -> bool {
self.found_script[table_index]
}
#[inline]
pub fn chosen_script(&self, table_index: TableIndex) -> Option<hb_tag_t> {
self.chosen_script[table_index]
}
#[inline]
pub fn get_global_mask(&self) -> hb_mask_t {
self.global_mask
}
#[inline]
pub fn get_mask(&self, feature_tag: hb_tag_t) -> (hb_mask_t, u32) {
self.features
.binary_search_by_key(&feature_tag, |f| f.tag)
.map_or((0, 0), |idx| {
(self.features[idx].mask, self.features[idx].shift)
})
}
#[inline]
pub fn get_1_mask(&self, feature_tag: hb_tag_t) -> hb_mask_t {
self.features
.binary_search_by_key(&feature_tag, |f| f.tag)
.map_or(0, |idx| self.features[idx].one_mask)
}
#[inline]
pub fn get_feature_index(
&self,
table_index: TableIndex,
feature_tag: hb_tag_t,
) -> Option<FeatureIndex> {
self.features
.binary_search_by_key(&feature_tag, |f| f.tag)
.ok()
.and_then(|idx| self.features[idx].index[table_index])
}
#[inline]
pub fn get_feature_stage(
&self,
table_index: TableIndex,
feature_tag: hb_tag_t,
) -> Option<usize> {
self.features
.binary_search_by_key(&feature_tag, |f| f.tag)
.map(|idx| self.features[idx].stage[table_index])
.ok()
}
#[inline]
pub fn stages(&self, table_index: TableIndex) -> &[StageMap] {
&self.stages[table_index]
}
#[inline]
pub fn lookup(&self, table_index: TableIndex, index: usize) -> &lookup_map_t {
&self.lookups[table_index][index]
}
#[inline]
pub fn stage_lookups(&self, table_index: TableIndex, stage: usize) -> &[lookup_map_t] {
&self.lookups[table_index][self.stage_lookup_range(table_index, stage)]
}
#[inline]
pub fn stage_lookup_range(&self, table_index: TableIndex, stage: usize) -> Range<usize> {
let stages = &self.stages[table_index];
let lookups = &self.lookups[table_index];
let start = stage
.checked_sub(1)
.map_or(0, |prev| stages[prev].last_lookup);
let end = stages
.get(stage)
.map_or(lookups.len(), |curr| curr.last_lookup);
start..end
}
}
pub type hb_ot_map_feature_flags_t = u32;
pub const F_NONE: u32 = 0x0000;
pub const F_GLOBAL: u32 = 0x0001; /* Feature applies to all characters; results in no mask allocated for it. */
pub const F_HAS_FALLBACK: u32 = 0x0002; /* Has fallback implementation, so include mask bit even if feature not found. */
pub const F_MANUAL_ZWNJ: u32 = 0x0004; /* Don't skip over ZWNJ when matching **context**. */
pub const F_MANUAL_ZWJ: u32 = 0x0008; /* Don't skip over ZWJ when matching **input**. */
pub const F_MANUAL_JOINERS: u32 = F_MANUAL_ZWNJ | F_MANUAL_ZWJ;
pub const F_GLOBAL_MANUAL_JOINERS: u32 = F_GLOBAL | F_MANUAL_JOINERS;
pub const F_GLOBAL_HAS_FALLBACK: u32 = F_GLOBAL | F_HAS_FALLBACK;
pub const F_GLOBAL_SEARCH: u32 = 0x0010; /* If feature not found in LangSys, look for it in global feature list and pick one. */
pub const F_RANDOM: u32 = 0x0020; /* Randomly select a glyph from an AlternateSubstFormat1 subtable. */
pub const F_PER_SYLLABLE: u32 = 0x0040; /* Contain lookup application to within syllable. */
pub struct hb_ot_map_builder_t<'a> {
face: &'a hb_font_t<'a>,
found_script: [bool; 2],
script_index: [Option<ScriptIndex>; 2],
chosen_script: [Option<hb_tag_t>; 2],
lang_index: [Option<LanguageIndex>; 2],
current_stage: [usize; 2],
feature_infos: Vec<feature_info_t>,
stages: [Vec<stage_info_t>; 2],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct feature_info_t {
tag: hb_tag_t,
// sequence number, used for stable sorting only
seq: usize,
max_value: u32,
flags: hb_ot_map_feature_flags_t,
// for non-global features, what should the unset glyphs take
default_value: u32,
// GSUB/GPOS
stage: [usize; 2],
}
#[derive(Clone, Copy)]
struct stage_info_t {
index: usize,
pause_func: Option<pause_func_t>,
}
impl<'a> hb_ot_map_builder_t<'a> {
pub fn new(
face: &'a hb_font_t<'a>,
script: Option<Script>,
language: Option<&Language>,
) -> Self {
// Fetch script/language indices for GSUB/GPOS. We need these later to skip
// features not available in either table and not waste precious bits for them.
let (script_tags, lang_tags) = tag::tags_from_script_and_language(script, language);
let mut found_script = [false; 2];
let mut script_index = [None; 2];
let mut chosen_script = [None; 2];
let mut lang_index = [None; 2];
for (table_index, table) in face.layout_tables() {
if let Some((found, idx, tag)) = table.select_script(&script_tags) {
chosen_script[table_index] = Some(tag);
found_script[table_index] = found;
script_index[table_index] = Some(idx);
if let Some(idx) = table.select_script_language(idx, &lang_tags) {
lang_index[table_index] = Some(idx);
}
}
}
Self {
face,
found_script,
script_index,
chosen_script,
lang_index,
current_stage: [0, 0],
feature_infos: Vec::new(),
stages: [Vec::new(), Vec::new()],
}
}
#[inline]
pub fn chosen_script(&self, table_index: TableIndex) -> Option<hb_tag_t> {
self.chosen_script[table_index]
}
#[inline]
pub fn add_feature(&mut self, tag: hb_tag_t, flags: hb_ot_map_feature_flags_t, value: u32) {
if !tag.is_null() {
let seq = self.feature_infos.len();
self.feature_infos.push(feature_info_t {
tag,
seq,
max_value: value,
flags,
default_value: if flags & F_GLOBAL != 0 { value } else { 0 },
stage: self.current_stage,
});
}
}
#[inline]
pub fn enable_feature(&mut self, tag: hb_tag_t, flags: hb_ot_map_feature_flags_t, value: u32) {
self.add_feature(tag, flags | F_GLOBAL, value);
}
#[inline]
pub fn disable_feature(&mut self, tag: hb_tag_t) {
self.add_feature(tag, F_GLOBAL, 0);
}
#[inline]
pub fn add_gsub_pause(&mut self, pause: Option<pause_func_t>) {
self.add_pause(TableIndex::GSUB, pause);
}
#[inline]
pub fn add_gpos_pause(&mut self, pause: Option<pause_func_t>) {
self.add_pause(TableIndex::GPOS, pause);
}
fn add_pause(&mut self, table_index: TableIndex, pause: Option<pause_func_t>) {
self.stages[table_index].push(stage_info_t {
index: self.current_stage[table_index],
pause_func: pause,
});
self.current_stage[table_index] += 1;
}
const GLOBAL_BIT_MASK: hb_mask_t = glyph_flag::DEFINED + 1;
const GLOBAL_BIT_SHIFT: u32 = glyph_flag::DEFINED.count_ones();
pub fn compile(&mut self) -> hb_ot_map_t {
// We default to applying required feature in stage 0. If the required
// feature has a tag that is known to the shaper, we apply required feature
// in the stage for that tag.
let mut required_index = [None; 2];
let mut required_tag = [None; 2];
for (table_index, table) in self.face.layout_tables() {
if let Some(script) = self.script_index[table_index] {
let lang = self.lang_index[table_index];
if let Some((idx, tag)) = table.get_required_language_feature(script, lang) {
required_index[table_index] = Some(idx);
required_tag[table_index] = Some(tag);
}
}
}
let (features, required_stage, global_mask) = self.collect_feature_maps(required_tag);
self.add_gsub_pause(None);
self.add_gpos_pause(None);
let (lookups, stages) =
self.collect_lookup_stages(&features, required_index, required_stage);
hb_ot_map_t {
found_script: self.found_script,
chosen_script: self.chosen_script,
global_mask,
features,
lookups,
stages,
}
}
fn collect_feature_maps(
&mut self,
required_tag: [Option<hb_tag_t>; 2],
) -> (Vec<feature_map_t>, [usize; 2], hb_mask_t) {
let mut map_features = Vec::new();
let mut required_stage = [0; 2];
let mut global_mask = Self::GLOBAL_BIT_MASK;
let mut next_bit = Self::GLOBAL_BIT_SHIFT + 1;
// Sort features and merge duplicates.
self.dedup_feature_infos();
for info in &self.feature_infos {
let bits_needed = if info.flags & F_GLOBAL != 0 && info.max_value == 1 {
// Uses the global bit.
0
} else {
// Limit bits per feature.
let v = info.max_value;
let num_bits = 8 * core::mem::size_of_val(&v) as u32 - v.leading_zeros();
hb_ot_map_t::MAX_BITS.min(num_bits)
};
let bits_available = 8 * core::mem::size_of::<hb_mask_t>() as u32;
if info.max_value == 0 || next_bit + bits_needed > bits_available {
// Feature disabled, or not enough bits.
continue;
}
let mut found = false;
let mut feature_index = [None; 2];
for (table_index, table) in self.face.layout_tables() {
if required_tag[table_index] == Some(info.tag) {
required_stage[table_index] = info.stage[table_index];
}
if let Some(script) = self.script_index[table_index] {
let lang = self.lang_index[table_index];
if let Some(idx) = table.find_language_feature(script, lang, info.tag) {
feature_index[table_index] = Some(idx);
found = true;
}
}
}
if !found && info.flags & F_GLOBAL_SEARCH != 0 {
// hb_ot_layout_table_find_feature
for (table_index, table) in self.face.layout_tables() {
if let Some(idx) = table.features.index(info.tag) {
feature_index[table_index] = Some(idx);
found = true;
}
}
}
if !found && !info.flags & F_HAS_FALLBACK != 0 {
continue;
}
let (shift, mask) = if info.flags & F_GLOBAL != 0 && info.max_value == 1 {
// Uses the global bit
(Self::GLOBAL_BIT_SHIFT, Self::GLOBAL_BIT_MASK)
} else {
let shift = next_bit;
let mask = (1 << (next_bit + bits_needed)) - (1 << next_bit);
next_bit += bits_needed;
global_mask |= (info.default_value << shift) & mask;
(shift, mask)
};
map_features.push(feature_map_t {
tag: info.tag,
index: feature_index,
stage: info.stage,
shift,
mask,
one_mask: (1 << shift) & mask,
auto_zwnj: info.flags & F_MANUAL_ZWNJ == 0,
auto_zwj: info.flags & F_MANUAL_ZWJ == 0,
random: info.flags & F_RANDOM != 0,
per_syllable: info.flags & F_PER_SYLLABLE != 0,
});
}
(map_features, required_stage, global_mask)
}
fn dedup_feature_infos(&mut self) {
let feature_infos = &mut self.feature_infos;
if feature_infos.is_empty() {
return;
}
feature_infos.sort();
let mut j = 0;
for i in 1..feature_infos.len() {
if feature_infos[i].tag != feature_infos[j].tag {
j += 1;
feature_infos[j] = feature_infos[i];
} else {
if feature_infos[i].flags & F_GLOBAL != 0 {
feature_infos[j].flags |= F_GLOBAL;
feature_infos[j].max_value = feature_infos[i].max_value;
feature_infos[j].default_value = feature_infos[i].default_value;
} else {
if feature_infos[j].flags & F_GLOBAL != 0 {
feature_infos[j].flags ^= F_GLOBAL;
}
feature_infos[j].max_value =
feature_infos[j].max_value.max(feature_infos[i].max_value);
// Inherit default_value from j
}
let flags = feature_infos[i].flags & F_HAS_FALLBACK;
feature_infos[j].flags |= flags;
feature_infos[j].stage[0] =
feature_infos[j].stage[0].min(feature_infos[i].stage[0]);
feature_infos[j].stage[1] =
feature_infos[j].stage[1].min(feature_infos[i].stage[1]);
}
}
feature_infos.truncate(j + 1);
}
fn collect_lookup_stages(
&self,
map_features: &[feature_map_t],
required_feature_index: [Option<FeatureIndex>; 2],
required_feature_stage: [usize; 2],
) -> ([Vec<lookup_map_t>; 2], [Vec<StageMap>; 2]) {
let mut map_lookups = [Vec::new(), Vec::new()];
let mut map_stages = [Vec::new(), Vec::new()];
for table_index in TableIndex::iter() {
// Collect lookup indices for features.
let mut stage_index = 0;
let mut last_lookup = 0;
let coords = self.face.ttfp_face.variation_coordinates();
let variation_index = self
.face
.layout_table(table_index)
.and_then(|t| t.variations?.find_index(coords));
for stage in 0..self.current_stage[table_index] {
if let Some(feature_index) = required_feature_index[table_index] {
if required_feature_stage[table_index] == stage {
self.add_lookups(
&mut map_lookups[table_index],
table_index,
feature_index,
variation_index,
Self::GLOBAL_BIT_MASK,
true,
true,
false,
false,
);
}
}
for feature in map_features {
if let Some(feature_index) = feature.index[table_index] {
if feature.stage[table_index] == stage {
self.add_lookups(
&mut map_lookups[table_index],
table_index,
feature_index,
variation_index,
feature.mask,
feature.auto_zwnj,
feature.auto_zwj,
feature.random,
feature.per_syllable,
);
}
}
}
// Sort lookups and merge duplicates.
let lookups = &mut map_lookups[table_index];
let len = lookups.len();
if last_lookup < len {
lookups[last_lookup..].sort();
let mut j = last_lookup;
for i in j + 1..len {
if lookups[i].index != lookups[j].index {
j += 1;
lookups[j] = lookups[i];
} else {
lookups[j].mask |= lookups[i].mask;
lookups[j].auto_zwnj &= lookups[i].auto_zwnj;
lookups[j].auto_zwj &= lookups[i].auto_zwj;
}
}
lookups.truncate(j + 1);
}
last_lookup = lookups.len();
if let Some(info) = self.stages[table_index].get(stage_index) {
if info.index == stage {
map_stages[table_index].push(StageMap {
last_lookup,
pause_func: info.pause_func,
});
stage_index += 1;
}
}
}
}
(map_lookups, map_stages)
}
fn add_lookups(
&self,
lookups: &mut Vec<lookup_map_t>,
table_index: TableIndex,
feature_index: FeatureIndex,
variation_index: Option<VariationIndex>,
mask: hb_mask_t,
auto_zwnj: bool,
auto_zwj: bool,
random: bool,
per_syllable: bool,
) -> Option<()> {
let table = self.face.layout_table(table_index)?;
let lookup_count = table.lookups.len();
let feature = match variation_index {
Some(idx) => table
.variations
.and_then(|var| var.find_substitute(feature_index, idx))
.or_else(|| table.features.get(feature_index))?,
None => table.features.get(feature_index)?,
};
for index in feature.lookup_indices {
if index < lookup_count {
lookups.push(lookup_map_t {
mask,
index,
auto_zwnj,
auto_zwj,
random,
per_syllable,
});
}
}
Some(())
}
}

883
vendor/rustybuzz/src/hb/ot_shape.rs vendored Normal file
View File

@@ -0,0 +1,883 @@
use super::aat_map;
use super::buffer::*;
use super::ot_layout::*;
use super::ot_layout_gpos_table::GPOS;
use super::ot_map::*;
use super::ot_shape_complex::*;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::unicode::{hb_unicode_general_category_t, CharExt, GeneralCategoryExt};
use super::*;
use super::{hb_font_t, hb_tag_t};
use crate::hb::aat_layout::hb_aat_layout_remove_deleted_glyphs;
use crate::BufferFlags;
use crate::{Direction, Feature, Language, Script};
pub struct hb_ot_shape_planner_t<'a> {
pub face: &'a hb_font_t<'a>,
pub direction: Direction,
pub script: Option<Script>,
pub ot_map: hb_ot_map_builder_t<'a>,
pub aat_map: aat_map::hb_aat_map_builder_t,
pub apply_morx: bool,
pub script_zero_marks: bool,
pub script_fallback_mark_positioning: bool,
pub shaper: &'static hb_ot_complex_shaper_t,
}
impl<'a> hb_ot_shape_planner_t<'a> {
pub fn new(
face: &'a hb_font_t<'a>,
direction: Direction,
script: Option<Script>,
language: Option<&Language>,
) -> Self {
let ot_map = hb_ot_map_builder_t::new(face, script, language);
let aat_map = aat_map::hb_aat_map_builder_t::default();
let mut shaper = match script {
Some(script) => hb_ot_shape_complex_categorize(
script,
direction,
ot_map.chosen_script(TableIndex::GSUB),
),
None => &DEFAULT_SHAPER,
};
let script_zero_marks = shaper.zero_width_marks != HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE;
let script_fallback_mark_positioning = shaper.fallback_position;
// https://github.com/harfbuzz/harfbuzz/issues/2124
let apply_morx =
face.tables().morx.is_some() && (direction.is_horizontal() || face.gsub.is_none());
// https://github.com/harfbuzz/harfbuzz/issues/1528
if apply_morx && shaper as *const _ != &DEFAULT_SHAPER as *const _ {
shaper = &DUMBER_SHAPER;
}
hb_ot_shape_planner_t {
face,
direction,
script,
ot_map,
aat_map,
apply_morx,
script_zero_marks,
script_fallback_mark_positioning,
shaper,
}
}
pub fn collect_features(&mut self, user_features: &[Feature]) {
const COMMON_FEATURES: &[(hb_tag_t, hb_ot_map_feature_flags_t)] = &[
(hb_tag_t::from_bytes(b"abvm"), F_GLOBAL),
(hb_tag_t::from_bytes(b"blwm"), F_GLOBAL),
(hb_tag_t::from_bytes(b"ccmp"), F_GLOBAL),
(hb_tag_t::from_bytes(b"locl"), F_GLOBAL),
(hb_tag_t::from_bytes(b"mark"), F_GLOBAL_MANUAL_JOINERS),
(hb_tag_t::from_bytes(b"mkmk"), F_GLOBAL_MANUAL_JOINERS),
(hb_tag_t::from_bytes(b"rlig"), F_GLOBAL),
];
const HORIZONTAL_FEATURES: &[(hb_tag_t, hb_ot_map_feature_flags_t)] = &[
(hb_tag_t::from_bytes(b"calt"), F_GLOBAL),
(hb_tag_t::from_bytes(b"clig"), F_GLOBAL),
(hb_tag_t::from_bytes(b"curs"), F_GLOBAL),
(hb_tag_t::from_bytes(b"dist"), F_GLOBAL),
(hb_tag_t::from_bytes(b"kern"), F_GLOBAL_HAS_FALLBACK),
(hb_tag_t::from_bytes(b"liga"), F_GLOBAL),
(hb_tag_t::from_bytes(b"rclt"), F_GLOBAL),
];
let empty = F_NONE;
self.ot_map
.enable_feature(hb_tag_t::from_bytes(b"rvrn"), empty, 1);
self.ot_map.add_gsub_pause(None);
match self.direction {
Direction::LeftToRight => {
self.ot_map
.enable_feature(hb_tag_t::from_bytes(b"ltra"), empty, 1);
self.ot_map
.enable_feature(hb_tag_t::from_bytes(b"ltrm"), empty, 1);
}
Direction::RightToLeft => {
self.ot_map
.enable_feature(hb_tag_t::from_bytes(b"rtla"), empty, 1);
self.ot_map
.add_feature(hb_tag_t::from_bytes(b"rtlm"), empty, 1);
}
_ => {}
}
// Automatic fractions.
self.ot_map
.add_feature(hb_tag_t::from_bytes(b"frac"), empty, 1);
self.ot_map
.add_feature(hb_tag_t::from_bytes(b"numr"), empty, 1);
self.ot_map
.add_feature(hb_tag_t::from_bytes(b"dnom"), empty, 1);
// Random!
self.ot_map.enable_feature(
hb_tag_t::from_bytes(b"rand"),
F_RANDOM,
hb_ot_map_t::MAX_VALUE,
);
// Tracking. We enable dummy feature here just to allow disabling
// AAT 'trak' table using features.
// https://github.com/harfbuzz/harfbuzz/issues/1303
self.ot_map
.enable_feature(hb_tag_t::from_bytes(b"trak"), F_HAS_FALLBACK, 1);
self.ot_map
.enable_feature(hb_tag_t::from_bytes(b"Harf"), empty, 1); // Considered required.
self.ot_map
.enable_feature(hb_tag_t::from_bytes(b"HARF"), empty, 1); // Considered discretionary.
if let Some(func) = self.shaper.collect_features {
func(self);
}
self.ot_map
.enable_feature(hb_tag_t::from_bytes(b"Buzz"), empty, 1); // Considered required.
self.ot_map
.enable_feature(hb_tag_t::from_bytes(b"BUZZ"), empty, 1); // Considered discretionary.
for &(tag, flags) in COMMON_FEATURES {
self.ot_map.add_feature(tag, flags, 1);
}
if self.direction.is_horizontal() {
for &(tag, flags) in HORIZONTAL_FEATURES {
self.ot_map.add_feature(tag, flags, 1);
}
} else {
// We only apply `vert` feature. See:
// https://github.com/harfbuzz/harfbuzz/commit/d71c0df2d17f4590d5611239577a6cb532c26528
// https://lists.freedesktop.org/archives/harfbuzz/2013-August/003490.html
// We really want to find a 'vert' feature if there's any in the font, no
// matter which script/langsys it is listed (or not) under.
// See various bugs referenced from:
// https://github.com/harfbuzz/harfbuzz/issues/63
self.ot_map
.enable_feature(hb_tag_t::from_bytes(b"vert"), F_GLOBAL_SEARCH, 1);
}
for feature in user_features {
let flags = if feature.is_global() { F_GLOBAL } else { empty };
self.ot_map.add_feature(feature.tag, flags, feature.value);
}
if self.apply_morx {
for feature in user_features {
self.aat_map
.add_feature(self.face, feature.tag, feature.value);
}
}
if let Some(func) = self.shaper.override_features {
func(self);
}
}
pub fn compile(mut self, user_features: &[Feature]) -> hb_ot_shape_plan_t {
let ot_map = self.ot_map.compile();
let aat_map = if self.apply_morx {
self.aat_map.compile(self.face)
} else {
aat_map::hb_aat_map_t::default()
};
let frac_mask = ot_map.get_1_mask(hb_tag_t::from_bytes(b"frac"));
let numr_mask = ot_map.get_1_mask(hb_tag_t::from_bytes(b"numr"));
let dnom_mask = ot_map.get_1_mask(hb_tag_t::from_bytes(b"dnom"));
let has_frac = frac_mask != 0 || (numr_mask != 0 && dnom_mask != 0);
let rtlm_mask = ot_map.get_1_mask(hb_tag_t::from_bytes(b"rtlm"));
let has_vert = ot_map.get_1_mask(hb_tag_t::from_bytes(b"vert")) != 0;
let horizontal = self.direction.is_horizontal();
let kern_tag = if horizontal {
hb_tag_t::from_bytes(b"kern")
} else {
hb_tag_t::from_bytes(b"vkrn")
};
let kern_mask = ot_map.get_mask(kern_tag).0;
let requested_kerning = kern_mask != 0;
let trak_mask = ot_map.get_mask(hb_tag_t::from_bytes(b"trak")).0;
let requested_tracking = trak_mask != 0;
let has_gpos_kern = ot_map
.get_feature_index(TableIndex::GPOS, kern_tag)
.is_some();
let disable_gpos = self.shaper.gpos_tag.is_some()
&& self.shaper.gpos_tag != ot_map.chosen_script(TableIndex::GPOS);
// Decide who provides glyph classes. GDEF or Unicode.
let fallback_glyph_classes = !hb_ot_layout_has_glyph_classes(self.face);
// Decide who does substitutions. GSUB, morx, or fallback.
let apply_morx = self.apply_morx;
let mut apply_gpos = false;
let mut apply_kerx = false;
let mut apply_kern = false;
// Decide who does positioning. GPOS, kerx, kern, or fallback.
let has_kerx = self.face.tables().kerx.is_some();
let has_gsub = self.face.tables().gsub.is_some();
let has_gpos = !disable_gpos && self.face.tables().gpos.is_some();
// Prefer GPOS over kerx if GSUB is present;
// https://github.com/harfbuzz/harfbuzz/issues/3008
if has_kerx && !(has_gsub && has_gpos) {
apply_kerx = true;
} else if has_gpos {
apply_gpos = true;
}
if !apply_kerx && (!has_gpos_kern || !apply_gpos) {
if has_kerx {
apply_kerx = true;
} else if hb_ot_layout_has_kerning(self.face) {
apply_kern = true;
}
}
let apply_fallback_kern = !(apply_gpos || apply_kerx || apply_kern);
let zero_marks = self.script_zero_marks
&& !apply_kerx
&& (!apply_kern || !hb_ot_layout_has_machine_kerning(self.face));
let has_gpos_mark = ot_map.get_1_mask(hb_tag_t::from_bytes(b"mark")) != 0;
let mut adjust_mark_positioning_when_zeroing = !apply_gpos
&& !apply_kerx
&& (!apply_kern || !hb_ot_layout_has_cross_kerning(self.face));
let fallback_mark_positioning =
adjust_mark_positioning_when_zeroing && self.script_fallback_mark_positioning;
// If we're using morx shaping, we cancel mark position adjustment because
// Apple Color Emoji assumes this will NOT be done when forming emoji sequences;
// https://github.com/harfbuzz/harfbuzz/issues/2967.
if apply_morx {
adjust_mark_positioning_when_zeroing = false;
}
// Currently we always apply trak.
let apply_trak = requested_tracking && self.face.tables().trak.is_some();
let mut plan = hb_ot_shape_plan_t {
direction: self.direction,
script: self.script,
shaper: self.shaper,
ot_map,
aat_map,
data: None,
frac_mask,
numr_mask,
dnom_mask,
rtlm_mask,
kern_mask,
trak_mask,
requested_kerning,
has_frac,
has_vert,
has_gpos_mark,
zero_marks,
fallback_glyph_classes,
fallback_mark_positioning,
adjust_mark_positioning_when_zeroing,
apply_gpos,
apply_kern,
apply_fallback_kern,
apply_kerx,
apply_morx,
apply_trak,
user_features: user_features.to_vec(),
};
if let Some(func) = self.shaper.create_data {
plan.data = Some(func(&plan));
}
plan
}
}
pub struct ShapeContext<'a> {
pub plan: &'a hb_ot_shape_plan_t,
pub face: &'a hb_font_t<'a>,
pub buffer: &'a mut hb_buffer_t,
// Transient stuff
pub target_direction: Direction,
}
// Pull it all together!
pub fn shape_internal(ctx: &mut ShapeContext) {
ctx.buffer.enter();
initialize_masks(ctx);
set_unicode_props(ctx.buffer);
insert_dotted_circle(ctx.buffer, ctx.face);
form_clusters(ctx.buffer);
ensure_native_direction(ctx.buffer);
if let Some(func) = ctx.plan.shaper.preprocess_text {
func(ctx.plan, ctx.face, ctx.buffer);
}
substitute_pre(ctx);
position(ctx);
substitute_post(ctx);
propagate_flags(ctx.buffer);
ctx.buffer.direction = ctx.target_direction;
ctx.buffer.leave();
}
fn substitute_pre(ctx: &mut ShapeContext) {
hb_ot_substitute_default(ctx);
hb_ot_substitute_complex(ctx);
if ctx.plan.apply_morx && !ctx.plan.apply_gpos {
hb_aat_layout_remove_deleted_glyphs(&mut ctx.buffer);
}
}
fn substitute_post(ctx: &mut ShapeContext) {
if ctx.plan.apply_morx && ctx.plan.apply_gpos {
aat_layout::hb_aat_layout_remove_deleted_glyphs(ctx.buffer);
}
hide_default_ignorables(ctx.buffer, ctx.face);
if let Some(func) = ctx.plan.shaper.postprocess_glyphs {
func(ctx.plan, ctx.face, ctx.buffer);
}
}
fn hb_ot_substitute_default(ctx: &mut ShapeContext) {
rotate_chars(ctx);
ot_shape_normalize::_hb_ot_shape_normalize(ctx.plan, ctx.buffer, ctx.face);
setup_masks(ctx);
// This is unfortunate to go here, but necessary...
if ctx.plan.fallback_mark_positioning {
ot_shape_fallback::_hb_ot_shape_fallback_mark_position_recategorize_marks(
ctx.plan, ctx.face, ctx.buffer,
);
}
map_glyphs_fast(ctx.buffer);
}
fn hb_ot_substitute_complex(ctx: &mut ShapeContext) {
hb_ot_layout_substitute_start(ctx.face, ctx.buffer);
if ctx.plan.fallback_glyph_classes {
hb_synthesize_glyph_classes(ctx.buffer);
}
substitute_by_plan(ctx.plan, ctx.face, ctx.buffer);
}
fn substitute_by_plan(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
if plan.apply_morx {
aat_layout::hb_aat_layout_substitute(plan, face, buffer);
} else {
super::ot_layout_gsub_table::substitute(plan, face, buffer);
}
}
fn position(ctx: &mut ShapeContext) {
ctx.buffer.clear_positions();
position_default(ctx);
position_complex(ctx);
if ctx.buffer.direction.is_backward() {
ctx.buffer.reverse();
}
}
fn position_default(ctx: &mut ShapeContext) {
let len = ctx.buffer.len;
if ctx.buffer.direction.is_horizontal() {
for (info, pos) in ctx.buffer.info[..len]
.iter()
.zip(&mut ctx.buffer.pos[..len])
{
pos.x_advance = ctx.face.glyph_h_advance(info.as_glyph());
}
} else {
for (info, pos) in ctx.buffer.info[..len]
.iter()
.zip(&mut ctx.buffer.pos[..len])
{
let glyph = info.as_glyph();
pos.y_advance = ctx.face.glyph_v_advance(glyph);
pos.x_offset -= ctx.face.glyph_h_origin(glyph);
pos.y_offset -= ctx.face.glyph_v_origin(glyph);
}
}
if ctx.buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_SPACE_FALLBACK != 0 {
ot_shape_fallback::_hb_ot_shape_fallback_spaces(ctx.plan, ctx.face, ctx.buffer);
}
}
fn position_complex(ctx: &mut ShapeContext) {
// If the font has no GPOS and direction is forward, then when
// zeroing mark widths, we shift the mark with it, such that the
// mark is positioned hanging over the previous glyph. When
// direction is backward we don't shift and it will end up
// hanging over the next glyph after the final reordering.
//
// Note: If fallback positioning happens, we don't care about
// this as it will be overridden.
let adjust_offsets_when_zeroing =
ctx.plan.adjust_mark_positioning_when_zeroing && ctx.buffer.direction.is_forward();
// We change glyph origin to what GPOS expects (horizontal), apply GPOS, change it back.
GPOS::position_start(ctx.face, ctx.buffer);
if ctx.plan.zero_marks
&& ctx.plan.shaper.zero_width_marks == HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_EARLY
{
zero_mark_widths_by_gdef(ctx.buffer, adjust_offsets_when_zeroing);
}
position_by_plan(ctx.plan, ctx.face, ctx.buffer);
if ctx.plan.zero_marks
&& ctx.plan.shaper.zero_width_marks == HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE
{
zero_mark_widths_by_gdef(ctx.buffer, adjust_offsets_when_zeroing);
}
// Finish off. Has to follow a certain order.
GPOS::position_finish_advances(ctx.face, ctx.buffer);
zero_width_default_ignorables(ctx.buffer);
if ctx.plan.apply_morx {
aat_layout::hb_aat_layout_zero_width_deleted_glyphs(ctx.buffer);
}
GPOS::position_finish_offsets(ctx.face, ctx.buffer);
if ctx.plan.fallback_mark_positioning {
ot_shape_fallback::position_marks(
ctx.plan,
ctx.face,
ctx.buffer,
adjust_offsets_when_zeroing,
);
}
}
fn position_by_plan(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
if plan.apply_gpos {
super::ot_layout_gpos_table::position(plan, face, buffer);
} else if plan.apply_kerx {
aat_layout::hb_aat_layout_position(plan, face, buffer);
}
if plan.apply_kern {
super::kerning::kern(plan, face, buffer);
} else if plan.apply_fallback_kern {
ot_shape_fallback::_hb_ot_shape_fallback_kern(plan, face, buffer);
}
if plan.apply_trak {
aat_layout::hb_aat_layout_track(plan, face, buffer);
}
}
fn initialize_masks(ctx: &mut ShapeContext) {
let global_mask = ctx.plan.ot_map.get_global_mask();
ctx.buffer.reset_masks(global_mask);
}
fn setup_masks(ctx: &mut ShapeContext) {
setup_masks_fraction(ctx);
if let Some(func) = ctx.plan.shaper.setup_masks {
func(ctx.plan, ctx.face, ctx.buffer);
}
for feature in &ctx.plan.user_features {
if !feature.is_global() {
let (mask, shift) = ctx.plan.ot_map.get_mask(feature.tag);
ctx.buffer
.set_masks(feature.value << shift, mask, feature.start, feature.end);
}
}
}
fn setup_masks_fraction(ctx: &mut ShapeContext) {
let buffer = &mut ctx.buffer;
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_NON_ASCII == 0 || !ctx.plan.has_frac {
return;
}
let (pre_mask, post_mask) = if buffer.direction.is_forward() {
(
ctx.plan.numr_mask | ctx.plan.frac_mask,
ctx.plan.frac_mask | ctx.plan.dnom_mask,
)
} else {
(
ctx.plan.frac_mask | ctx.plan.dnom_mask,
ctx.plan.numr_mask | ctx.plan.frac_mask,
)
};
let len = buffer.len;
let mut i = 0;
while i < len {
// FRACTION SLASH
if buffer.info[i].glyph_id == 0x2044 {
let mut start = i;
while start > 0
&& _hb_glyph_info_get_general_category(&buffer.info[start - 1])
== hb_unicode_general_category_t::DecimalNumber
{
start -= 1;
}
let mut end = i + 1;
while end < len
&& _hb_glyph_info_get_general_category(&buffer.info[end])
== hb_unicode_general_category_t::DecimalNumber
{
end += 1;
}
buffer.unsafe_to_break(Some(start), Some(end));
for info in &mut buffer.info[start..i] {
info.mask |= pre_mask;
}
buffer.info[i].mask |= ctx.plan.frac_mask;
for info in &mut buffer.info[i + 1..end] {
info.mask |= post_mask;
}
i = end;
} else {
i += 1;
}
}
}
fn set_unicode_props(buffer: &mut hb_buffer_t) {
// Implement enough of Unicode Graphemes here that shaping
// in reverse-direction wouldn't break graphemes. Namely,
// we mark all marks and ZWJ and ZWJ,Extended_Pictographic
// sequences as continuations. The foreach_grapheme()
// macro uses this bit.
//
// https://www.unicode.org/reports/tr29/#Regex_Definitions
let len = buffer.len;
let mut i = 0;
while i < len {
// Mutably borrow buffer.info[i] and immutably borrow
// buffer.info[i - 1] (if present) in a way that the borrow
// checker can understand.
let (prior, later) = buffer.info.split_at_mut(i);
let info = &mut later[0];
info.init_unicode_props(&mut buffer.scratch_flags);
// Marks are already set as continuation by the above line.
// Handle Emoji_Modifier and ZWJ-continuation.
if _hb_glyph_info_get_general_category(info)
== hb_unicode_general_category_t::ModifierSymbol
&& matches!(info.glyph_id, 0x1F3FB..=0x1F3FF)
{
_hb_glyph_info_set_continuation(info);
} else if i != 0 && matches!(info.glyph_id, 0x1F1E6..=0x1F1FF) {
// Should never fail because we checked for i > 0.
// TODO: use let chains when they become stable
let prev = prior.last().unwrap();
if matches!(prev.glyph_id, 0x1F1E6..=0x1F1FF) && !_hb_glyph_info_is_continuation(prev) {
_hb_glyph_info_set_continuation(info);
}
} else if _hb_glyph_info_is_zwj(info) {
_hb_glyph_info_set_continuation(info);
if let Some(next) = buffer.info[..len].get_mut(i + 1) {
if next.as_char().is_emoji_extended_pictographic() {
next.init_unicode_props(&mut buffer.scratch_flags);
_hb_glyph_info_set_continuation(next);
i += 1;
}
}
} else if matches!(info.glyph_id, 0xE0020..=0xE007F) {
// Or part of the Other_Grapheme_Extend that is not marks.
// As of Unicode 11 that is just:
//
// 200C ; Other_Grapheme_Extend # Cf ZERO WIDTH NON-JOINER
// FF9E..FF9F ; Other_Grapheme_Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA
// SEMI-VOICED SOUND MARK E0020..E007F ; Other_Grapheme_Extend # Cf [96] TAG SPACE..CANCEL TAG
//
// ZWNJ is special, we don't want to merge it as there's no need, and keeping
// it separate results in more granular clusters. Ignore Katakana for now.
// Tags are used for Emoji sub-region flag sequences:
// https://github.com/harfbuzz/harfbuzz/issues/1556
_hb_glyph_info_set_continuation(info);
}
i += 1;
}
}
fn insert_dotted_circle(buffer: &mut hb_buffer_t, face: &hb_font_t) {
if !buffer
.flags
.contains(BufferFlags::DO_NOT_INSERT_DOTTED_CIRCLE)
&& buffer.flags.contains(BufferFlags::BEGINNING_OF_TEXT)
&& buffer.context_len[0] == 0
&& _hb_glyph_info_is_unicode_mark(&buffer.info[0])
&& face.has_glyph(0x25CC)
{
let mut info = hb_glyph_info_t {
glyph_id: 0x25CC,
mask: buffer.cur(0).mask,
cluster: buffer.cur(0).cluster,
..hb_glyph_info_t::default()
};
info.init_unicode_props(&mut buffer.scratch_flags);
buffer.clear_output();
buffer.output_info(info);
buffer.sync();
}
}
fn form_clusters(buffer: &mut hb_buffer_t) {
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_NON_ASCII != 0 {
if buffer.cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES {
foreach_grapheme!(buffer, start, end, { buffer.merge_clusters(start, end) });
} else {
foreach_grapheme!(buffer, start, end, {
buffer.unsafe_to_break(Some(start), Some(end));
});
}
}
}
fn ensure_native_direction(buffer: &mut hb_buffer_t) {
let dir = buffer.direction;
let mut hor = buffer
.script
.and_then(Direction::from_script)
.unwrap_or_default();
// Numeric runs in natively-RTL scripts are actually native-LTR, so we reset
// the horiz_dir if the run contains at least one decimal-number char, and no
// letter chars (ideally we should be checking for chars with strong
// directionality but hb-unicode currently lacks bidi categories).
//
// This allows digit sequences in Arabic etc to be shaped in "native"
// direction, so that features like ligatures will work as intended.
//
// https://github.com/harfbuzz/harfbuzz/issues/501
//
// Similar thing about Regional_Indicators; They are bidi=L, but Script=Common.
// If they are present in a run of natively-RTL text, they get assigned a script
// with natively RTL direction, which would result in wrong shaping if we
// assign such native RTL direction to them then. Detect that as well.
//
// https://github.com/harfbuzz/harfbuzz/issues/3314
if hor == Direction::RightToLeft && dir == Direction::LeftToRight {
let mut found_number = false;
let mut found_letter = false;
let mut found_ri = false;
for info in &buffer.info {
let gc = _hb_glyph_info_get_general_category(info);
if gc == hb_unicode_general_category_t::DecimalNumber {
found_number = true;
} else if gc.is_letter() {
found_letter = true;
break;
} else if matches!(info.glyph_id, 0x1F1E6..=0x1F1FF) {
found_ri = true;
}
}
if (found_number || found_ri) && !found_letter {
hor = Direction::LeftToRight;
}
}
// TODO vertical:
// The only BTT vertical script is Ogham, but it's not clear to me whether OpenType
// Ogham fonts are supposed to be implemented BTT or not. Need to research that
// first.
if (dir.is_horizontal() && dir != hor && hor != Direction::Invalid)
|| (dir.is_vertical() && dir != Direction::TopToBottom)
{
_hb_ot_layout_reverse_graphemes(buffer);
buffer.direction = buffer.direction.reverse();
}
}
fn rotate_chars(ctx: &mut ShapeContext) {
let len = ctx.buffer.len;
if ctx.target_direction.is_backward() {
let rtlm_mask = ctx.plan.rtlm_mask;
for info in &mut ctx.buffer.info[..len] {
if let Some(c) = info.as_char().mirrored().map(u32::from) {
if ctx.face.has_glyph(c) {
info.glyph_id = c;
continue;
}
}
info.mask |= rtlm_mask;
}
}
if ctx.target_direction.is_vertical() && !ctx.plan.has_vert {
for info in &mut ctx.buffer.info[..len] {
if let Some(c) = info.as_char().vertical().map(u32::from) {
if ctx.face.has_glyph(c) {
info.glyph_id = c;
}
}
}
}
}
fn map_glyphs_fast(buffer: &mut hb_buffer_t) {
// Normalization process sets up glyph_index(), we just copy it.
let len = buffer.len;
for info in &mut buffer.info[..len] {
info.glyph_id = info.glyph_index();
}
}
fn hb_synthesize_glyph_classes(buffer: &mut hb_buffer_t) {
let len = buffer.len;
for info in &mut buffer.info[..len] {
// Never mark default-ignorables as marks.
// They won't get in the way of lookups anyway,
// but having them as mark will cause them to be skipped
// over if the lookup-flag says so, but at least for the
// Mongolian variation selectors, looks like Uniscribe
// marks them as non-mark. Some Mongolian fonts without
// GDEF rely on this. Another notable character that
// this applies to is COMBINING GRAPHEME JOINER.
let class = if _hb_glyph_info_get_general_category(info)
!= hb_unicode_general_category_t::NonspacingMark
|| _hb_glyph_info_is_default_ignorable(info)
{
GlyphPropsFlags::BASE_GLYPH
} else {
GlyphPropsFlags::MARK
};
info.set_glyph_props(class.bits());
}
}
fn zero_width_default_ignorables(buffer: &mut hb_buffer_t) {
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES != 0
&& !buffer
.flags
.contains(BufferFlags::PRESERVE_DEFAULT_IGNORABLES)
&& !buffer
.flags
.contains(BufferFlags::REMOVE_DEFAULT_IGNORABLES)
{
let len = buffer.len;
for (info, pos) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) {
if _hb_glyph_info_is_default_ignorable(info) {
pos.x_advance = 0;
pos.y_advance = 0;
pos.x_offset = 0;
pos.y_offset = 0;
}
}
}
}
fn zero_mark_widths_by_gdef(buffer: &mut hb_buffer_t, adjust_offsets: bool) {
let len = buffer.len;
for (info, pos) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) {
if _hb_glyph_info_is_mark(info) {
if adjust_offsets {
pos.x_offset -= pos.x_advance;
pos.y_offset -= pos.y_advance;
}
pos.x_advance = 0;
pos.y_advance = 0;
}
}
}
fn hide_default_ignorables(buffer: &mut hb_buffer_t, face: &hb_font_t) {
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES != 0
&& !buffer
.flags
.contains(BufferFlags::PRESERVE_DEFAULT_IGNORABLES)
{
if !buffer
.flags
.contains(BufferFlags::REMOVE_DEFAULT_IGNORABLES)
{
if let Some(invisible) = buffer
.invisible
.or_else(|| face.get_nominal_glyph(u32::from(' ')))
{
let len = buffer.len;
for info in &mut buffer.info[..len] {
if _hb_glyph_info_is_default_ignorable(info) {
info.glyph_id = u32::from(invisible.0);
}
}
return;
}
}
buffer.delete_glyphs_inplace(_hb_glyph_info_is_default_ignorable);
}
}
fn propagate_flags(buffer: &mut hb_buffer_t) {
// Propagate cluster-level glyph flags to be the same on all cluster glyphs.
// Simplifies using them.
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_GLYPH_FLAGS != 0 {
foreach_cluster!(buffer, start, end, {
let mut mask = 0;
for info in &buffer.info[start..end] {
mask |= info.mask * glyph_flag::DEFINED;
}
if mask != 0 {
for info in &mut buffer.info[start..end] {
info.mask |= mask;
}
}
});
}
}

View File

@@ -0,0 +1,337 @@
use alloc::boxed::Box;
use core::any::Any;
use super::buffer::*;
use super::common::TagExt;
use super::ot_shape::*;
use super::ot_shape_normalize::*;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::{hb_font_t, hb_tag_t, script, Direction, Script};
impl hb_glyph_info_t {
pub(crate) fn complex_var_u8_category(&self) -> u8 {
let v: &[u8; 4] = bytemuck::cast_ref(&self.var2);
v[2]
}
pub(crate) fn set_complex_var_u8_category(&mut self, c: u8) {
let v: &mut [u8; 4] = bytemuck::cast_mut(&mut self.var2);
v[2] = c;
}
pub(crate) fn complex_var_u8_auxiliary(&self) -> u8 {
let v: &[u8; 4] = bytemuck::cast_ref(&self.var2);
v[3]
}
pub(crate) fn set_complex_var_u8_auxiliary(&mut self, c: u8) {
let v: &mut [u8; 4] = bytemuck::cast_mut(&mut self.var2);
v[3] = c;
}
}
pub const MAX_COMBINING_MARKS: usize = 32;
pub type hb_ot_shape_zero_width_marks_type_t = u32;
pub const HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE: u32 = 0;
pub const HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_EARLY: u32 = 1;
pub const HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE: u32 = 2;
pub const DEFAULT_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
collect_features: None,
override_features: None,
create_data: None,
preprocess_text: None,
postprocess_glyphs: None,
normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_AUTO,
decompose: None,
compose: None,
setup_masks: None,
gpos_tag: None,
reorder_marks: None,
zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE,
fallback_position: true,
};
pub struct hb_ot_complex_shaper_t {
/// Called during `shape_plan()`.
/// Shapers should use plan.map to add their features and callbacks.
pub collect_features: Option<fn(&mut hb_ot_shape_planner_t)>,
/// Called during `shape_plan()`.
/// Shapers should use plan.map to override features and add callbacks after
/// common features are added.
pub override_features: Option<fn(&mut hb_ot_shape_planner_t)>,
/// Called at the end of `shape_plan()`.
/// Whatever shapers return will be accessible through `plan.data()` later.
pub create_data: Option<fn(&hb_ot_shape_plan_t) -> Box<dyn Any + Send + Sync>>,
/// Called during `shape()`.
/// Shapers can use to modify text before shaping starts.
pub preprocess_text: Option<fn(&hb_ot_shape_plan_t, &hb_font_t, &mut hb_buffer_t)>,
/// Called during `shape()`.
/// Shapers can use to modify text before shaping starts.
pub postprocess_glyphs: Option<fn(&hb_ot_shape_plan_t, &hb_font_t, &mut hb_buffer_t)>,
/// How to normalize.
pub normalization_preference: hb_ot_shape_normalization_mode_t,
/// Called during `shape()`'s normalization.
pub decompose: Option<fn(&hb_ot_shape_normalize_context_t, char) -> Option<(char, char)>>,
/// Called during `shape()`'s normalization.
pub compose: Option<fn(&hb_ot_shape_normalize_context_t, char, char) -> Option<char>>,
/// Called during `shape()`.
/// Shapers should use map to get feature masks and set on buffer.
/// Shapers may NOT modify characters.
pub setup_masks: Option<fn(&hb_ot_shape_plan_t, &hb_font_t, &mut hb_buffer_t)>,
/// If not `None`, then must match found GPOS script tag for
/// GPOS to be applied. Otherwise, fallback positioning will be used.
pub gpos_tag: Option<hb_tag_t>,
/// Called during `shape()`.
/// Shapers can use to modify ordering of combining marks.
pub reorder_marks: Option<fn(&hb_ot_shape_plan_t, &mut hb_buffer_t, usize, usize)>,
/// If and when to zero-width marks.
pub zero_width_marks: hb_ot_shape_zero_width_marks_type_t,
/// Whether to use fallback mark positioning.
pub fallback_position: bool,
}
// Same as default but no mark advance zeroing / fallback positioning.
// Dumbest shaper ever, basically.
pub const DUMBER_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
collect_features: None,
override_features: None,
create_data: None,
preprocess_text: None,
postprocess_glyphs: None,
normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_AUTO,
decompose: None,
compose: None,
setup_masks: None,
gpos_tag: None,
reorder_marks: None,
zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE,
fallback_position: false,
};
pub fn hb_ot_shape_complex_categorize(
script: Script,
direction: Direction,
chosen_gsub_script: Option<hb_tag_t>,
) -> &'static hb_ot_complex_shaper_t {
match script {
// Unicode-1.1 additions
script::ARABIC
// Unicode-3.0 additions
| script::SYRIAC => {
// For Arabic script, use the Arabic shaper even if no OT script tag was found.
// This is because we do fallback shaping for Arabic script (and not others).
// But note that Arabic shaping is applicable only to horizontal layout; for
// vertical text, just use the generic shaper instead.
//
// TODO: Does this still apply? Arabic fallback shaping was removed.
if (chosen_gsub_script != Some(hb_tag_t::default_script()) || script == script::ARABIC)
&& direction.is_horizontal()
{
&crate::hb::ot_shape_complex_arabic::ARABIC_SHAPER
} else {
&DEFAULT_SHAPER
}
}
// Unicode-1.1 additions
script::THAI
| script::LAO => &crate::hb::ot_shape_complex_thai::THAI_SHAPER,
// Unicode-1.1 additions
script::HANGUL => &crate::hb::ot_shape_complex_hangul::HANGUL_SHAPER,
// Unicode-1.1 additions
script::HEBREW => &crate::hb::ot_shape_complex_hebrew::HEBREW_SHAPER,
// Unicode-1.1 additions
script::BENGALI
| script::DEVANAGARI
| script::GUJARATI
| script::GURMUKHI
| script::KANNADA
| script::MALAYALAM
| script::ORIYA
| script::TAMIL
| script::TELUGU
// Unicode-3.0 additions
| script::SINHALA => {
// If the designer designed the font for the 'DFLT' script,
// (or we ended up arbitrarily pick 'latn'), use the default shaper.
// Otherwise, use the specific shaper.
//
// If it's indy3 tag, send to USE.
if chosen_gsub_script == Some(hb_tag_t::default_script()) ||
chosen_gsub_script == Some(hb_tag_t::from_bytes(b"latn")) {
&DEFAULT_SHAPER
} else if chosen_gsub_script.map_or(false, |tag| tag.to_bytes()[3] == b'3') {
&crate::hb::ot_shape_complex_use::UNIVERSAL_SHAPER
} else {
&crate::hb::ot_shape_complex_indic::INDIC_SHAPER
}
}
script::KHMER => &crate::hb::ot_shape_complex_khmer::KHMER_SHAPER,
script::MYANMAR => {
// If the designer designed the font for the 'DFLT' script,
// (or we ended up arbitrarily pick 'latn'), use the default shaper.
// Otherwise, use the specific shaper.
//
// If designer designed for 'mymr' tag, also send to default
// shaper. That's tag used from before Myanmar shaping spec
// was developed. The shaping spec uses 'mym2' tag.
if chosen_gsub_script == Some(hb_tag_t::default_script()) ||
chosen_gsub_script == Some(hb_tag_t::from_bytes(b"latn")) ||
chosen_gsub_script == Some(hb_tag_t::from_bytes(b"mymr"))
{
&DEFAULT_SHAPER
} else {
&crate::hb::ot_shape_complex_myanmar::MYANMAR_SHAPER
}
}
// https://github.com/harfbuzz/harfbuzz/issues/1162
script::MYANMAR_ZAWGYI => &crate::hb::ot_shape_complex_myanmar::MYANMAR_ZAWGYI_SHAPER,
// Unicode-2.0 additions
script::TIBETAN
// Unicode-3.0 additions
| script::MONGOLIAN
// | script::SINHALA
// Unicode-3.2 additions
| script::BUHID
| script::HANUNOO
| script::TAGALOG
| script::TAGBANWA
// Unicode-4.0 additions
| script::LIMBU
| script::TAI_LE
// Unicode-4.1 additions
| script::BUGINESE
| script::KHAROSHTHI
| script::SYLOTI_NAGRI
| script::TIFINAGH
// Unicode-5.0 additions
| script::BALINESE
| script::NKO
| script::PHAGS_PA
// Unicode-5.1 additions
| script::CHAM
| script::KAYAH_LI
| script::LEPCHA
| script::REJANG
| script::SAURASHTRA
| script::SUNDANESE
// Unicode-5.2 additions
| script::EGYPTIAN_HIEROGLYPHS
| script::JAVANESE
| script::KAITHI
| script::MEETEI_MAYEK
| script::TAI_THAM
| script::TAI_VIET
// Unicode-6.0 additions
| script::BATAK
| script::BRAHMI
| script::MANDAIC
// Unicode-6.1 additions
| script::CHAKMA
| script::MIAO
| script::SHARADA
| script::TAKRI
// Unicode-7.0 additions
| script::DUPLOYAN
| script::GRANTHA
| script::KHOJKI
| script::KHUDAWADI
| script::MAHAJANI
| script::MANICHAEAN
| script::MODI
| script::PAHAWH_HMONG
| script::PSALTER_PAHLAVI
| script::SIDDHAM
| script::TIRHUTA
// Unicode-8.0 additions
| script::AHOM
| script::MULTANI
// Unicode-9.0 additions
| script::ADLAM
| script::BHAIKSUKI
| script::MARCHEN
| script::NEWA
// Unicode-10.0 additions
| script::MASARAM_GONDI
| script::SOYOMBO
| script::ZANABAZAR_SQUARE
// Unicode-11.0 additions
| script::DOGRA
| script::GUNJALA_GONDI
| script::HANIFI_ROHINGYA
| script::MAKASAR
| script::MEDEFAIDRIN
| script::OLD_SOGDIAN
| script::SOGDIAN
// Unicode-12.0 additions
| script::ELYMAIC
| script::NANDINAGARI
| script::NYIAKENG_PUACHUE_HMONG
| script::WANCHO
// Unicode-13.0 additions
| script::CHORASMIAN
| script::DIVES_AKURU
| script::KHITAN_SMALL_SCRIPT
| script::YEZIDI
// Unicode-14.0 additions
| script::CYPRO_MINOAN
| script::OLD_UYGHUR
| script::TANGSA
| script::TOTO
| script::VITHKUQI => {
// If the designer designed the font for the 'DFLT' script,
// (or we ended up arbitrarily pick 'latn'), use the default shaper.
// Otherwise, use the specific shaper.
// Note that for some simple scripts, there may not be *any*
// GSUB/GPOS needed, so there may be no scripts found!
if chosen_gsub_script == Some(hb_tag_t::default_script()) ||
chosen_gsub_script == Some(hb_tag_t::from_bytes(b"latn")) {
&DEFAULT_SHAPER
} else {
&crate::hb::ot_shape_complex_use::UNIVERSAL_SHAPER
}
}
_ => &DEFAULT_SHAPER
}
}

View File

@@ -0,0 +1,673 @@
use alloc::boxed::Box;
use super::algs::*;
use super::buffer::*;
use super::ot_layout::*;
use super::ot_map::*;
use super::ot_shape::*;
use super::ot_shape_complex::*;
use super::ot_shape_normalize::HB_OT_SHAPE_NORMALIZATION_MODE_AUTO;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::unicode::*;
use super::{hb_font_t, hb_glyph_info_t, hb_mask_t, hb_tag_t, script, Script};
const HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH: hb_buffer_scratch_flags_t =
HB_BUFFER_SCRATCH_FLAG_COMPLEX0;
// See:
// https://github.com/harfbuzz/harfbuzz/commit/6e6f82b6f3dde0fc6c3c7d991d9ec6cfff57823d#commitcomment-14248516
fn is_word_category(gc: hb_unicode_general_category_t) -> bool {
(rb_flag_unsafe(gc.to_rb())
& (rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_UNASSIGNED)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_SPACING_MARK)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL)))
!= 0
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
pub enum hb_arabic_joining_type_t {
U = 0,
L = 1,
R = 2,
D = 3,
// We don't have C, like harfbuzz, because Rust doesn't allow duplicated enum variants.
GroupAlaph = 4,
GroupDalathRish = 5,
T = 7,
X = 8, // means: use general-category to choose between U or T.
}
fn get_joining_type(u: char, gc: hb_unicode_general_category_t) -> hb_arabic_joining_type_t {
let j_type = super::ot_shape_complex_arabic_table::joining_type(u);
if j_type != hb_arabic_joining_type_t::X {
return j_type;
}
let ok = rb_flag_unsafe(gc.to_rb())
& (rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_FORMAT));
if ok != 0 {
hb_arabic_joining_type_t::T
} else {
hb_arabic_joining_type_t::U
}
}
fn feature_is_syriac(tag: hb_tag_t) -> bool {
matches!(tag.to_bytes()[3], b'2' | b'3')
}
const ARABIC_FEATURES: &[hb_tag_t] = &[
hb_tag_t::from_bytes(b"isol"),
hb_tag_t::from_bytes(b"fina"),
hb_tag_t::from_bytes(b"fin2"),
hb_tag_t::from_bytes(b"fin3"),
hb_tag_t::from_bytes(b"medi"),
hb_tag_t::from_bytes(b"med2"),
hb_tag_t::from_bytes(b"init"),
];
mod arabic_action_t {
pub const ISOL: u8 = 0;
pub const FINA: u8 = 1;
pub const FIN2: u8 = 2;
pub const FIN3: u8 = 3;
pub const MEDI: u8 = 4;
pub const MED2: u8 = 5;
pub const INIT: u8 = 6;
pub const NONE: u8 = 7;
// We abuse the same byte for other things...
pub const STRETCHING_FIXED: u8 = 8;
pub const STRETCHING_REPEATING: u8 = 9;
#[inline]
pub fn is_stch(n: u8) -> bool {
matches!(n, STRETCHING_FIXED | STRETCHING_REPEATING)
}
}
const STATE_TABLE: &[[(u8, u8, u16); 6]] = &[
// jt_U, jt_L, jt_R,
// jt_D, jg_ALAPH, jg_DALATH_RISH
// State 0: prev was U, not willing to join.
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::NONE, arabic_action_t::ISOL, 1),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::NONE, arabic_action_t::ISOL, 1),
(arabic_action_t::NONE, arabic_action_t::ISOL, 6),
],
// State 1: prev was R or action::ISOL/ALAPH, not willing to join.
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::NONE, arabic_action_t::ISOL, 1),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::NONE, arabic_action_t::FIN2, 5),
(arabic_action_t::NONE, arabic_action_t::ISOL, 6),
],
// State 2: prev was D/L in action::ISOL form, willing to join.
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::INIT, arabic_action_t::FINA, 1),
(arabic_action_t::INIT, arabic_action_t::FINA, 3),
(arabic_action_t::INIT, arabic_action_t::FINA, 4),
(arabic_action_t::INIT, arabic_action_t::FINA, 6),
],
// State 3: prev was D in action::FINA form, willing to join.
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::MEDI, arabic_action_t::FINA, 1),
(arabic_action_t::MEDI, arabic_action_t::FINA, 3),
(arabic_action_t::MEDI, arabic_action_t::FINA, 4),
(arabic_action_t::MEDI, arabic_action_t::FINA, 6),
],
// State 4: prev was action::FINA ALAPH, not willing to join.
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::MED2, arabic_action_t::ISOL, 1),
(arabic_action_t::MED2, arabic_action_t::ISOL, 2),
(arabic_action_t::MED2, arabic_action_t::FIN2, 5),
(arabic_action_t::MED2, arabic_action_t::ISOL, 6),
],
// State 5: prev was FIN2/FIN3 ALAPH, not willing to join.
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::ISOL, arabic_action_t::ISOL, 1),
(arabic_action_t::ISOL, arabic_action_t::ISOL, 2),
(arabic_action_t::ISOL, arabic_action_t::FIN2, 5),
(arabic_action_t::ISOL, arabic_action_t::ISOL, 6),
],
// State 6: prev was DALATH/RISH, not willing to join.
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::NONE, arabic_action_t::ISOL, 1),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::NONE, arabic_action_t::FIN3, 5),
(arabic_action_t::NONE, arabic_action_t::ISOL, 6),
],
];
impl hb_glyph_info_t {
fn arabic_shaping_action(&self) -> u8 {
self.complex_var_u8_auxiliary()
}
fn set_arabic_shaping_action(&mut self, action: u8) {
self.set_complex_var_u8_auxiliary(action)
}
}
fn collect_features(planner: &mut hb_ot_shape_planner_t) {
// We apply features according to the Arabic spec, with pauses
// in between most.
//
// The pause between init/medi/... and rlig is required. See eg:
// https://bugzilla.mozilla.org/show_bug.cgi?id=644184
//
// The pauses between init/medi/... themselves are not necessarily
// needed as only one of those features is applied to any character.
// The only difference it makes is when fonts have contextual
// substitutions. We now follow the order of the spec, which makes
// for better experience if that's what Uniscribe is doing.
//
// At least for Arabic, looks like Uniscribe has a pause between
// rlig and calt. Otherwise the IranNastaliq's ALLAH ligature won't
// work. However, testing shows that rlig and calt are applied
// together for Mongolian in Uniscribe. As such, we only add a
// pause for Arabic, not other scripts.
//
// A pause after calt is required to make KFGQPC Uthmanic Script HAFS
// work correctly. See https://github.com/harfbuzz/harfbuzz/issues/505
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"stch"), F_NONE, 1);
planner.ot_map.add_gsub_pause(Some(record_stch));
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"ccmp"), F_NONE, 1);
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"locl"), F_NONE, 1);
planner.ot_map.add_gsub_pause(None);
for feature in ARABIC_FEATURES {
let has_fallback = planner.script == Some(script::ARABIC) && !feature_is_syriac(*feature);
let flags = if has_fallback { F_HAS_FALLBACK } else { F_NONE };
planner.ot_map.add_feature(*feature, flags, 1);
planner.ot_map.add_gsub_pause(None);
}
// Normally, Unicode says a ZWNJ means "don't ligate". In Arabic script
// however, it says a ZWJ should also mean "don't ligate". So we run
// the main ligating features as MANUAL_ZWJ.
planner.ot_map.enable_feature(
hb_tag_t::from_bytes(b"rlig"),
F_MANUAL_ZWJ | F_HAS_FALLBACK,
1,
);
if planner.script == Some(script::ARABIC) {
planner.ot_map.add_gsub_pause(Some(arabic_fallback_shape));
}
// No pause after rclt.
// See 98460779bae19e4d64d29461ff154b3527bf8420
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"rclt"), F_MANUAL_ZWJ, 1);
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"calt"), F_MANUAL_ZWJ, 1);
planner.ot_map.add_gsub_pause(None);
// The spec includes 'cswh'. Earlier versions of Windows
// used to enable this by default, but testing suggests
// that Windows 8 and later do not enable it by default,
// and spec now says 'Off by default'.
// We disabled this in ae23c24c32.
// Note that IranNastaliq uses this feature extensively
// to fixup broken glyph sequences. Oh well...
// Test case: U+0643,U+0640,U+0631.
// planner.ot_map.enable_feature(feature::CONTEXTUAL_SWASH, F_NONE, 1);
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"mset"), F_NONE, 1);
}
pub struct arabic_shape_plan_t {
// The "+ 1" in the next array is to accommodate for the "NONE" command,
// which is not an OpenType feature, but this simplifies the code by not
// having to do a "if (... < NONE) ..." and just rely on the fact that
// mask_array[NONE] == 0.
mask_array: [hb_mask_t; ARABIC_FEATURES.len() + 1],
has_stch: bool,
}
pub fn data_create_arabic(plan: &hb_ot_shape_plan_t) -> arabic_shape_plan_t {
let has_stch = plan.ot_map.get_1_mask(hb_tag_t::from_bytes(b"stch")) != 0;
let mut mask_array = [0; ARABIC_FEATURES.len() + 1];
for i in 0..ARABIC_FEATURES.len() {
mask_array[i] = plan.ot_map.get_1_mask(ARABIC_FEATURES[i]);
}
arabic_shape_plan_t {
mask_array,
has_stch,
}
}
fn arabic_joining(buffer: &mut hb_buffer_t) {
let mut prev: Option<usize> = None;
let mut state = 0;
// Check pre-context.
for i in 0..buffer.context_len[0] {
let c = buffer.context[0][i];
let this_type = get_joining_type(c, c.general_category());
if this_type == hb_arabic_joining_type_t::T {
continue;
}
state = STATE_TABLE[state][this_type as usize].2 as usize;
break;
}
for i in 0..buffer.len {
let this_type = get_joining_type(
buffer.info[i].as_char(),
_hb_glyph_info_get_general_category(&buffer.info[i]),
);
if this_type == hb_arabic_joining_type_t::T {
buffer.info[i].set_arabic_shaping_action(arabic_action_t::NONE);
continue;
}
let entry = &STATE_TABLE[state][this_type as usize];
if entry.0 != arabic_action_t::NONE && prev.is_some() {
if let Some(prev) = prev {
buffer.info[prev].set_arabic_shaping_action(entry.0);
buffer.unsafe_to_break(Some(prev), Some(i + 1));
}
}
// States that have a possible prev_action.
else {
if let Some(prev) = prev {
if this_type >= hb_arabic_joining_type_t::R || (2 <= state && state <= 5) {
buffer.unsafe_to_concat(Some(prev), Some(i + 1));
}
} else {
if this_type >= hb_arabic_joining_type_t::R {
buffer.unsafe_to_concat_from_outbuffer(Some(0), Some(i + 1));
}
}
}
buffer.info[i].set_arabic_shaping_action(entry.1);
prev = Some(i);
state = entry.2 as usize;
}
for i in 0..buffer.context_len[1] {
let c = buffer.context[1][i];
let this_type = get_joining_type(c, c.general_category());
if this_type == hb_arabic_joining_type_t::T {
continue;
}
let entry = &STATE_TABLE[state][this_type as usize];
if entry.0 != arabic_action_t::NONE && prev.is_some() {
if let Some(prev) = prev {
buffer.info[prev].set_arabic_shaping_action(entry.0);
buffer.unsafe_to_break(Some(prev), Some(buffer.len));
}
}
// States that have a possible prev_action.
else if 2 <= state && state <= 5 {
if let Some(prev) = prev {
buffer.unsafe_to_concat(Some(prev), Some(buffer.len));
}
}
break;
}
}
fn mongolian_variation_selectors(buffer: &mut hb_buffer_t) {
// Copy arabic_shaping_action() from base to Mongolian variation selectors.
let len = buffer.len;
let info = &mut buffer.info;
for i in 1..len {
if (0x180B..=0x180D).contains(&info[i].glyph_id) || info[i].glyph_id == 0x180F {
let a = info[i - 1].arabic_shaping_action();
info[i].set_arabic_shaping_action(a);
}
}
}
fn setup_masks_arabic_plan(plan: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
let arabic_plan = plan.data::<arabic_shape_plan_t>();
setup_masks_inner(arabic_plan, plan.script, buffer)
}
pub fn setup_masks_inner(
arabic_plan: &arabic_shape_plan_t,
script: Option<Script>,
buffer: &mut hb_buffer_t,
) {
arabic_joining(buffer);
if script == Some(script::MONGOLIAN) {
mongolian_variation_selectors(buffer);
}
for info in buffer.info_slice_mut() {
info.mask |= arabic_plan.mask_array[info.arabic_shaping_action() as usize];
}
}
fn arabic_fallback_shape(_: &hb_ot_shape_plan_t, _: &hb_font_t, _: &mut hb_buffer_t) {}
// Stretch feature: "stch".
// See example here:
// https://docs.microsoft.com/en-us/typography/script-development/syriac
// We implement this in a generic way, such that the Arabic subtending
// marks can use it as well.
fn record_stch(plan: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
let arabic_plan = plan.data::<arabic_shape_plan_t>();
if !arabic_plan.has_stch {
return;
}
// 'stch' feature was just applied. Look for anything that multiplied,
// and record it for stch treatment later. Note that rtlm, frac, etc
// are applied before stch, but we assume that they didn't result in
// anything multiplying into 5 pieces, so it's safe-ish...
let len = buffer.len;
let info = &mut buffer.info;
let mut has_stch = false;
for glyph_info in &mut info[..len] {
if _hb_glyph_info_multiplied(glyph_info) {
let comp = if _hb_glyph_info_get_lig_comp(glyph_info) % 2 != 0 {
arabic_action_t::STRETCHING_REPEATING
} else {
arabic_action_t::STRETCHING_FIXED
};
glyph_info.set_arabic_shaping_action(comp);
has_stch = true;
}
}
if has_stch {
buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH;
}
}
fn apply_stch(face: &hb_font_t, buffer: &mut hb_buffer_t) {
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH == 0 {
return;
}
// The Arabic shaper currently always processes in RTL mode, so we should
// stretch / position the stretched pieces to the left / preceding glyphs.
// We do a two pass implementation:
// First pass calculates the exact number of extra glyphs we need,
// We then enlarge buffer to have that much room,
// Second pass applies the stretch, copying things to the end of buffer.
let mut extra_glyphs_needed: usize = 0; // Set during MEASURE, used during CUT
const MEASURE: usize = 0;
const CUT: usize = 1;
for step in 0..2 {
let new_len = buffer.len + extra_glyphs_needed; // write head during CUT
let mut i = buffer.len;
let mut j = new_len;
while i != 0 {
if !arabic_action_t::is_stch(buffer.info[i - 1].arabic_shaping_action()) {
if step == CUT {
j -= 1;
buffer.info[j] = buffer.info[i - 1];
buffer.pos[j] = buffer.pos[i - 1];
}
i -= 1;
continue;
}
// Yay, justification!
let mut w_total = 0; // Total to be filled
let mut w_fixed = 0; // Sum of fixed tiles
let mut w_repeating = 0; // Sum of repeating tiles
let mut n_repeating: i32 = 0;
let end = i;
while i != 0 && arabic_action_t::is_stch(buffer.info[i - 1].arabic_shaping_action()) {
i -= 1;
let width = face.glyph_h_advance(buffer.info[i].as_glyph()) as i32;
if buffer.info[i].arabic_shaping_action() == arabic_action_t::STRETCHING_FIXED {
w_fixed += width;
} else {
w_repeating += width;
n_repeating += 1;
}
}
let start = i;
let mut context = i;
while context != 0
&& !arabic_action_t::is_stch(buffer.info[context - 1].arabic_shaping_action())
&& (_hb_glyph_info_is_default_ignorable(&buffer.info[context - 1])
|| is_word_category(_hb_glyph_info_get_general_category(
&buffer.info[context - 1],
)))
{
context -= 1;
w_total += buffer.pos[context].x_advance;
}
i += 1; // Don't touch i again.
// Number of additional times to repeat each repeating tile.
let mut n_copies: i32 = 0;
let w_remaining = w_total - w_fixed;
if w_remaining > w_repeating && w_repeating > 0 {
n_copies = w_remaining / (w_repeating) - 1;
}
// See if we can improve the fit by adding an extra repeat and squeezing them together a bit.
let mut extra_repeat_overlap = 0;
let shortfall = w_remaining - w_repeating * (n_copies + 1);
if shortfall > 0 && n_repeating > 0 {
n_copies += 1;
let excess = (n_copies + 1) * w_repeating - w_remaining;
if excess > 0 {
extra_repeat_overlap = excess / (n_copies * n_repeating);
}
}
if step == MEASURE {
extra_glyphs_needed += (n_copies * n_repeating) as usize;
} else {
buffer.unsafe_to_break(Some(context), Some(end));
let mut x_offset = 0;
for k in (start + 1..=end).rev() {
let width = face.glyph_h_advance(buffer.info[k - 1].as_glyph()) as i32;
let mut repeat = 1;
if buffer.info[k - 1].arabic_shaping_action()
== arabic_action_t::STRETCHING_REPEATING
{
repeat += n_copies;
}
for n in 0..repeat {
x_offset -= width;
if n > 0 {
x_offset += extra_repeat_overlap;
}
buffer.pos[k - 1].x_offset = x_offset;
// Append copy.
j -= 1;
buffer.info[j] = buffer.info[k - 1];
buffer.pos[j] = buffer.pos[k - 1];
}
}
}
i -= 1;
}
if step == MEASURE {
buffer.ensure(buffer.len + extra_glyphs_needed);
} else {
debug_assert_eq!(j, 0);
buffer.set_len(new_len);
}
}
}
fn postprocess_glyphs_arabic(_: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
apply_stch(face, buffer)
}
// http://www.unicode.org/reports/tr53/
const MODIFIER_COMBINING_MARKS: &[u32] = &[
0x0654, // ARABIC HAMZA ABOVE
0x0655, // ARABIC HAMZA BELOW
0x0658, // ARABIC MARK NOON GHUNNA
0x06DC, // ARABIC SMALL HIGH SEEN
0x06E3, // ARABIC SMALL LOW SEEN
0x06E7, // ARABIC SMALL HIGH YEH
0x06E8, // ARABIC SMALL HIGH NOON
0x08CA, // ARABIC SMALL HIGH FARSI YEH
0x08CB, // ARABIC SMALL HIGH YEH BARREE WITH TWO DOTS BELOW
0x08CD, // ARABIC SMALL HIGH ZAH
0x08CE, // ARABIC LARGE ROUND DOT ABOVE
0x08CF, // ARABIC LARGE ROUND DOT BELOW
0x08D3, // ARABIC SMALL LOW WAW
0x08F3, // ARABIC SMALL HIGH WAW
];
fn reorder_marks_arabic(
_: &hb_ot_shape_plan_t,
buffer: &mut hb_buffer_t,
mut start: usize,
end: usize,
) {
let mut i = start;
for cc in [220u8, 230].iter().cloned() {
while i < end && _hb_glyph_info_get_modified_combining_class(&buffer.info[i]) < cc {
i += 1;
}
if i == end {
break;
}
if _hb_glyph_info_get_modified_combining_class(&buffer.info[i]) > cc {
continue;
}
let mut j = i;
while j < end
&& _hb_glyph_info_get_modified_combining_class(&buffer.info[j]) == cc
&& MODIFIER_COMBINING_MARKS.contains(&buffer.info[j].glyph_id)
{
j += 1;
}
if i == j {
continue;
}
// Shift it!
let mut temp = [hb_glyph_info_t::default(); MAX_COMBINING_MARKS];
debug_assert!(j - i <= MAX_COMBINING_MARKS);
buffer.merge_clusters(start, j);
temp[..j - i].copy_from_slice(&buffer.info[i..j]);
for k in (0..i - start).rev() {
buffer.info[k + start + j - i] = buffer.info[k + start];
}
buffer.info[start..][..j - i].copy_from_slice(&temp[..j - i]);
// Renumber CC such that the reordered sequence is still sorted.
// 22 and 26 are chosen because they are smaller than all Arabic categories,
// and are folded back to 220/230 respectively during fallback mark positioning.
//
// We do this because the CGJ-handling logic in the normalizer relies on
// mark sequences having an increasing order even after this reordering.
// https://github.com/harfbuzz/harfbuzz/issues/554
// This, however, does break some obscure sequences, where the normalizer
// might compose a sequence that it should not. For example, in the seequence
// ALEF, HAMZAH, MADDAH, we should NOT try to compose ALEF+MADDAH, but with this
// renumbering, we will.
let new_start = start + j - i;
let new_cc = if cc == 220 {
modified_combining_class::CCC22
} else {
modified_combining_class::CCC26
};
while start < new_start {
_hb_glyph_info_set_modified_combining_class(&mut buffer.info[start], new_cc);
start += 1;
}
i = j;
}
}
pub const ARABIC_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
collect_features: Some(collect_features),
override_features: None,
create_data: Some(|plan| Box::new(data_create_arabic(plan))),
preprocess_text: None,
postprocess_glyphs: Some(postprocess_glyphs_arabic),
normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_AUTO,
decompose: None,
compose: None,
setup_masks: Some(setup_masks_arabic_plan),
gpos_tag: None,
reorder_marks: Some(reorder_marks_arabic),
zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE,
fallback_position: true,
};

View File

@@ -0,0 +1,193 @@
// WARNING: this file was generated by scripts/gen-arabic-table.py
use super::ot_shape_complex_arabic::hb_arabic_joining_type_t::{
self, GroupAlaph as A, GroupDalathRish as DR, D, L, R, T, U, X,
};
#[rustfmt::skip]
pub const JOINING_TABLE: &[hb_arabic_joining_type_t] = &[
/* Arabic */
/* 0600 */ U,U,U,U,U,U,X,X,U,X,X,U,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* 0620 */ D,U,R,R,R,R,D,R,D,R,D,D,D,D,D,R,R,R,R,D,D,D,D,D,D,D,D,D,D,D,D,D,
/* 0640 */ D,D,D,D,D,D,D,D,R,D,D,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* 0660 */ X,X,X,X,X,X,X,X,X,X,X,X,X,X,D,D,X,R,R,R,U,R,R,R,D,D,D,D,D,D,D,D,
/* 0680 */ D,D,D,D,D,D,D,D,R,R,R,R,R,R,R,R,R,R,R,R,R,R,R,R,R,R,D,D,D,D,D,D,
/* 06A0 */ D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,
/* 06C0 */ R,D,D,R,R,R,R,R,R,R,R,R,D,R,D,R,D,D,R,R,X,R,X,X,X,X,X,X,X,U,X,X,
/* 06E0 */ X,X,X,X,X,X,X,X,X,X,X,X,X,X,R,R,X,X,X,X,X,X,X,X,X,X,D,D,D,X,X,D,
/* Syriac */
/* 0700 */ X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,T,A,X,D,D,D,DR,DR,R,R,R,D,D,D,D,R,D,
/* 0720 */ D,D,D,D,D,D,D,D,R,D,DR,D,R,D,D,DR,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* 0740 */ X,X,X,X,X,X,X,X,X,X,X,X,X,R,D,D,
/* Arabic Supplement */
/* 0740 */ D,D,D,D,D,D,D,D,D,R,R,R,D,D,D,D,
/* 0760 */ D,D,D,D,D,D,D,D,D,D,D,R,R,D,D,D,D,R,D,R,R,D,D,D,R,R,D,D,D,D,D,D,
/* FILLER */
/* 0780 */ X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* 07A0 */ X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* NKo */
/* 07C0 */ X,X,X,X,X,X,X,X,X,X,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,
/* 07E0 */ D,D,D,D,D,D,D,D,D,D,D,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,D,X,X,X,X,X,
/* FILLER */
/* 0800 */ X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* 0820 */ X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* Mandaic */
/* 0840 */ R,D,D,D,D,D,R,R,D,R,D,D,D,D,D,D,D,D,D,D,R,D,R,R,R,X,X,X,X,X,X,X,
/* Syriac Supplement */
/* 0860 */ D,U,D,D,D,D,U,R,D,R,R,X,X,X,X,X,
/* Arabic Extended-B */
/* 0860 */ R,R,R,R,R,R,R,R,R,R,R,R,R,R,R,R,
/* 0880 */ R,R,R,D,D,D,D,U,U,D,D,D,D,D,R,X,U,U,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* Arabic Extended-A */
/* 08A0 */ D,D,D,D,D,D,D,D,D,D,R,R,R,U,R,D,D,R,R,D,D,D,D,D,D,R,D,D,D,D,D,D,
/* 08C0 */ D,D,D,D,D,D,D,D,D,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* 08E0 */ X,X,U,
/* Mongolian */
/* 1800 */ U,D,X,X,D,X,X,X,U,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* 1820 */ D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,
/* 1840 */ D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,
/* 1860 */ D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,X,X,X,X,X,X,X,
/* 1880 */ U,U,U,U,U,T,T,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,
/* 18A0 */ D,D,D,D,D,D,D,D,D,X,D,
/* General Punctuation */
/* 2000 */ U,D,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* 2020 */ X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,U,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* 2040 */ X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* 2060 */ X,X,X,X,X,X,U,U,U,U,
/* Phags-pa */
/* A840 */ D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,
/* A860 */ D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,L,U,
/* Manichaean */
/* 10AC0 */ D,D,D,D,D,R,U,R,U,R,R,U,U,L,R,R,R,R,R,D,D,D,D,L,D,D,D,D,D,R,D,D,
/* 10AE0 */ D,R,U,U,R,X,X,X,X,X,X,D,D,D,D,R,
/* Psalter Pahlavi */
/* 10B80 */ D,R,D,R,R,R,D,D,D,R,D,D,R,D,R,R,D,R,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* 10BA0 */ X,X,X,X,X,X,X,X,X,R,R,R,R,D,D,U,
/* Hanifi Rohingya */
/* 10D00 */ L,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,
/* 10D20 */ D,D,R,D,
/* Sogdian */
/* 10F20 */ D,D,D,R,D,D,D,D,D,D,D,D,D,D,D,D,
/* 10F40 */ D,D,D,D,D,U,X,X,X,X,X,X,X,X,X,X,X,D,D,D,R,X,X,X,X,X,X,X,X,X,X,X,
/* 10F60 */ X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* Old Uyghur */
/* 10F60 */ D,D,D,D,R,R,D,D,D,D,D,D,D,D,D,D,
/* 10F80 */ D,D,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* 10FA0 */ X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,
/* Chorasmian */
/* 10FA0 */ D,U,D,D,R,R,R,U,D,R,R,D,D,R,D,D,
/* 10FC0 */ U,D,R,R,D,U,U,U,U,R,D,L,
/* Kaithi */
/* 110A0 */ U,X,X,
/* 110C0 */ X,X,X,X,X,X,X,X,X,X,X,X,X,U,
/* Adlam */
/* 1E900 */ D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,
/* 1E920 */ D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,
/* 1E940 */ D,D,D,D,X,X,X,X,X,X,X,T,
];
const JOINING_OFFSET_0X0600: usize = 0;
const JOINING_OFFSET_0X1806: usize = 739;
const JOINING_OFFSET_0X200C: usize = 904;
const JOINING_OFFSET_0XA840: usize = 998;
const JOINING_OFFSET_0X10AC0: usize = 1050;
const JOINING_OFFSET_0X10B80: usize = 1098;
const JOINING_OFFSET_0X10D00: usize = 1146;
const JOINING_OFFSET_0X10F30: usize = 1182;
const JOINING_OFFSET_0X110BD: usize = 1338;
const JOINING_OFFSET_0X1E900: usize = 1355;
pub fn joining_type(u: char) -> hb_arabic_joining_type_t {
let u = u as u32;
match u >> 12 {
0x0 => {
if (0x0600..=0x08E2).contains(&u) {
return JOINING_TABLE[u as usize - 0x0600 + JOINING_OFFSET_0X0600];
}
}
0x1 => {
if (0x1806..=0x18AA).contains(&u) {
return JOINING_TABLE[u as usize - 0x1806 + JOINING_OFFSET_0X1806];
}
}
0x2 => {
if (0x200C..=0x2069).contains(&u) {
return JOINING_TABLE[u as usize - 0x200C + JOINING_OFFSET_0X200C];
}
}
0xA => {
if (0xA840..=0xA873).contains(&u) {
return JOINING_TABLE[u as usize - 0xA840 + JOINING_OFFSET_0XA840];
}
}
0x10 => {
if (0x10AC0..=0x10AEF).contains(&u) {
return JOINING_TABLE[u as usize - 0x10AC0 + JOINING_OFFSET_0X10AC0];
}
if (0x10B80..=0x10BAF).contains(&u) {
return JOINING_TABLE[u as usize - 0x10B80 + JOINING_OFFSET_0X10B80];
}
if (0x10D00..=0x10D23).contains(&u) {
return JOINING_TABLE[u as usize - 0x10D00 + JOINING_OFFSET_0X10D00];
}
if (0x10F30..=0x10FCB).contains(&u) {
return JOINING_TABLE[u as usize - 0x10F30 + JOINING_OFFSET_0X10F30];
}
}
0x11 => {
if (0x110BD..=0x110CD).contains(&u) {
return JOINING_TABLE[u as usize - 0x110BD + JOINING_OFFSET_0X110BD];
}
}
0x1E => {
if (0x1E900..=0x1E94B).contains(&u) {
return JOINING_TABLE[u as usize - 0x1E900 + JOINING_OFFSET_0X1E900];
}
}
_ => {}
}
X
}

View File

@@ -0,0 +1,379 @@
use alloc::boxed::Box;
use super::buffer::*;
use super::ot_map::*;
use super::ot_shape::*;
use super::ot_shape_complex::*;
use super::ot_shape_normalize::HB_OT_SHAPE_NORMALIZATION_MODE_NONE;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::*;
use crate::BufferFlags;
const LJMO: u8 = 1;
const VJMO: u8 = 2;
const TJMO: u8 = 3;
impl hb_glyph_info_t {
fn hangul_shaping_feature(&self) -> u8 {
self.complex_var_u8_auxiliary()
}
fn set_hangul_shaping_feature(&mut self, feature: u8) {
self.set_complex_var_u8_auxiliary(feature)
}
}
fn collect_features_hangul(planner: &mut hb_ot_shape_planner_t) {
planner
.ot_map
.add_feature(hb_tag_t::from_bytes(b"ljmo"), F_NONE, 1);
planner
.ot_map
.add_feature(hb_tag_t::from_bytes(b"vjmo"), F_NONE, 1);
planner
.ot_map
.add_feature(hb_tag_t::from_bytes(b"tjmo"), F_NONE, 1);
}
fn override_features_hangul(planner: &mut hb_ot_shape_planner_t) {
// Uniscribe does not apply 'calt' for Hangul, and certain fonts
// (Noto Sans CJK, Source Sans Han, etc) apply all of jamo lookups
// in calt, which is not desirable.
planner
.ot_map
.disable_feature(hb_tag_t::from_bytes(b"calt"));
}
struct hangul_shape_plan_t {
mask_array: [hb_mask_t; 4],
}
fn data_create_hangul(map: &hb_ot_map_t) -> hangul_shape_plan_t {
hangul_shape_plan_t {
mask_array: [
0,
map.get_1_mask(hb_tag_t::from_bytes(b"ljmo")),
map.get_1_mask(hb_tag_t::from_bytes(b"vjmo")),
map.get_1_mask(hb_tag_t::from_bytes(b"tjmo")),
],
}
}
const L_BASE: u32 = 0x1100;
const V_BASE: u32 = 0x1161;
const T_BASE: u32 = 0x11A7;
const L_COUNT: u32 = 19;
const V_COUNT: u32 = 21;
const T_COUNT: u32 = 28;
const N_COUNT: u32 = V_COUNT * T_COUNT;
const S_COUNT: u32 = L_COUNT * N_COUNT;
const S_BASE: u32 = 0xAC00;
fn is_combining_l(u: u32) -> bool {
(L_BASE..=L_BASE + L_COUNT - 1).contains(&u)
}
fn is_combining_v(u: u32) -> bool {
(V_BASE..=V_BASE + V_COUNT - 1).contains(&u)
}
fn is_combining_t(u: u32) -> bool {
(T_BASE + 1..=T_BASE + T_COUNT - 1).contains(&u)
}
fn is_combined_s(u: u32) -> bool {
(S_BASE..=S_BASE + S_COUNT - 1).contains(&u)
}
fn is_l(u: u32) -> bool {
(0x1100..=0x115F).contains(&u) || (0xA960..=0xA97C).contains(&u)
}
fn is_v(u: u32) -> bool {
(0x1160..=0x11A7).contains(&u) || (0xD7B0..=0xD7C6).contains(&u)
}
fn is_t(u: u32) -> bool {
(0x11A8..=0x11FF).contains(&u) || (0xD7CB..=0xD7FB).contains(&u)
}
fn is_hangul_tone(u: u32) -> bool {
(0x302E..=0x302F).contains(&u)
}
fn is_zero_width_char(face: &hb_font_t, c: char) -> bool {
if let Some(glyph) = face.get_nominal_glyph(c as u32) {
face.glyph_h_advance(glyph) == 0
} else {
false
}
}
fn preprocess_text_hangul(_: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
// Hangul syllables come in two shapes: LV, and LVT. Of those:
//
// - LV can be precomposed, or decomposed. Lets call those
// <LV> and <L,V>,
// - LVT can be fully precomposed, partially precomposed, or
// fully decomposed. Ie. <LVT>, <LV,T>, or <L,V,T>.
//
// The composition / decomposition is mechanical. However, not
// all <L,V> sequences compose, and not all <LV,T> sequences
// compose.
//
// Here are the specifics:
//
// - <L>: U+1100..115F, U+A960..A97F
// - <V>: U+1160..11A7, U+D7B0..D7C7
// - <T>: U+11A8..11FF, U+D7CB..D7FB
//
// - Only the <L,V> sequences for some of the U+11xx ranges combine.
// - Only <LV,T> sequences for some of the Ts in U+11xx range combine.
//
// Here is what we want to accomplish in this shaper:
//
// - If the whole syllable can be precomposed, do that,
// - Otherwise, fully decompose and apply ljmo/vjmo/tjmo features.
// - If a valid syllable is followed by a Hangul tone mark, reorder the tone
// mark to precede the whole syllable - unless it is a zero-width glyph, in
// which case we leave it untouched, assuming it's designed to overstrike.
//
// That is, of the different possible syllables:
//
// <L>
// <L,V>
// <L,V,T>
// <LV>
// <LVT>
// <LV, T>
//
// - <L> needs no work.
//
// - <LV> and <LVT> can stay the way they are if the font supports them, otherwise we
// should fully decompose them if font supports.
//
// - <L,V> and <L,V,T> we should compose if the whole thing can be composed.
//
// - <LV,T> we should compose if the whole thing can be composed, otherwise we should
// decompose.
buffer.clear_output();
// Extent of most recently seen syllable; valid only if start < end
let mut start = 0;
let mut end = 0;
buffer.idx = 0;
while buffer.idx < buffer.len {
let u = buffer.cur(0).glyph_id;
let c = buffer.cur(0).as_char();
if is_hangul_tone(u) {
// We could cache the width of the tone marks and the existence of dotted-circle,
// but the use of the Hangul tone mark characters seems to be rare enough that
// I didn't bother for now.
if start < end && end == buffer.out_len {
// Tone mark follows a valid syllable; move it in front, unless it's zero width.
buffer.unsafe_to_break_from_outbuffer(Some(start), Some(buffer.idx));
buffer.next_glyph();
if !is_zero_width_char(face, c) {
buffer.merge_out_clusters(start, end + 1);
let out_info = buffer.out_info_mut();
let tone = out_info[end];
for i in (0..end - start).rev() {
out_info[i + start + 1] = out_info[i + start];
}
out_info[start] = tone;
}
} else {
// No valid syllable as base for tone mark; try to insert dotted circle.
if !buffer
.flags
.contains(BufferFlags::DO_NOT_INSERT_DOTTED_CIRCLE)
&& face.has_glyph(0x25CC)
{
let mut chars = [0; 2];
if !is_zero_width_char(face, c) {
chars[0] = u;
chars[1] = 0x25CC;
} else {
chars[0] = 0x25CC;
chars[1] = u;
}
buffer.replace_glyphs(1, 2, &chars);
} else {
// No dotted circle available in the font; just leave tone mark untouched.
buffer.next_glyph();
}
}
start = buffer.out_len;
end = buffer.out_len;
continue;
}
// Remember current position as a potential syllable start;
// will only be used if we set end to a later position.
start = buffer.out_len;
if is_l(u) && buffer.idx + 1 < buffer.len {
let l = u;
let v = buffer.cur(1).glyph_id;
if is_v(v) {
// Have <L,V> or <L,V,T>.
let mut t = 0;
let mut tindex = 0;
if buffer.idx + 2 < buffer.len {
t = buffer.cur(2).glyph_id;
if is_t(t) {
// Only used if isCombiningT (t); otherwise invalid.
tindex = t - T_BASE;
} else {
// The next character was not a trailing jamo.
t = 0;
}
}
let offset = if t != 0 { 3 } else { 2 };
buffer.unsafe_to_break(Some(buffer.idx), Some(buffer.idx + offset));
// We've got a syllable <L,V,T?>; see if it can potentially be composed.
if is_combining_l(l) && is_combining_v(v) && (t == 0 || is_combining_t(t)) {
// Try to compose; if this succeeds, end is set to start+1.
let s = S_BASE + (l - L_BASE) * N_COUNT + (v - V_BASE) * T_COUNT + tindex;
if face.has_glyph(s) {
let n = if t != 0 { 3 } else { 2 };
buffer.replace_glyphs(n, 1, &[s]);
end = start + 1;
continue;
}
}
// We didn't compose, either because it's an Old Hangul syllable without a
// precomposed character in Unicode, or because the font didn't support the
// necessary precomposed glyph.
// Set jamo features on the individual glyphs, and advance past them.
buffer.cur_mut(0).set_hangul_shaping_feature(LJMO);
buffer.next_glyph();
buffer.cur_mut(0).set_hangul_shaping_feature(VJMO);
buffer.next_glyph();
if t != 0 {
buffer.cur_mut(0).set_hangul_shaping_feature(TJMO);
buffer.next_glyph();
end = start + 3;
} else {
end = start + 2;
}
if buffer.cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES {
buffer.merge_out_clusters(start, end);
}
continue;
}
} else if is_combined_s(u) {
// Have <LV>, <LVT>, or <LV,T>
let s = u;
let has_glyph = face.has_glyph(s);
let lindex = (s - S_BASE) / N_COUNT;
let nindex = (s - S_BASE) % N_COUNT;
let vindex = nindex / T_COUNT;
let tindex = nindex % T_COUNT;
if tindex == 0 && buffer.idx + 1 < buffer.len && is_combining_t(buffer.cur(1).glyph_id)
{
// <LV,T>, try to combine.
let new_tindex = buffer.cur(1).glyph_id - T_BASE;
let new_s = s + new_tindex;
if face.has_glyph(new_s) {
buffer.replace_glyphs(2, 1, &[new_s]);
end = start + 1;
continue;
} else {
// Mark unsafe between LV and T.
buffer.unsafe_to_break(Some(buffer.idx), Some(buffer.idx + 2));
}
}
// Otherwise, decompose if font doesn't support <LV> or <LVT>,
// or if having non-combining <LV,T>. Note that we already handled
// combining <LV,T> above.
if !has_glyph
|| (tindex == 0 && buffer.idx + 1 < buffer.len && is_t(buffer.cur(1).glyph_id))
{
let decomposed = [L_BASE + lindex, V_BASE + vindex, T_BASE + tindex];
if face.has_glyph(decomposed[0])
&& face.has_glyph(decomposed[1])
&& (tindex == 0 || face.has_glyph(decomposed[2]))
{
let mut s_len = if tindex != 0 { 3 } else { 2 };
buffer.replace_glyphs(1, s_len, &decomposed);
// If we decomposed an LV because of a non-combining T following,
// we want to include this T in the syllable.
if has_glyph && tindex == 0 {
buffer.next_glyph();
s_len += 1;
}
// We decomposed S: apply jamo features to the individual glyphs
// that are now in `buffer.out_info`.
end = start + s_len;
buffer.out_info_mut()[start + 0].set_hangul_shaping_feature(LJMO);
buffer.out_info_mut()[start + 1].set_hangul_shaping_feature(VJMO);
if start + 2 < end {
buffer.out_info_mut()[start + 2].set_hangul_shaping_feature(TJMO);
}
if buffer.cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES {
buffer.merge_out_clusters(start, end);
}
continue;
} else if tindex == 0 && buffer.idx + 1 > buffer.len && is_t(buffer.cur(1).glyph_id)
{
// Mark unsafe between LV and T.
buffer.unsafe_to_break(Some(buffer.idx), Some(buffer.idx + 2));
}
}
if has_glyph {
// We didn't decompose the S, so just advance past it.
end = start + 1;
buffer.next_glyph();
continue;
}
}
// Didn't find a recognizable syllable, so we leave end <= start;
// this will prevent tone-mark reordering happening.
buffer.next_glyph();
}
buffer.sync();
}
fn setup_masks_hangul(plan: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
let hangul_plan = plan.data::<hangul_shape_plan_t>();
for info in buffer.info_slice_mut() {
info.mask |= hangul_plan.mask_array[info.hangul_shaping_feature() as usize];
}
}
pub const HANGUL_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
collect_features: Some(collect_features_hangul),
override_features: Some(override_features_hangul),
create_data: Some(|plan| Box::new(data_create_hangul(&plan.ot_map))),
preprocess_text: Some(preprocess_text_hangul),
postprocess_glyphs: None,
normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_NONE,
decompose: None,
compose: None,
setup_masks: Some(setup_masks_hangul),
gpos_tag: None,
reorder_marks: None,
zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE,
fallback_position: false,
};

View File

@@ -0,0 +1,139 @@
use super::ot_shape_complex::*;
use super::ot_shape_normalize::*;
use super::{hb_tag_t, unicode};
pub const HEBREW_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
collect_features: None,
override_features: None,
create_data: None,
preprocess_text: None,
postprocess_glyphs: None,
normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_AUTO,
decompose: None,
compose: Some(compose),
setup_masks: None,
gpos_tag: Some(hb_tag_t::from_bytes(b"hebr")),
reorder_marks: None,
zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE,
fallback_position: true,
};
const S_DAGESH_FORMS: &[char] = &[
'\u{FB30}', // ALEF
'\u{FB31}', // BET
'\u{FB32}', // GIMEL
'\u{FB33}', // DALET
'\u{FB34}', // HE
'\u{FB35}', // VAV
'\u{FB36}', // ZAYIN
'\u{0000}', // HET
'\u{FB38}', // TET
'\u{FB39}', // YOD
'\u{FB3A}', // FINAL KAF
'\u{FB3B}', // KAF
'\u{FB3C}', // LAMED
'\u{0000}', // FINAL MEM
'\u{FB3E}', // MEM
'\u{0000}', // FINAL NUN
'\u{FB40}', // NUN
'\u{FB41}', // SAMEKH
'\u{0000}', // AYIN
'\u{FB43}', // FINAL PE
'\u{FB44}', // PE
'\u{0000}', // FINAL TSADI
'\u{FB46}', // TSADI
'\u{FB47}', // QOF
'\u{FB48}', // RESH
'\u{FB49}', // SHIN
'\u{FB4A}', // TAV
];
fn compose(ctx: &hb_ot_shape_normalize_context_t, a: char, b: char) -> Option<char> {
// Hebrew presentation-form shaping.
// https://bugzilla.mozilla.org/show_bug.cgi?id=728866
// Hebrew presentation forms with dagesh, for characters U+05D0..05EA;
// Note that some letters do not have a dagesh presForm encoded.
match unicode::compose(a, b) {
Some(c) => Some(c),
None if !ctx.plan.has_gpos_mark => {
// Special-case Hebrew presentation forms that are excluded from
// standard normalization, but wanted for old fonts.
let a = a as u32;
let b = b as u32;
match b {
0x05B4 => {
// HIRIQ
match a {
0x05D9 => Some('\u{FB1D}'), // YOD
_ => None,
}
}
0x05B7 => {
// patah
match a {
0x05D9 => Some('\u{FB1F}'), // YIDDISH YOD YOD
0x05D0 => Some('\u{FB2E}'), // ALEF
_ => None,
}
}
0x05B8 => {
// QAMATS
match a {
0x05D0 => Some('\u{FB2F}'), // ALEF
_ => None,
}
}
0x05B9 => {
// HOLAM
match a {
0x05D5 => Some('\u{FB4B}'), // VAV
_ => None,
}
}
0x05BC => {
// DAGESH
match a {
0x05D0..=0x05EA => {
let c = S_DAGESH_FORMS[a as usize - 0x05D0];
if c != '\0' {
Some(c)
} else {
None
}
}
0xFB2A => Some('\u{FB2C}'), // SHIN WITH SHIN DOT
0xFB2B => Some('\u{FB2D}'), // SHIN WITH SIN DOT
_ => None,
}
}
0x05BF => {
// RAFE
match a {
0x05D1 => Some('\u{FB4C}'), // BET
0x05DB => Some('\u{FB4D}'), // KAF
0x05E4 => Some('\u{FB4E}'), // PE
_ => None,
}
}
0x05C1 => {
// SHIN DOT
match a {
0x05E9 => Some('\u{FB2A}'), // SHIN
0xFB49 => Some('\u{FB2C}'), // SHIN WITH DAGESH
_ => None,
}
}
0x05C2 => {
// SIN DOT
match a {
0x05E9 => Some('\u{FB2B}'), // SHIN
0xFB49 => Some('\u{FB2D}'), // SHIN WITH DAGESH
_ => None,
}
}
_ => None,
}
}
None => None,
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,129 @@
#![allow(
dead_code,
non_upper_case_globals,
unused_assignments,
unused_parens,
while_true,
clippy::assign_op_pattern,
clippy::comparison_chain,
clippy::double_parens,
clippy::unnecessary_cast,
clippy::single_match,
clippy::never_loop
)]
use super::buffer::hb_buffer_t;
%%{
machine indic_syllable_machine;
alphtype u8;
write data;
}%%
%%{
C = 1;
V = 2;
N = 3;
H = 4;
ZWNJ = 5;
ZWJ = 6;
M = 7;
SM = 8;
A = 10;
PLACEHOLDER = 11;
DOTTEDCIRCLE = 12;
RS = 13;
Repha = 15;
Ra = 16;
CM = 17;
Symbol= 18;
CS = 19;
c = (C | Ra); # is_consonant
n = ((ZWNJ?.RS)? (N.N?)?); # is_consonant_modifier
z = ZWJ|ZWNJ; # is_joiner
reph = (Ra H | Repha); # possible reph
cn = c.ZWJ?.n?;
forced_rakar = ZWJ H ZWJ Ra;
symbol = Symbol.N?;
matra_group = z*.M.N?.(H | forced_rakar)?;
syllable_tail = (z?.SM.SM?.ZWNJ?)? A*;
halant_group = (z?.H.(ZWJ.N?)?);
final_halant_group = halant_group | H.ZWNJ;
medial_group = CM?;
halant_or_matra_group = (final_halant_group | matra_group*);
complex_syllable_tail = (halant_group.cn)* medial_group halant_or_matra_group syllable_tail;
consonant_syllable = (Repha|CS)? cn complex_syllable_tail;
vowel_syllable = reph? V.n? (ZWJ | complex_syllable_tail);
standalone_cluster = ((Repha|CS)? PLACEHOLDER | reph? DOTTEDCIRCLE).n? complex_syllable_tail;
symbol_cluster = symbol syllable_tail;
broken_cluster = reph? n? complex_syllable_tail;
other = any;
main := |*
consonant_syllable => { found_syllable!(SyllableType::ConsonantSyllable); };
vowel_syllable => { found_syllable!(SyllableType::VowelSyllable); };
standalone_cluster => { found_syllable!(SyllableType::StandaloneCluster); };
symbol_cluster => { found_syllable!(SyllableType::SymbolCluster); };
broken_cluster => { found_syllable!(SyllableType::BrokenCluster); /*buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_BROKEN_SYLLABLE;*/ };
other => { found_syllable!(SyllableType::NonIndicCluster); };
*|;
}%%
#[derive(Clone, Copy)]
pub enum SyllableType {
ConsonantSyllable = 0,
VowelSyllable,
StandaloneCluster,
SymbolCluster,
BrokenCluster,
NonIndicCluster,
}
pub fn find_syllables_indic(buffer: &mut hb_buffer_t) {
let mut cs = 0;
let mut ts = 0;
let mut te = 0;
let mut act = 0;
let mut p = 0;
let pe = buffer.len;
let eof = buffer.len;
let mut syllable_serial = 1u8;
macro_rules! found_syllable {
($kind:expr) => {{
found_syllable(ts, te, &mut syllable_serial, $kind, buffer)
}}
}
%%{
write init;
getkey (buffer.info[p].indic_category() as u8);
write exec;
}%%
}
#[inline]
fn found_syllable(
start: usize,
end: usize,
syllable_serial: &mut u8,
kind: SyllableType,
buffer: &mut hb_buffer_t,
) {
for i in start..end {
buffer.info[i].set_syllable((*syllable_serial << 4) | kind as u8);
}
*syllable_serial += 1;
if *syllable_serial == 16 {
*syllable_serial = 1;
}
}

View File

@@ -0,0 +1,458 @@
// This file is autogenerated. Do not edit it!
//
// See docs/ragel.md for details.
#![allow(
dead_code,
non_upper_case_globals,
unused_assignments,
unused_parens,
while_true,
clippy::assign_op_pattern,
clippy::comparison_chain,
clippy::double_parens,
clippy::unnecessary_cast,
clippy::single_match,
clippy::never_loop
)]
use super::buffer::hb_buffer_t;
static _indic_syllable_machine_trans_keys: [u8; 284] = [
7, 7, 3, 7, 4, 6, 4, 7, 3, 7, 5, 5, 14, 14, 3, 7, 3, 12, 3, 7, 7, 7, 4, 6, 4, 7, 3, 7, 5, 5,
14, 14, 3, 7, 3, 12, 3, 12, 3, 12, 7, 7, 4, 6, 4, 7, 3, 7, 5, 5, 14, 14, 3, 7, 3, 7, 3, 12, 7,
7, 4, 6, 4, 7, 3, 7, 5, 5, 14, 14, 3, 7, 3, 7, 4, 7, 7, 7, 0, 17, 2, 15, 2, 15, 3, 15, 0, 14,
4, 9, 4, 9, 9, 9, 4, 9, 0, 14, 0, 14, 0, 14, 2, 9, 3, 9, 4, 9, 3, 9, 4, 9, 2, 9, 4, 9, 2, 15,
2, 15, 2, 15, 2, 15, 3, 15, 0, 14, 2, 15, 2, 15, 3, 15, 0, 14, 4, 9, 9, 9, 4, 9, 0, 14, 0, 14,
2, 9, 3, 9, 4, 9, 3, 9, 4, 9, 4, 9, 2, 9, 4, 9, 2, 15, 2, 15, 3, 7, 2, 15, 2, 15, 3, 15, 0, 14,
2, 15, 0, 14, 4, 9, 9, 9, 4, 9, 0, 14, 0, 14, 2, 9, 3, 9, 4, 9, 2, 15, 3, 9, 4, 9, 4, 9, 2, 9,
4, 9, 2, 15, 3, 12, 3, 7, 2, 15, 2, 15, 3, 15, 0, 14, 2, 15, 0, 14, 4, 9, 9, 9, 4, 9, 0, 14, 0,
14, 2, 9, 3, 9, 4, 9, 2, 15, 3, 9, 4, 9, 4, 9, 2, 9, 4, 9, 0, 15, 2, 15, 0, 15, 3, 12, 4, 9, 9,
9, 4, 9, 0, 14, 2, 9, 4, 9, 4, 9, 9, 9, 4, 9, 0, 14, 0, 0,
];
static _indic_syllable_machine_char_class: [i8; 21] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 8, 13, 14, 15, 16, 17, 0, 0,
];
static _indic_syllable_machine_index_offsets: [i16; 143] = [
0, 1, 6, 9, 13, 18, 19, 20, 25, 35, 40, 41, 44, 48, 53, 54, 55, 60, 70, 80, 90, 91, 94, 98,
103, 104, 105, 110, 115, 125, 126, 129, 133, 138, 139, 140, 145, 150, 154, 155, 173, 187, 201,
214, 229, 235, 241, 242, 248, 263, 278, 293, 301, 308, 314, 321, 327, 335, 341, 355, 369, 383,
397, 410, 425, 439, 453, 466, 481, 487, 488, 494, 509, 524, 532, 539, 545, 552, 558, 564, 572,
578, 592, 606, 611, 625, 639, 652, 667, 681, 696, 702, 703, 709, 724, 739, 747, 754, 760, 774,
781, 787, 793, 801, 807, 821, 831, 836, 850, 864, 877, 892, 906, 921, 927, 928, 934, 949, 964,
972, 979, 985, 999, 1006, 1012, 1018, 1026, 1032, 1048, 1062, 1078, 1088, 1094, 1095, 1101,
1116, 1124, 1130, 1136, 1137, 1143, 0, 0,
];
static _indic_syllable_machine_indices: [i16; 1160] = [
1, 2, 3, 3, 4, 1, 3, 3, 4, 3, 3, 4, 1, 5, 3, 3, 4, 1, 6, 7, 8, 3, 3, 4, 1, 2, 3, 3, 4, 1, 0, 0,
0, 0, 9, 11, 12, 12, 13, 14, 14, 12, 12, 13, 12, 12, 13, 14, 15, 12, 12, 13, 14, 16, 17, 18,
12, 12, 13, 14, 11, 12, 12, 13, 14, 10, 10, 10, 10, 19, 11, 12, 12, 13, 14, 10, 10, 10, 10, 20,
22, 23, 23, 24, 25, 21, 21, 21, 21, 26, 25, 23, 23, 24, 23, 23, 24, 25, 28, 23, 23, 24, 25, 29,
30, 22, 23, 23, 24, 25, 31, 23, 23, 24, 25, 33, 34, 34, 35, 36, 32, 32, 32, 32, 37, 36, 34, 34,
35, 34, 34, 35, 36, 38, 34, 34, 35, 36, 39, 40, 33, 34, 34, 35, 36, 41, 34, 34, 35, 36, 23, 23,
24, 1, 43, 46, 47, 48, 49, 50, 51, 24, 25, 45, 52, 53, 53, 26, 54, 55, 56, 57, 58, 60, 61, 62,
63, 4, 1, 59, 64, 59, 59, 9, 59, 59, 65, 66, 61, 67, 67, 4, 1, 59, 64, 59, 59, 59, 59, 59, 65,
61, 67, 67, 4, 1, 59, 64, 59, 59, 59, 59, 59, 65, 46, 59, 59, 59, 68, 69, 59, 1, 59, 64, 59,
59, 59, 59, 46, 70, 70, 59, 1, 59, 64, 64, 59, 59, 71, 59, 64, 64, 64, 59, 59, 59, 59, 64, 46,
59, 72, 59, 70, 70, 59, 1, 59, 64, 59, 59, 59, 59, 46, 46, 59, 59, 59, 70, 70, 59, 1, 59, 64,
59, 59, 59, 59, 46, 46, 59, 59, 59, 70, 69, 59, 1, 59, 64, 59, 59, 59, 59, 46, 73, 7, 74, 75,
4, 1, 59, 64, 7, 74, 75, 4, 1, 59, 64, 74, 74, 4, 1, 59, 64, 76, 77, 77, 4, 1, 59, 64, 68, 78,
59, 1, 59, 64, 68, 59, 70, 70, 59, 1, 59, 64, 70, 78, 59, 1, 59, 64, 60, 61, 67, 67, 4, 1, 59,
64, 59, 59, 59, 59, 59, 65, 60, 61, 62, 67, 4, 1, 59, 64, 59, 59, 9, 59, 59, 65, 80, 81, 82,
83, 13, 14, 79, 84, 79, 79, 20, 79, 79, 85, 86, 81, 87, 83, 13, 14, 79, 84, 79, 79, 79, 79, 79,
85, 81, 87, 83, 13, 14, 79, 84, 79, 79, 79, 79, 79, 85, 88, 79, 79, 79, 89, 90, 79, 14, 79, 84,
79, 79, 79, 79, 88, 91, 81, 92, 93, 13, 14, 79, 84, 79, 79, 19, 79, 79, 85, 94, 81, 87, 87, 13,
14, 79, 84, 79, 79, 79, 79, 79, 85, 81, 87, 87, 13, 14, 79, 84, 79, 79, 79, 79, 79, 85, 88, 79,
79, 79, 95, 90, 79, 14, 79, 84, 79, 79, 79, 79, 88, 84, 79, 79, 96, 79, 84, 84, 84, 79, 79, 79,
79, 84, 88, 79, 97, 79, 95, 95, 79, 14, 79, 84, 79, 79, 79, 79, 88, 88, 79, 79, 79, 95, 95, 79,
14, 79, 84, 79, 79, 79, 79, 88, 98, 17, 99, 100, 13, 14, 79, 84, 17, 99, 100, 13, 14, 79, 84,
99, 99, 13, 14, 79, 84, 101, 102, 102, 13, 14, 79, 84, 89, 103, 79, 14, 79, 84, 95, 95, 79, 14,
79, 84, 89, 79, 95, 95, 79, 14, 79, 84, 95, 103, 79, 14, 79, 84, 91, 81, 87, 87, 13, 14, 79,
84, 79, 79, 79, 79, 79, 85, 91, 81, 92, 87, 13, 14, 79, 84, 79, 79, 19, 79, 79, 85, 11, 12, 12,
13, 14, 80, 81, 87, 83, 13, 14, 79, 84, 79, 79, 79, 79, 79, 85, 105, 49, 106, 106, 24, 25, 104,
52, 104, 104, 104, 104, 104, 56, 49, 106, 106, 24, 25, 104, 52, 104, 104, 104, 104, 104, 56,
107, 104, 104, 104, 108, 109, 104, 25, 104, 52, 104, 104, 104, 104, 107, 48, 49, 110, 111, 24,
25, 104, 52, 104, 104, 26, 104, 104, 56, 107, 104, 104, 104, 112, 109, 104, 25, 104, 52, 104,
104, 104, 104, 107, 52, 104, 104, 113, 104, 52, 52, 52, 104, 104, 104, 104, 52, 107, 104, 114,
104, 112, 112, 104, 25, 104, 52, 104, 104, 104, 104, 107, 107, 104, 104, 104, 112, 112, 104,
25, 104, 52, 104, 104, 104, 104, 107, 115, 30, 116, 117, 24, 25, 104, 52, 30, 116, 117, 24, 25,
104, 52, 116, 116, 24, 25, 104, 52, 48, 49, 106, 106, 24, 25, 104, 52, 104, 104, 104, 104, 104,
56, 118, 119, 119, 24, 25, 104, 52, 108, 120, 104, 25, 104, 52, 112, 112, 104, 25, 104, 52,
108, 104, 112, 112, 104, 25, 104, 52, 112, 120, 104, 25, 104, 52, 48, 49, 110, 106, 24, 25,
104, 52, 104, 104, 26, 104, 104, 56, 22, 23, 23, 24, 25, 121, 121, 121, 121, 26, 22, 23, 23,
24, 25, 123, 124, 125, 126, 35, 36, 122, 127, 122, 122, 37, 122, 122, 128, 129, 124, 126, 126,
35, 36, 122, 127, 122, 122, 122, 122, 122, 128, 124, 126, 126, 35, 36, 122, 127, 122, 122, 122,
122, 122, 128, 130, 122, 122, 122, 131, 132, 122, 36, 122, 127, 122, 122, 122, 122, 130, 123,
124, 125, 53, 35, 36, 122, 127, 122, 122, 37, 122, 122, 128, 130, 122, 122, 122, 133, 132, 122,
36, 122, 127, 122, 122, 122, 122, 130, 127, 122, 122, 134, 122, 127, 127, 127, 122, 122, 122,
122, 127, 130, 122, 135, 122, 133, 133, 122, 36, 122, 127, 122, 122, 122, 122, 130, 130, 122,
122, 122, 133, 133, 122, 36, 122, 127, 122, 122, 122, 122, 130, 136, 40, 137, 138, 35, 36, 122,
127, 40, 137, 138, 35, 36, 122, 127, 137, 137, 35, 36, 122, 127, 123, 124, 126, 126, 35, 36,
122, 127, 122, 122, 122, 122, 122, 128, 139, 140, 140, 35, 36, 122, 127, 131, 141, 122, 36,
122, 127, 133, 133, 122, 36, 122, 127, 131, 122, 133, 133, 122, 36, 122, 127, 133, 141, 122,
36, 122, 127, 46, 47, 48, 49, 110, 106, 24, 25, 104, 52, 53, 53, 26, 104, 46, 56, 60, 142, 62,
63, 4, 1, 59, 64, 59, 59, 9, 59, 59, 65, 46, 47, 48, 49, 143, 144, 24, 145, 59, 146, 59, 53,
26, 59, 46, 56, 22, 147, 147, 24, 145, 59, 64, 59, 59, 26, 146, 59, 59, 148, 59, 146, 146, 146,
59, 59, 59, 59, 146, 46, 59, 72, 22, 147, 147, 24, 145, 59, 64, 59, 59, 59, 59, 46, 150, 149,
151, 151, 149, 43, 149, 152, 151, 151, 149, 43, 149, 152, 152, 149, 149, 153, 149, 152, 152,
152, 149, 149, 149, 149, 152, 46, 121, 121, 121, 121, 121, 121, 121, 121, 121, 53, 121, 121,
121, 46, 0, 0,
];
static _indic_syllable_machine_index_defaults: [i16; 143] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 21, 21, 27, 21, 21, 21, 21,
21, 21, 32, 32, 32, 32, 32, 32, 32, 32, 32, 0, 42, 45, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79,
79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104,
104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 121, 121, 122, 122, 122, 122, 122, 122, 122,
122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 104, 59, 59, 59, 59, 59, 59,
59, 149, 149, 149, 149, 149, 121, 0, 0,
];
static _indic_syllable_machine_cond_targs: [i16; 156] = [
39, 45, 50, 2, 51, 5, 6, 53, 57, 58, 39, 67, 11, 73, 68, 14, 15, 75, 80, 81, 84, 39, 89, 21,
95, 90, 98, 39, 24, 25, 97, 103, 39, 112, 30, 118, 113, 121, 33, 34, 120, 126, 39, 137, 39, 39,
40, 60, 85, 87, 105, 106, 91, 107, 127, 128, 99, 135, 140, 39, 41, 43, 8, 59, 46, 54, 42, 1,
44, 48, 0, 47, 49, 52, 3, 4, 55, 7, 56, 39, 61, 63, 18, 83, 69, 76, 62, 9, 64, 78, 71, 65, 17,
82, 66, 10, 70, 72, 74, 12, 13, 77, 16, 79, 39, 86, 26, 88, 101, 93, 19, 104, 20, 92, 94, 96,
22, 23, 100, 27, 102, 39, 39, 108, 110, 28, 35, 114, 122, 109, 111, 124, 116, 29, 115, 117,
119, 31, 32, 123, 36, 125, 129, 130, 134, 131, 132, 37, 133, 39, 136, 38, 138, 139, 0, 0,
];
static _indic_syllable_machine_cond_actions: [i8; 156] = [
1, 0, 2, 0, 2, 0, 0, 2, 2, 2, 3, 2, 0, 2, 0, 0, 0, 2, 2, 2, 2, 4, 2, 0, 5, 0, 5, 6, 0, 0, 5, 2,
7, 2, 0, 2, 0, 2, 0, 0, 2, 2, 8, 0, 0, 11, 2, 2, 5, 0, 12, 12, 0, 2, 5, 2, 5, 2, 0, 13, 2, 0,
0, 2, 0, 2, 2, 0, 2, 2, 0, 0, 2, 2, 0, 0, 0, 0, 2, 14, 2, 0, 0, 2, 0, 2, 2, 0, 2, 2, 2, 2, 0,
2, 2, 0, 0, 2, 2, 0, 0, 0, 0, 2, 15, 5, 0, 5, 2, 2, 0, 5, 0, 0, 2, 5, 0, 0, 0, 0, 2, 16, 17, 2,
0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 0, 0, 2, 0, 18, 18, 0, 0, 0, 0, 19, 2, 0, 0, 0, 0,
0,
];
static _indic_syllable_machine_to_state_actions: [i8; 143] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
static _indic_syllable_machine_from_state_actions: [i8; 143] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
static _indic_syllable_machine_eof_trans: [i16; 143] = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 22, 22, 28, 22, 22, 22, 22,
22, 22, 33, 33, 33, 33, 33, 33, 33, 33, 33, 1, 43, 45, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,
60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105,
105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 122, 122, 123, 123, 123, 123, 123, 123, 123,
123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 105, 60, 60, 60, 60, 60, 60,
60, 150, 150, 150, 150, 150, 122, 0, 0,
];
static indic_syllable_machine_start: i32 = 39;
static indic_syllable_machine_first_final: i32 = 39;
static indic_syllable_machine_error: i32 = -1;
static indic_syllable_machine_en_main: i32 = 39;
#[derive(Clone, Copy)]
pub enum SyllableType {
ConsonantSyllable = 0,
VowelSyllable,
StandaloneCluster,
SymbolCluster,
BrokenCluster,
NonIndicCluster,
}
pub fn find_syllables_indic(buffer: &mut hb_buffer_t) {
let mut cs = 0;
let mut ts = 0;
let mut te = 0;
let mut act = 0;
let mut p = 0;
let pe = buffer.len;
let eof = buffer.len;
let mut syllable_serial = 1u8;
macro_rules! found_syllable {
($kind:expr) => {{
found_syllable(ts, te, &mut syllable_serial, $kind, buffer)
}};
}
{
cs = (indic_syllable_machine_start) as i32;
ts = 0;
te = 0;
act = 0;
}
{
let mut _trans = 0;
let mut _keys: i32 = 0;
let mut _inds: i32 = 0;
let mut _ic = 0;
'_resume: while (p != pe || p == eof) {
'_again: while (true) {
match (_indic_syllable_machine_from_state_actions[(cs) as usize]) {
10 => {
ts = p;
}
_ => {}
}
if (p == eof) {
{
if (_indic_syllable_machine_eof_trans[(cs) as usize] > 0) {
{
_trans =
(_indic_syllable_machine_eof_trans[(cs) as usize]) as u32 - 1;
}
}
}
} else {
{
_keys = (cs << 1) as i32;
_inds = (_indic_syllable_machine_index_offsets[(cs) as usize]) as i32;
if ((buffer.info[p].indic_category() as u8) <= 19
&& (buffer.info[p].indic_category() as u8) >= 1)
{
{
_ic = (_indic_syllable_machine_char_class
[((buffer.info[p].indic_category() as u8) as i32 - 1) as usize])
as i32;
if (_ic
<= (_indic_syllable_machine_trans_keys[(_keys + 1) as usize])
as i32
&& _ic
>= (_indic_syllable_machine_trans_keys[(_keys) as usize])
as i32)
{
_trans = (_indic_syllable_machine_indices[(_inds
+ (_ic
- (_indic_syllable_machine_trans_keys[(_keys) as usize])
as i32)
as i32)
as usize])
as u32;
} else {
_trans = (_indic_syllable_machine_index_defaults[(cs) as usize])
as u32;
}
}
} else {
{
_trans =
(_indic_syllable_machine_index_defaults[(cs) as usize]) as u32;
}
}
}
}
cs = (_indic_syllable_machine_cond_targs[(_trans) as usize]) as i32;
if (_indic_syllable_machine_cond_actions[(_trans) as usize] != 0) {
{
match (_indic_syllable_machine_cond_actions[(_trans) as usize]) {
2 => {
te = p + 1;
}
11 => {
te = p + 1;
{
found_syllable!(SyllableType::NonIndicCluster);
}
}
13 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::ConsonantSyllable);
}
}
14 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::VowelSyllable);
}
}
17 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::StandaloneCluster);
}
}
19 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::SymbolCluster);
}
}
15 => {
{
{
te = p;
p = p - 1;
{
found_syllable!(SyllableType::BrokenCluster);
/*buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_BROKEN_SYLLABLE;*/
}
}
}
}
16 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::NonIndicCluster);
}
}
1 => {
p = (te) - 1;
{
found_syllable!(SyllableType::ConsonantSyllable);
}
}
3 => {
p = (te) - 1;
{
found_syllable!(SyllableType::VowelSyllable);
}
}
7 => {
p = (te) - 1;
{
found_syllable!(SyllableType::StandaloneCluster);
}
}
8 => {
p = (te) - 1;
{
found_syllable!(SyllableType::SymbolCluster);
}
}
4 => {
{
{
p = (te) - 1;
{
found_syllable!(SyllableType::BrokenCluster);
/*buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_BROKEN_SYLLABLE;*/
}
}
}
}
6 => {
{
{
match (act) {
1 => {
p = (te) - 1;
{
found_syllable!(
SyllableType::ConsonantSyllable
);
}
}
5 => {
p = (te) - 1;
{
found_syllable!(SyllableType::BrokenCluster);
/*buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_BROKEN_SYLLABLE;*/
}
}
6 => {
p = (te) - 1;
{
found_syllable!(SyllableType::NonIndicCluster);
}
}
_ => {}
}
}
}
}
18 => {
{
{
te = p + 1;
}
}
{
{
act = 1;
}
}
}
5 => {
{
{
te = p + 1;
}
}
{
{
act = 5;
}
}
}
12 => {
{
{
te = p + 1;
}
}
{
{
act = 6;
}
}
}
_ => {}
}
}
}
break '_again;
}
if (p == eof) {
{
if (cs >= 39) {
break '_resume;
}
}
} else {
{
match (_indic_syllable_machine_to_state_actions[(cs) as usize]) {
9 => {
ts = 0;
}
_ => {}
}
p += 1;
continue '_resume;
}
}
break '_resume;
}
}
}
#[inline]
fn found_syllable(
start: usize,
end: usize,
syllable_serial: &mut u8,
kind: SyllableType,
buffer: &mut hb_buffer_t,
) {
for i in start..end {
buffer.info[i].set_syllable((*syllable_serial << 4) | kind as u8);
}
*syllable_serial += 1;
if *syllable_serial == 16 {
*syllable_serial = 1;
}
}

View File

@@ -0,0 +1,392 @@
// WARNING: this file was generated by scripts/gen-indic-table.py
#![allow(non_camel_case_types)]
#![allow(unused_imports)]
use super::ot_shape_complex_indic::{MatraCategory, SyllabicCategory};
use SyllabicCategory::Avagraha as ISC_A;
use SyllabicCategory::Bindu as ISC_Bi;
use SyllabicCategory::BrahmiJoiningNumber as ISC_BJN;
use SyllabicCategory::CantillationMark as ISC_Ca;
use SyllabicCategory::Consonant as ISC_C;
use SyllabicCategory::ConsonantDead as ISC_CD;
use SyllabicCategory::ConsonantFinal as ISC_CF;
use SyllabicCategory::ConsonantHeadLetter as ISC_CHL;
use SyllabicCategory::ConsonantInitialPostfixed as ISC_CIP;
use SyllabicCategory::ConsonantKiller as ISC_CK;
use SyllabicCategory::ConsonantMedial as ISC_CM;
use SyllabicCategory::ConsonantPlaceholder as ISC_CP;
use SyllabicCategory::ConsonantPrecedingRepha as ISC_CPR;
use SyllabicCategory::ConsonantPrefixed as ISC_CPrf;
use SyllabicCategory::ConsonantSubjoined as ISC_CS;
use SyllabicCategory::ConsonantSucceedingRepha as ISC_CSR;
use SyllabicCategory::ConsonantWithStacker as ISC_CWS;
use SyllabicCategory::GeminationMark as ISC_GM;
use SyllabicCategory::InvisibleStacker as ISC_IS;
use SyllabicCategory::Joiner as ISC_ZWJ;
use SyllabicCategory::ModifyingLetter as ISC_ML;
use SyllabicCategory::NonJoiner as ISC_ZWNJ;
use SyllabicCategory::Nukta as ISC_N;
use SyllabicCategory::Number as ISC_Nd;
use SyllabicCategory::NumberJoiner as ISC_NJ;
use SyllabicCategory::Other as ISC_x;
use SyllabicCategory::PureKiller as ISC_PK;
use SyllabicCategory::RegisterShifter as ISC_RS;
use SyllabicCategory::SyllableModifier as ISC_SM;
use SyllabicCategory::ToneLetter as ISC_TL;
use SyllabicCategory::ToneMark as ISC_TM;
use SyllabicCategory::Virama as ISC_V;
use SyllabicCategory::Visarga as ISC_Vs;
use SyllabicCategory::Vowel as ISC_Vo;
use SyllabicCategory::VowelDependent as ISC_M;
use SyllabicCategory::VowelIndependent as ISC_VI;
use MatraCategory::Bottom as IMC_B;
use MatraCategory::BottomAndLeft as IMC_BL;
use MatraCategory::BottomAndRight as IMC_BR;
use MatraCategory::Left as IMC_L;
use MatraCategory::LeftAndRight as IMC_LR;
use MatraCategory::NotApplicable as IMC_x;
use MatraCategory::Overstruck as IMC_O;
use MatraCategory::Right as IMC_R;
use MatraCategory::Top as IMC_T;
use MatraCategory::TopAndBottom as IMC_TB;
use MatraCategory::TopAndBottomAndLeft as IMC_TBL;
use MatraCategory::TopAndBottomAndRight as IMC_TBR;
use MatraCategory::TopAndLeft as IMC_TL;
use MatraCategory::TopAndLeftAndRight as IMC_TLR;
use MatraCategory::TopAndRight as IMC_TR;
use MatraCategory::VisualOrderLeft as IMC_VOL;
#[rustfmt::skip]
const TABLE: &[(SyllabicCategory, MatraCategory)] = &[
/* Basic Latin */
/* 0028 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_CP,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0030 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0038 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* Latin-1 Supplement */
/* 00B0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_SM,IMC_x), (ISC_SM,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 00B8 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 00C0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 00C8 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 00D0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_CP,IMC_x),
/* Devanagari */
/* 0900 */ (ISC_Bi,IMC_T), (ISC_Bi,IMC_T), (ISC_Bi,IMC_T), (ISC_Vs,IMC_R), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0908 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0910 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0918 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0920 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0928 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0930 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0938 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_M,IMC_T), (ISC_M,IMC_R), (ISC_N,IMC_B), (ISC_A,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_L),
/* 0940 */ (ISC_M,IMC_R), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_M,IMC_T),
/* 0948 */ (ISC_M,IMC_T), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_V,IMC_B), (ISC_M,IMC_L), (ISC_M,IMC_R),
/* 0950 */ (ISC_x,IMC_x), (ISC_Ca,IMC_T), (ISC_Ca,IMC_B), (ISC_x,IMC_T), (ISC_x,IMC_T), (ISC_M,IMC_T), (ISC_M,IMC_B), (ISC_M,IMC_B),
/* 0958 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0960 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0968 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0970 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0978 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* Bengali */
/* 0980 */ (ISC_CP,IMC_x), (ISC_Bi,IMC_T), (ISC_Bi,IMC_R), (ISC_Vs,IMC_R), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0988 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x),
/* 0990 */ (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0998 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 09A0 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 09A8 */ (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 09B0 */ (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 09B8 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_N,IMC_B), (ISC_A,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_L),
/* 09C0 */ (ISC_M,IMC_R), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_L),
/* 09C8 */ (ISC_M,IMC_L), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_LR), (ISC_M,IMC_LR), (ISC_V,IMC_B), (ISC_CD,IMC_x), (ISC_x,IMC_x),
/* 09D0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_R),
/* 09D8 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x),
/* 09E0 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 09E8 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 09F0 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 09F8 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_Bi,IMC_x), (ISC_x,IMC_x), (ISC_SM,IMC_T), (ISC_x,IMC_x),
/* Gurmukhi */
/* 0A00 */ (ISC_x,IMC_x), (ISC_Bi,IMC_T), (ISC_Bi,IMC_T), (ISC_Vs,IMC_R), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0A08 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x),
/* 0A10 */ (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0A18 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0A20 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0A28 */ (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0A30 */ (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x),
/* 0A38 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_N,IMC_B), (ISC_x,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_L),
/* 0A40 */ (ISC_M,IMC_R), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_T),
/* 0A48 */ (ISC_M,IMC_T), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_V,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0A50 */ (ISC_x,IMC_x), (ISC_Ca,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0A58 */ (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x),
/* 0A60 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0A68 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0A70 */ (ISC_Bi,IMC_T), (ISC_GM,IMC_T), (ISC_CP,IMC_x), (ISC_CP,IMC_x), (ISC_x,IMC_x), (ISC_CM,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0A78 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* Gujarati */
/* 0A80 */ (ISC_x,IMC_x), (ISC_Bi,IMC_T), (ISC_Bi,IMC_T), (ISC_Vs,IMC_R), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0A88 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x),
/* 0A90 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0A98 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0AA0 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0AA8 */ (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0AB0 */ (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0AB8 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_N,IMC_B), (ISC_A,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_L),
/* 0AC0 */ (ISC_M,IMC_R), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_M,IMC_T), (ISC_x,IMC_x), (ISC_M,IMC_T),
/* 0AC8 */ (ISC_M,IMC_T), (ISC_M,IMC_TR), (ISC_x,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_V,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0AD0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0AD8 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0AE0 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0AE8 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0AF0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0AF8 */ (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_N,IMC_T), (ISC_N,IMC_T), (ISC_N,IMC_T),
/* Oriya */
/* 0B00 */ (ISC_x,IMC_x), (ISC_Bi,IMC_T), (ISC_Bi,IMC_R), (ISC_Vs,IMC_R), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0B08 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x),
/* 0B10 */ (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0B18 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0B20 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0B28 */ (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0B30 */ (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0B38 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_N,IMC_B), (ISC_A,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_T),
/* 0B40 */ (ISC_M,IMC_R), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_L),
/* 0B48 */ (ISC_M,IMC_TL), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_LR),(ISC_M,IMC_TLR), (ISC_V,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0B50 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_M,IMC_TR),
/* 0B58 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x),
/* 0B60 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0B68 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0B70 */ (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0B78 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* Tamil */
/* 0B80 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_Bi,IMC_T), (ISC_ML,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0B88 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0B90 */ (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0B98 */ (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0BA0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0BA8 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0BB0 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0BB8 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_R),
/* 0BC0 */ (ISC_M,IMC_T), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_L), (ISC_M,IMC_L),
/* 0BC8 */ (ISC_M,IMC_L), (ISC_x,IMC_x), (ISC_M,IMC_LR), (ISC_M,IMC_LR), (ISC_M,IMC_LR), (ISC_V,IMC_T), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0BD0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_R),
/* 0BD8 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0BE0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0BE8 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0BF0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0BF8 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* Telugu */
/* 0C00 */ (ISC_Bi,IMC_T), (ISC_Bi,IMC_R), (ISC_Bi,IMC_R), (ISC_Vs,IMC_R), (ISC_Bi,IMC_T), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0C08 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0C10 */ (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0C18 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0C20 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0C28 */ (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0C30 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0C38 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_N,IMC_B), (ISC_A,IMC_x), (ISC_M,IMC_T), (ISC_M,IMC_T),
/* 0C40 */ (ISC_M,IMC_T), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_x,IMC_x), (ISC_M,IMC_T), (ISC_M,IMC_T),
/* 0C48 */ (ISC_M,IMC_TB), (ISC_x,IMC_x), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_V,IMC_T), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0C50 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_T), (ISC_M,IMC_B), (ISC_x,IMC_x),
/* 0C58 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_CD,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0C60 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0C68 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0C70 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0C78 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* Kannada */
/* 0C80 */ (ISC_Bi,IMC_x), (ISC_Bi,IMC_T), (ISC_Bi,IMC_R), (ISC_Vs,IMC_R), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0C88 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0C90 */ (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0C98 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0CA0 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0CA8 */ (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0CB0 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0CB8 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_N,IMC_B), (ISC_A,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_T),
/* 0CC0 */ (ISC_M,IMC_TR), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_x,IMC_x), (ISC_M,IMC_T), (ISC_M,IMC_TR),
/* 0CC8 */ (ISC_M,IMC_TR), (ISC_x,IMC_x), (ISC_M,IMC_TR), (ISC_M,IMC_TR), (ISC_M,IMC_T), (ISC_V,IMC_T), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0CD0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_x,IMC_x),
/* 0CD8 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_CD,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x),
/* 0CE0 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0CE8 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0CF0 */ (ISC_x,IMC_x),(ISC_CWS,IMC_x),(ISC_CWS,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0CF8 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* Malayalam */
/* 0D00 */ (ISC_Bi,IMC_T), (ISC_Bi,IMC_T), (ISC_Bi,IMC_R), (ISC_Vs,IMC_R), (ISC_Bi,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0D08 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0D10 */ (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0D18 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0D20 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0D28 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0D30 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0D38 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_PK,IMC_T), (ISC_PK,IMC_T), (ISC_A,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_R),
/* 0D40 */ (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_x,IMC_x), (ISC_M,IMC_L), (ISC_M,IMC_L),
/* 0D48 */ (ISC_M,IMC_L), (ISC_x,IMC_x), (ISC_M,IMC_LR), (ISC_M,IMC_LR), (ISC_M,IMC_LR), (ISC_V,IMC_T),(ISC_CPR,IMC_T), (ISC_x,IMC_x),
/* 0D50 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_CD,IMC_x), (ISC_CD,IMC_x), (ISC_CD,IMC_x), (ISC_M,IMC_R),
/* 0D58 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x),
/* 0D60 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0D68 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0D70 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0D78 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_CD,IMC_x), (ISC_CD,IMC_x), (ISC_CD,IMC_x), (ISC_CD,IMC_x), (ISC_CD,IMC_x), (ISC_CD,IMC_x),
/* Sinhala */
/* 0D80 */ (ISC_x,IMC_x), (ISC_Bi,IMC_T), (ISC_Bi,IMC_R), (ISC_Vs,IMC_R), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0D88 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 0D90 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_x,IMC_x),
/* 0D98 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0DA0 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0DA8 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0DB0 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 0DB8 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 0DC0 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x),
/* 0DC8 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_V,IMC_T), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_R),
/* 0DD0 */ (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_M,IMC_B), (ISC_x,IMC_x), (ISC_M,IMC_B), (ISC_x,IMC_x),
/* 0DD8 */ (ISC_M,IMC_R), (ISC_M,IMC_L), (ISC_M,IMC_TL), (ISC_M,IMC_L), (ISC_M,IMC_LR),(ISC_M,IMC_TLR), (ISC_M,IMC_LR), (ISC_M,IMC_R),
/* 0DE0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0DE8 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 0DF0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* Myanmar */
/* 1000 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 1008 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 1010 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 1018 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 1020 */ (ISC_C,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 1028 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_R), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_M,IMC_B),
/* 1030 */ (ISC_M,IMC_B), (ISC_M,IMC_L), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_Bi,IMC_T), (ISC_TM,IMC_B),
/* 1038 */ (ISC_Vs,IMC_R), (ISC_IS,IMC_x), (ISC_PK,IMC_T), (ISC_CM,IMC_R),(ISC_CM,IMC_TBL), (ISC_CM,IMC_B), (ISC_CM,IMC_B), (ISC_C,IMC_x),
/* 1040 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 1048 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_x,IMC_x), (ISC_CP,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_CP,IMC_x), (ISC_x,IMC_x),
/* 1050 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_R),
/* 1058 */ (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_CM,IMC_B), (ISC_CM,IMC_B),
/* 1060 */ (ISC_CM,IMC_B), (ISC_C,IMC_x), (ISC_M,IMC_R), (ISC_TM,IMC_R), (ISC_TM,IMC_R), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_M,IMC_R),
/* 1068 */ (ISC_M,IMC_R), (ISC_TM,IMC_R), (ISC_TM,IMC_R), (ISC_TM,IMC_R), (ISC_TM,IMC_R), (ISC_TM,IMC_R), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 1070 */ (ISC_C,IMC_x), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 1078 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 1080 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_CM,IMC_B), (ISC_M,IMC_R), (ISC_M,IMC_L), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_TM,IMC_R),
/* 1088 */ (ISC_TM,IMC_R), (ISC_TM,IMC_R), (ISC_TM,IMC_R), (ISC_TM,IMC_R), (ISC_TM,IMC_R), (ISC_TM,IMC_B), (ISC_C,IMC_x), (ISC_TM,IMC_R),
/* 1090 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 1098 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_TM,IMC_R), (ISC_TM,IMC_R), (ISC_M,IMC_R), (ISC_M,IMC_T), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* Khmer */
/* 1780 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 1788 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 1790 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 1798 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* 17A0 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 17A8 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x),
/* 17B0 */ (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_VI,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_M,IMC_R), (ISC_M,IMC_T),
/* 17B8 */ (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_M,IMC_T), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_M,IMC_B), (ISC_M,IMC_TL),(ISC_M,IMC_TLR),
/* 17C0 */ (ISC_M,IMC_LR), (ISC_M,IMC_L), (ISC_M,IMC_L), (ISC_M,IMC_L), (ISC_M,IMC_LR), (ISC_M,IMC_LR), (ISC_Bi,IMC_T), (ISC_Vs,IMC_R),
/* 17C8 */ (ISC_M,IMC_R), (ISC_RS,IMC_T), (ISC_RS,IMC_T), (ISC_SM,IMC_T),(ISC_CSR,IMC_T), (ISC_CK,IMC_T), (ISC_SM,IMC_T), (ISC_SM,IMC_T),
/* 17D0 */ (ISC_SM,IMC_T), (ISC_PK,IMC_T), (ISC_IS,IMC_x), (ISC_SM,IMC_T), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 17D8 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_A,IMC_x), (ISC_SM,IMC_T), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 17E0 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* 17E8 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* Vedic Extensions */
/* 1CD0 */ (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_x,IMC_x), (ISC_Ca,IMC_O), (ISC_Ca,IMC_B), (ISC_Ca,IMC_B), (ISC_Ca,IMC_B),
/* 1CD8 */ (ISC_Ca,IMC_B), (ISC_Ca,IMC_B), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_B), (ISC_Ca,IMC_B), (ISC_Ca,IMC_B), (ISC_Ca,IMC_B),
/* 1CE0 */ (ISC_Ca,IMC_T), (ISC_Ca,IMC_R), (ISC_x,IMC_O), (ISC_x,IMC_O), (ISC_x,IMC_O), (ISC_x,IMC_O), (ISC_x,IMC_O), (ISC_x,IMC_O),
/* 1CE8 */ (ISC_x,IMC_O), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_B), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 1CF0 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_CD,IMC_x), (ISC_CD,IMC_x), (ISC_Ca,IMC_T),(ISC_CWS,IMC_x),(ISC_CWS,IMC_x), (ISC_Ca,IMC_R),
/* 1CF8 */ (ISC_Ca,IMC_x), (ISC_Ca,IMC_x), (ISC_CP,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* General Punctuation */
/* 2008 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),(ISC_ZWNJ,IMC_x),(ISC_ZWJ,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 2010 */ (ISC_CP,IMC_x), (ISC_CP,IMC_x), (ISC_CP,IMC_x), (ISC_CP,IMC_x), (ISC_CP,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* Superscripts and Subscripts */
/* 2070 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_SM,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 2078 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* 2080 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_SM,IMC_x), (ISC_SM,IMC_x), (ISC_SM,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* Devanagari Extended */
/* A8E0 */ (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T),
/* A8E8 */ (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Ca,IMC_T),
/* A8F0 */ (ISC_Ca,IMC_T), (ISC_Ca,IMC_T), (ISC_Bi,IMC_x), (ISC_Bi,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x),
/* A8F8 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_VI,IMC_x), (ISC_M,IMC_T),
/* Myanmar Extended-B */
/* A9E0 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_M,IMC_T), (ISC_x,IMC_x), (ISC_C,IMC_x),
/* A9E8 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* A9F0 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_Nd,IMC_x),
/* A9F8 */ (ISC_Nd,IMC_x), (ISC_Nd,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_x,IMC_x),
/* Myanmar Extended-A */
/* AA60 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* AA68 */ (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x),
/* AA70 */ (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_C,IMC_x), (ISC_CP,IMC_x), (ISC_CP,IMC_x), (ISC_CP,IMC_x), (ISC_x,IMC_x),
/* AA78 */ (ISC_x,IMC_x), (ISC_x,IMC_x), (ISC_C,IMC_x), (ISC_TM,IMC_R), (ISC_TM,IMC_T), (ISC_TM,IMC_R), (ISC_C,IMC_x), (ISC_C,IMC_x),
];
const OFFSET_0X0028: usize = 0;
const OFFSET_0X00B0: usize = 24;
const OFFSET_0X0900: usize = 64;
const OFFSET_0X1000: usize = 1336;
const OFFSET_0X1780: usize = 1496;
const OFFSET_0X1CD0: usize = 1608;
const OFFSET_0X2008: usize = 1656;
const OFFSET_0X2070: usize = 1672;
const OFFSET_0XA8E0: usize = 1696;
const OFFSET_0XA9E0: usize = 1728;
const OFFSET_0XAA60: usize = 1760;
#[rustfmt::skip]
pub fn get_categories(u: u32) -> (SyllabicCategory, MatraCategory) {
match u >> 12 {
0x0 => {
if u == 0x00A0 { return (ISC_CP, IMC_x); }
if (0x0028..=0x003F).contains(&u) { return TABLE[u as usize - 0x0028 + OFFSET_0X0028]; }
if (0x00B0..=0x00D7).contains(&u) { return TABLE[u as usize - 0x00B0 + OFFSET_0X00B0]; }
if (0x0900..=0x0DF7).contains(&u) { return TABLE[u as usize - 0x0900 + OFFSET_0X0900]; }
}
0x1 => {
if (0x1000..=0x109F).contains(&u) { return TABLE[u as usize - 0x1000 + OFFSET_0X1000]; }
if (0x1780..=0x17EF).contains(&u) { return TABLE[u as usize - 0x1780 + OFFSET_0X1780]; }
if (0x1CD0..=0x1CFF).contains(&u) { return TABLE[u as usize - 0x1CD0 + OFFSET_0X1CD0]; }
}
0x2 => {
if u == 0x25CC { return (ISC_CP, IMC_x); }
if (0x2008..=0x2017).contains(&u) { return TABLE[u as usize - 0x2008 + OFFSET_0X2008]; }
if (0x2070..=0x2087).contains(&u) { return TABLE[u as usize - 0x2070 + OFFSET_0X2070]; }
}
0xA => {
if (0xA8E0..=0xA8FF).contains(&u) { return TABLE[u as usize - 0xA8E0 + OFFSET_0XA8E0]; }
if (0xA9E0..=0xA9FF).contains(&u) { return TABLE[u as usize - 0xA9E0 + OFFSET_0XA9E0]; }
if (0xAA60..=0xAA7F).contains(&u) { return TABLE[u as usize - 0xAA60 + OFFSET_0XAA60]; }
}
_ => {}
}
(ISC_x, IMC_x)
}

View File

@@ -0,0 +1,323 @@
use alloc::boxed::Box;
use super::buffer::hb_buffer_t;
use super::ot_map::*;
use super::ot_shape::*;
use super::ot_shape_complex::*;
use super::ot_shape_complex_indic::{category, position};
use super::ot_shape_normalize::*;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::unicode::{CharExt, GeneralCategoryExt};
use super::{hb_font_t, hb_glyph_info_t, hb_mask_t, hb_tag_t};
pub const KHMER_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
collect_features: Some(collect_features),
override_features: Some(override_features),
create_data: Some(|plan| Box::new(KhmerShapePlan::new(plan))),
preprocess_text: None,
postprocess_glyphs: None,
normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT,
decompose: Some(decompose),
compose: Some(compose),
setup_masks: Some(setup_masks),
gpos_tag: None,
reorder_marks: None,
zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE,
fallback_position: false,
};
const KHMER_FEATURES: &[(hb_tag_t, hb_ot_map_feature_flags_t)] = &[
// Basic features.
// These features are applied all at once, before reordering, constrained
// to the syllable.
(
hb_tag_t::from_bytes(b"pref"),
F_MANUAL_JOINERS | F_PER_SYLLABLE,
),
(
hb_tag_t::from_bytes(b"blwf"),
F_MANUAL_JOINERS | F_PER_SYLLABLE,
),
(
hb_tag_t::from_bytes(b"abvf"),
F_MANUAL_JOINERS | F_PER_SYLLABLE,
),
(
hb_tag_t::from_bytes(b"pstf"),
F_MANUAL_JOINERS | F_PER_SYLLABLE,
),
(
hb_tag_t::from_bytes(b"cfar"),
F_MANUAL_JOINERS | F_PER_SYLLABLE,
),
// Other features.
// These features are applied all at once after clearing syllables.
(hb_tag_t::from_bytes(b"pres"), F_GLOBAL_MANUAL_JOINERS),
(hb_tag_t::from_bytes(b"abvs"), F_GLOBAL_MANUAL_JOINERS),
(hb_tag_t::from_bytes(b"blws"), F_GLOBAL_MANUAL_JOINERS),
(hb_tag_t::from_bytes(b"psts"), F_GLOBAL_MANUAL_JOINERS),
];
// Must be in the same order as the KHMER_FEATURES array.
mod khmer_feature {
pub const PREF: usize = 0;
pub const BLWF: usize = 1;
pub const ABVF: usize = 2;
pub const PSTF: usize = 3;
pub const CFAR: usize = 4;
}
impl hb_glyph_info_t {
fn set_khmer_properties(&mut self) {
let u = self.glyph_id;
let (mut cat, pos) = crate::hb::ot_shape_complex_indic::get_category_and_position(u);
// Re-assign category
// These categories are experimentally extracted from what Uniscribe allows.
match u {
0x179A => cat = category::RA,
0x17CC | 0x17C9 | 0x17CA => cat = category::ROBATIC,
0x17C6 | 0x17CB | 0x17CD | 0x17CE | 0x17CF | 0x17D0 | 0x17D1 => cat = category::X_GROUP,
// Just guessing. Uniscribe doesn't categorize it.
0x17C7 | 0x17C8 | 0x17DD | 0x17D3 => cat = category::Y_GROUP,
_ => {}
}
// Re-assign position.
if cat == category::M {
match pos {
position::PRE_C => cat = category::V_PRE,
position::BELOW_C => cat = category::V_BLW,
position::ABOVE_C => cat = category::V_AVB,
position::POST_C => cat = category::V_PST,
_ => {}
}
}
self.set_indic_category(cat);
}
}
struct KhmerShapePlan {
mask_array: [hb_mask_t; KHMER_FEATURES.len()],
}
impl KhmerShapePlan {
fn new(plan: &hb_ot_shape_plan_t) -> Self {
let mut mask_array = [0; KHMER_FEATURES.len()];
for (i, feature) in KHMER_FEATURES.iter().enumerate() {
mask_array[i] = if feature.1 & F_GLOBAL != 0 {
0
} else {
plan.ot_map.get_1_mask(feature.0)
}
}
KhmerShapePlan { mask_array }
}
}
fn collect_features(planner: &mut hb_ot_shape_planner_t) {
// Do this before any lookups have been applied.
planner.ot_map.add_gsub_pause(Some(setup_syllables));
planner.ot_map.add_gsub_pause(Some(reorder));
// Testing suggests that Uniscribe does NOT pause between basic
// features. Test with KhmerUI.ttf and the following three
// sequences:
//
// U+1789,U+17BC
// U+1789,U+17D2,U+1789
// U+1789,U+17D2,U+1789,U+17BC
//
// https://github.com/harfbuzz/harfbuzz/issues/974
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"locl"), F_PER_SYLLABLE, 1);
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"ccmp"), F_PER_SYLLABLE, 1);
for feature in KHMER_FEATURES.iter().take(5) {
planner.ot_map.add_feature(feature.0, feature.1, 1);
}
for feature in KHMER_FEATURES.iter().skip(5) {
planner.ot_map.add_feature(feature.0, feature.1, 1);
}
}
fn setup_syllables(_: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
super::ot_shape_complex_khmer_machine::find_syllables_khmer(buffer);
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
buffer.unsafe_to_break(Some(start), Some(end));
start = end;
end = buffer.next_syllable(start);
}
}
fn reorder(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
use super::ot_shape_complex_khmer_machine::SyllableType;
super::ot_shape_complex_syllabic::insert_dotted_circles(
face,
buffer,
SyllableType::BrokenCluster as u8,
category::DOTTED_CIRCLE,
Some(category::REPHA),
None,
);
let khmer_plan = plan.data::<KhmerShapePlan>();
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
reorder_syllable(khmer_plan, start, end, buffer);
start = end;
end = buffer.next_syllable(start);
}
}
fn reorder_syllable(
khmer_plan: &KhmerShapePlan,
start: usize,
end: usize,
buffer: &mut hb_buffer_t,
) {
use super::ot_shape_complex_khmer_machine::SyllableType;
let syllable_type = match buffer.info[start].syllable() & 0x0F {
0 => SyllableType::ConsonantSyllable,
1 => SyllableType::BrokenCluster,
2 => SyllableType::NonKhmerCluster,
_ => unreachable!(),
};
match syllable_type {
SyllableType::ConsonantSyllable | SyllableType::BrokenCluster => {
reorder_consonant_syllable(khmer_plan, start, end, buffer);
}
SyllableType::NonKhmerCluster => {}
}
}
// Rules from:
// https://docs.microsoft.com/en-us/typography/script-development/devanagari
fn reorder_consonant_syllable(
plan: &KhmerShapePlan,
start: usize,
end: usize,
buffer: &mut hb_buffer_t,
) {
// Setup masks.
{
// Post-base
let mask = plan.mask_array[khmer_feature::BLWF]
| plan.mask_array[khmer_feature::ABVF]
| plan.mask_array[khmer_feature::PSTF];
for info in &mut buffer.info[start + 1..end] {
info.mask |= mask;
}
}
let mut num_coengs = 0;
for i in start + 1..end {
// When a COENG + (Cons | IndV) combination are found (and subscript count
// is less than two) the character combination is handled according to the
// subscript type of the character following the COENG.
//
// ...
//
// Subscript Type 2 - The COENG + RO characters are reordered to immediately
// before the base glyph. Then the COENG + RO characters are assigned to have
// the 'pref' OpenType feature applied to them.
if buffer.info[i].indic_category() == category::COENG && num_coengs <= 2 && i + 1 < end {
num_coengs += 1;
if buffer.info[i + 1].indic_category() == category::RA {
for j in 0..2 {
buffer.info[i + j].mask |= plan.mask_array[khmer_feature::PREF];
}
// Move the Coeng,Ro sequence to the start.
buffer.merge_clusters(start, i + 2);
let t0 = buffer.info[i];
let t1 = buffer.info[i + 1];
for k in (0..i - start).rev() {
buffer.info[k + start + 2] = buffer.info[k + start];
}
buffer.info[start] = t0;
buffer.info[start + 1] = t1;
// Mark the subsequent stuff with 'cfar'. Used in Khmer.
// Read the feature spec.
// This allows distinguishing the following cases with MS Khmer fonts:
// U+1784,U+17D2,U+179A,U+17D2,U+1782
// U+1784,U+17D2,U+1782,U+17D2,U+179A
if plan.mask_array[khmer_feature::CFAR] != 0 {
for j in i + 2..end {
buffer.info[j].mask |= plan.mask_array[khmer_feature::CFAR];
}
}
num_coengs = 2; // Done.
}
} else if buffer.info[i].indic_category() == category::V_PRE {
// Reorder left matra piece.
// Move to the start.
buffer.merge_clusters(start, i + 1);
let t = buffer.info[i];
for k in (0..i - start).rev() {
buffer.info[k + start + 1] = buffer.info[k + start];
}
buffer.info[start] = t;
}
}
}
fn override_features(planner: &mut hb_ot_shape_planner_t) {
// Khmer spec has 'clig' as part of required shaping features:
// "Apply feature 'clig' to form ligatures that are desired for
// typographical correctness.", hence in overrides...
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"clig"), F_NONE, 1);
planner
.ot_map
.disable_feature(hb_tag_t::from_bytes(b"liga"));
}
fn decompose(_: &hb_ot_shape_normalize_context_t, ab: char) -> Option<(char, char)> {
// Decompose split matras that don't have Unicode decompositions.
match ab {
'\u{17BE}' | '\u{17BF}' | '\u{17C0}' | '\u{17C4}' | '\u{17C5}' => Some(('\u{17C1}', ab)),
_ => crate::hb::unicode::decompose(ab),
}
}
fn compose(_: &hb_ot_shape_normalize_context_t, a: char, b: char) -> Option<char> {
// Avoid recomposing split matras.
if a.general_category().is_mark() {
return None;
}
crate::hb::unicode::compose(a, b)
}
fn setup_masks(_: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
// We cannot setup masks here. We save information about characters
// and setup masks later on in a pause-callback.
for info in buffer.info_slice_mut() {
info.set_khmer_properties();
}
}

View File

@@ -0,0 +1,114 @@
#![allow(
dead_code,
non_upper_case_globals,
unused_assignments,
unused_parens,
while_true,
clippy::assign_op_pattern,
clippy::collapsible_if,
clippy::comparison_chain,
clippy::double_parens,
clippy::unnecessary_cast,
clippy::single_match,
clippy::never_loop
)]
use super::buffer::hb_buffer_t;
%%{
machine khmer_syllable_machine;
alphtype u8;
write data;
}%%
%%{
C = 1;
V = 2;
ZWNJ = 5;
ZWJ = 6;
PLACEHOLDER = 11;
DOTTEDCIRCLE = 12;
Coeng= 14;
Ra = 16;
Robatic = 20;
Xgroup = 21;
Ygroup = 22;
VAbv = 26;
VBlw = 27;
VPre = 28;
VPst = 29;
c = (C | Ra | V);
cn = c.((ZWJ|ZWNJ)?.Robatic)?;
joiner = (ZWJ | ZWNJ);
xgroup = (joiner*.Xgroup)*;
ygroup = Ygroup*;
# This grammar was experimentally extracted from what Uniscribe allows.
matra_group = VPre? xgroup VBlw? xgroup (joiner?.VAbv)? xgroup VPst?;
syllable_tail = xgroup matra_group xgroup (Coeng.c)? ygroup;
broken_cluster = (Coeng.cn)* (Coeng | syllable_tail);
consonant_syllable = (cn|PLACEHOLDER|DOTTEDCIRCLE) broken_cluster;
other = any;
main := |*
consonant_syllable => { found_syllable!(SyllableType::ConsonantSyllable); };
broken_cluster => { found_syllable!(SyllableType::BrokenCluster); };
other => { found_syllable!(SyllableType::NonKhmerCluster); };
*|;
}%%
#[derive(Clone, Copy)]
pub enum SyllableType {
ConsonantSyllable = 0,
BrokenCluster,
NonKhmerCluster,
}
pub fn find_syllables_khmer(buffer: &mut hb_buffer_t) {
let mut cs = 0;
let mut ts = 0;
let mut te = 0;
let mut act = 0;
let mut p = 0;
let pe = buffer.len;
let eof = buffer.len;
let mut syllable_serial = 1u8;
macro_rules! found_syllable {
($kind:expr) => {{
found_syllable(ts, te, &mut syllable_serial, $kind, buffer);
}}
}
%%{
write init;
getkey (buffer.info[p].indic_category() as u8);
write exec;
}%%
}
#[inline]
fn found_syllable(
start: usize,
end: usize,
syllable_serial: &mut u8,
kind: SyllableType,
buffer: &mut hb_buffer_t,
) {
for i in start..end {
buffer.info[i].set_syllable((*syllable_serial << 4) | kind as u8);
}
*syllable_serial += 1;
if *syllable_serial == 16 {
*syllable_serial = 1;
}
}

View File

@@ -0,0 +1,300 @@
// This file is autogenerated. Do not edit it!
//
// See docs/ragel.md for details.
#![allow(
dead_code,
non_upper_case_globals,
unused_assignments,
unused_parens,
while_true,
clippy::assign_op_pattern,
clippy::collapsible_if,
clippy::comparison_chain,
clippy::double_parens,
clippy::unnecessary_cast,
clippy::single_match,
clippy::never_loop
)]
use super::buffer::hb_buffer_t;
static _khmer_syllable_machine_trans_keys: [u8; 82] = [
2, 8, 2, 6, 2, 8, 2, 6, 0, 0, 2, 6, 2, 8, 2, 6, 2, 8, 2, 6, 2, 6, 2, 8, 2, 6, 0, 0, 2, 6, 2, 8,
2, 6, 2, 8, 2, 6, 2, 8, 0, 11, 2, 11, 2, 11, 2, 11, 7, 7, 2, 7, 2, 11, 2, 11, 2, 11, 0, 0, 2,
8, 2, 11, 2, 11, 7, 7, 2, 7, 2, 11, 2, 11, 0, 0, 2, 11, 2, 11, 0, 0,
];
static _khmer_syllable_machine_char_class: [i8; 31] = [
0, 0, 1, 1, 2, 2, 1, 1, 1, 1, 3, 3, 1, 4, 1, 0, 1, 1, 1, 5, 6, 7, 1, 1, 1, 8, 9, 10, 11, 0, 0,
];
static _khmer_syllable_machine_index_offsets: [i16; 42] = [
0, 7, 12, 19, 24, 25, 30, 37, 42, 49, 54, 59, 66, 71, 72, 77, 84, 89, 96, 101, 108, 120, 130,
140, 150, 151, 157, 167, 177, 187, 188, 195, 205, 215, 216, 222, 232, 242, 243, 253, 0, 0,
];
static _khmer_syllable_machine_indices: [i8; 265] = [
1, 0, 0, 2, 3, 0, 4, 1, 0, 0, 0, 3, 1, 0, 0, 0, 3, 0, 4, 5, 0, 0, 0, 4, 6, 7, 0, 0, 0, 8, 9, 0,
0, 0, 10, 0, 4, 9, 0, 0, 0, 10, 11, 0, 0, 0, 12, 0, 4, 11, 0, 0, 0, 12, 14, 13, 13, 13, 15, 14,
16, 16, 16, 15, 16, 17, 18, 16, 16, 16, 17, 19, 20, 16, 16, 16, 21, 22, 16, 16, 16, 23, 16, 17,
22, 16, 16, 16, 23, 24, 16, 16, 16, 25, 16, 17, 24, 16, 16, 16, 25, 14, 16, 16, 26, 15, 16, 17,
29, 28, 30, 2, 31, 28, 15, 19, 17, 23, 25, 21, 33, 32, 34, 2, 3, 6, 4, 10, 12, 8, 35, 32, 36,
32, 3, 6, 4, 10, 12, 8, 5, 32, 36, 32, 4, 6, 32, 32, 32, 8, 6, 7, 32, 36, 32, 8, 6, 37, 32, 36,
32, 10, 6, 4, 32, 32, 8, 38, 32, 36, 32, 12, 6, 4, 10, 32, 8, 35, 32, 34, 32, 3, 6, 4, 10, 12,
8, 29, 14, 39, 39, 39, 15, 39, 17, 41, 40, 42, 40, 15, 19, 17, 23, 25, 21, 18, 40, 42, 40, 17,
19, 40, 40, 40, 21, 19, 20, 40, 42, 40, 21, 19, 43, 40, 42, 40, 23, 19, 17, 40, 40, 21, 44, 40,
42, 40, 25, 19, 17, 23, 40, 21, 45, 46, 40, 31, 26, 15, 19, 17, 23, 25, 21, 41, 40, 31, 40, 15,
19, 17, 23, 25, 21, 0, 0,
];
static _khmer_syllable_machine_index_defaults: [i8; 42] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 28, 32, 32, 32, 32, 32,
32, 32, 32, 32, 39, 40, 40, 40, 40, 40, 40, 40, 40, 40, 0, 0,
];
static _khmer_syllable_machine_cond_targs: [i8; 49] = [
20, 1, 28, 22, 23, 3, 24, 5, 25, 7, 26, 9, 27, 20, 10, 31, 20, 32, 12, 33, 14, 34, 16, 35, 18,
36, 39, 20, 20, 21, 30, 37, 20, 0, 29, 2, 4, 6, 8, 20, 20, 11, 13, 15, 17, 38, 19, 0, 0,
];
static _khmer_syllable_machine_cond_actions: [i8; 49] = [
1, 0, 2, 2, 2, 0, 0, 0, 2, 0, 2, 0, 2, 3, 0, 4, 5, 2, 0, 0, 0, 2, 0, 2, 0, 2, 4, 0, 8, 2, 9, 0,
10, 0, 0, 0, 0, 0, 0, 11, 12, 0, 0, 0, 0, 4, 0, 0, 0,
];
static _khmer_syllable_machine_to_state_actions: [i8; 42] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
static _khmer_syllable_machine_from_state_actions: [i8; 42] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
static _khmer_syllable_machine_eof_trans: [i8; 42] = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 17, 17, 17, 17, 17, 17, 17, 17, 17, 28, 33, 33, 33, 33, 33,
33, 33, 33, 33, 40, 41, 41, 41, 41, 41, 41, 41, 41, 41, 0, 0,
];
static khmer_syllable_machine_start: i32 = 20;
static khmer_syllable_machine_first_final: i32 = 20;
static khmer_syllable_machine_error: i32 = -1;
static khmer_syllable_machine_en_main: i32 = 20;
#[derive(Clone, Copy)]
pub enum SyllableType {
ConsonantSyllable = 0,
BrokenCluster,
NonKhmerCluster,
}
pub fn find_syllables_khmer(buffer: &mut hb_buffer_t) {
let mut cs = 0;
let mut ts = 0;
let mut te = 0;
let mut act = 0;
let mut p = 0;
let pe = buffer.len;
let eof = buffer.len;
let mut syllable_serial = 1u8;
macro_rules! found_syllable {
($kind:expr) => {{
found_syllable(ts, te, &mut syllable_serial, $kind, buffer);
}};
}
{
cs = (khmer_syllable_machine_start) as i32;
ts = 0;
te = 0;
act = 0;
}
{
let mut _trans = 0;
let mut _keys: i32 = 0;
let mut _inds: i32 = 0;
let mut _ic = 0;
'_resume: while (p != pe || p == eof) {
'_again: while (true) {
match (_khmer_syllable_machine_from_state_actions[(cs) as usize]) {
7 => {
ts = p;
}
_ => {}
}
if (p == eof) {
{
if (_khmer_syllable_machine_eof_trans[(cs) as usize] > 0) {
{
_trans =
(_khmer_syllable_machine_eof_trans[(cs) as usize]) as u32 - 1;
}
}
}
} else {
{
_keys = (cs << 1) as i32;
_inds = (_khmer_syllable_machine_index_offsets[(cs) as usize]) as i32;
if ((buffer.info[p].indic_category() as u8) <= 29
&& (buffer.info[p].indic_category() as u8) >= 1)
{
{
_ic = (_khmer_syllable_machine_char_class
[((buffer.info[p].indic_category() as u8) as i32 - 1) as usize])
as i32;
if (_ic
<= (_khmer_syllable_machine_trans_keys[(_keys + 1) as usize])
as i32
&& _ic
>= (_khmer_syllable_machine_trans_keys[(_keys) as usize])
as i32)
{
_trans = (_khmer_syllable_machine_indices[(_inds
+ (_ic
- (_khmer_syllable_machine_trans_keys[(_keys) as usize])
as i32)
as i32)
as usize])
as u32;
} else {
_trans = (_khmer_syllable_machine_index_defaults[(cs) as usize])
as u32;
}
}
} else {
{
_trans =
(_khmer_syllable_machine_index_defaults[(cs) as usize]) as u32;
}
}
}
}
cs = (_khmer_syllable_machine_cond_targs[(_trans) as usize]) as i32;
if (_khmer_syllable_machine_cond_actions[(_trans) as usize] != 0) {
{
match (_khmer_syllable_machine_cond_actions[(_trans) as usize]) {
2 => {
te = p + 1;
}
8 => {
te = p + 1;
{
found_syllable!(SyllableType::NonKhmerCluster);
}
}
10 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::ConsonantSyllable);
}
}
12 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::BrokenCluster);
}
}
11 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::NonKhmerCluster);
}
}
1 => {
p = (te) - 1;
{
found_syllable!(SyllableType::ConsonantSyllable);
}
}
5 => {
p = (te) - 1;
{
found_syllable!(SyllableType::BrokenCluster);
}
}
3 => match (act) {
2 => {
p = (te) - 1;
{
found_syllable!(SyllableType::BrokenCluster);
}
}
3 => {
p = (te) - 1;
{
found_syllable!(SyllableType::NonKhmerCluster);
}
}
_ => {}
},
4 => {
{
{
te = p + 1;
}
}
{
{
act = 2;
}
}
}
9 => {
{
{
te = p + 1;
}
}
{
{
act = 3;
}
}
}
_ => {}
}
}
}
break '_again;
}
if (p == eof) {
{
if (cs >= 20) {
break '_resume;
}
}
} else {
{
match (_khmer_syllable_machine_to_state_actions[(cs) as usize]) {
6 => {
ts = 0;
}
_ => {}
}
p += 1;
continue '_resume;
}
}
break '_resume;
}
}
}
#[inline]
fn found_syllable(
start: usize,
end: usize,
syllable_serial: &mut u8,
kind: SyllableType,
buffer: &mut hb_buffer_t,
) {
for i in start..end {
buffer.info[i].set_syllable((*syllable_serial << 4) | kind as u8);
}
*syllable_serial += 1;
if *syllable_serial == 16 {
*syllable_serial = 1;
}
}

View File

@@ -0,0 +1,327 @@
use super::buffer::hb_buffer_t;
use super::ot_map::*;
use super::ot_shape::*;
use super::ot_shape_complex::*;
use super::ot_shape_complex_indic::{category, position};
use super::ot_shape_normalize::*;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::{hb_font_t, hb_glyph_info_t, hb_tag_t};
pub const MYANMAR_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
collect_features: Some(collect_features),
override_features: None,
create_data: None,
preprocess_text: None,
postprocess_glyphs: None,
normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT,
decompose: None,
compose: None,
setup_masks: Some(setup_masks),
gpos_tag: None,
reorder_marks: None,
zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_EARLY,
fallback_position: false,
};
// Ugly Zawgyi encoding.
// Disable all auto processing.
// https://github.com/harfbuzz/harfbuzz/issues/1162
pub const MYANMAR_ZAWGYI_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
collect_features: None,
override_features: None,
create_data: None,
preprocess_text: None,
postprocess_glyphs: None,
normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_NONE,
decompose: None,
compose: None,
setup_masks: None,
gpos_tag: None,
reorder_marks: None,
zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE,
fallback_position: false,
};
const MYANMAR_FEATURES: &[hb_tag_t] = &[
// Basic features.
// These features are applied in order, one at a time, after reordering,
// constrained to the syllable.
hb_tag_t::from_bytes(b"rphf"),
hb_tag_t::from_bytes(b"pref"),
hb_tag_t::from_bytes(b"blwf"),
hb_tag_t::from_bytes(b"pstf"),
// Other features.
// These features are applied all at once after clearing syllables.
hb_tag_t::from_bytes(b"pres"),
hb_tag_t::from_bytes(b"abvs"),
hb_tag_t::from_bytes(b"blws"),
hb_tag_t::from_bytes(b"psts"),
];
impl hb_glyph_info_t {
fn set_myanmar_properties(&mut self) {
let u = self.glyph_id;
let (mut cat, mut pos) = crate::hb::ot_shape_complex_indic::get_category_and_position(u);
// Myanmar
// https://docs.microsoft.com/en-us/typography/script-development/myanmar#analyze
if (0xFE00..=0xFE0F).contains(&u) {
cat = category::VS;
}
match u {
// The spec says C, IndicSyllableCategory doesn't have.
0x104E => cat = category::C,
0x002D | 0x00A0 | 0x00D7 | 0x2012 | 0x2013 | 0x2014 | 0x2015 | 0x2022 | 0x25CC
| 0x25FB | 0x25FC | 0x25FD | 0x25FE => cat = category::PLACEHOLDER,
0x1004 | 0x101B | 0x105A => cat = category::RA,
0x1032 | 0x1036 => cat = category::A,
0x1039 => cat = category::H,
0x103A => cat = category::SYMBOL,
0x1041 | 0x1042 | 0x1043 | 0x1044 | 0x1045 | 0x1046 | 0x1047 | 0x1048 | 0x1049
| 0x1090 | 0x1091 | 0x1092 | 0x1093 | 0x1094 | 0x1095 | 0x1096 | 0x1097 | 0x1098
| 0x1099 => cat = category::D,
// XXX The spec says D0, but Uniscribe doesn't seem to do.
0x1040 => cat = category::D,
0x103E => cat = category::X_GROUP,
0x1060 => cat = category::ML,
0x103C => cat = category::Y_GROUP,
0x103D | 0x1082 => cat = category::MW,
0x103B | 0x105E | 0x105F => cat = category::MY,
0x1063 | 0x1064 | 0x1069 | 0x106A | 0x106B | 0x106C | 0x106D | 0xAA7B => {
cat = category::PT
}
0x1038 | 0x1087 | 0x1088 | 0x1089 | 0x108A | 0x108B | 0x108C | 0x108D | 0x108F
| 0x109A | 0x109B | 0x109C => cat = category::SM,
0x104A | 0x104B => cat = category::P,
// https://github.com/harfbuzz/harfbuzz/issues/218
0xAA74 | 0xAA75 | 0xAA76 => cat = category::C,
_ => {}
}
// Re-assign position.
if cat == category::M {
match pos {
position::PRE_C => {
cat = category::V_PRE;
pos = position::PRE_M;
}
position::BELOW_C => cat = category::V_BLW,
position::ABOVE_C => cat = category::V_AVB,
position::POST_C => cat = category::V_PST,
_ => {}
}
}
self.set_indic_category(cat);
self.set_indic_position(pos);
}
}
fn collect_features(planner: &mut hb_ot_shape_planner_t) {
// Do this before any lookups have been applied.
planner.ot_map.add_gsub_pause(Some(setup_syllables));
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"locl"), F_PER_SYLLABLE, 1);
// The Indic specs do not require ccmp, but we apply it here since if
// there is a use of it, it's typically at the beginning.
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"ccmp"), F_PER_SYLLABLE, 1);
planner.ot_map.add_gsub_pause(Some(reorder));
for feature in MYANMAR_FEATURES.iter().take(4) {
planner.ot_map.enable_feature(*feature, F_MANUAL_ZWJ, 1);
planner.ot_map.add_gsub_pause(None);
}
for feature in MYANMAR_FEATURES.iter().skip(4) {
planner
.ot_map
.enable_feature(*feature, F_MANUAL_ZWJ | F_PER_SYLLABLE, 1);
}
}
fn setup_syllables(_: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
super::ot_shape_complex_myanmar_machine::find_syllables_myanmar(buffer);
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
buffer.unsafe_to_break(Some(start), Some(end));
start = end;
end = buffer.next_syllable(start);
}
}
fn reorder(_: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
use super::ot_shape_complex_myanmar_machine::SyllableType;
super::ot_shape_complex_syllabic::insert_dotted_circles(
face,
buffer,
SyllableType::BrokenCluster as u8,
category::PLACEHOLDER,
None,
None,
);
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
reorder_syllable(start, end, buffer);
start = end;
end = buffer.next_syllable(start);
}
}
fn reorder_syllable(start: usize, end: usize, buffer: &mut hb_buffer_t) {
use super::ot_shape_complex_myanmar_machine::SyllableType;
let syllable_type = match buffer.info[start].syllable() & 0x0F {
0 => SyllableType::ConsonantSyllable,
1 => SyllableType::PunctuationCluster,
2 => SyllableType::BrokenCluster,
3 => SyllableType::NonMyanmarCluster,
_ => unreachable!(),
};
match syllable_type {
// We already inserted dotted-circles, so just call the consonant_syllable.
SyllableType::ConsonantSyllable | SyllableType::BrokenCluster => {
initial_reordering_consonant_syllable(start, end, buffer);
}
SyllableType::PunctuationCluster | SyllableType::NonMyanmarCluster => {}
}
}
// Rules from:
// https://docs.microsoft.com/en-us/typography/script-development/myanmar
fn initial_reordering_consonant_syllable(start: usize, end: usize, buffer: &mut hb_buffer_t) {
let mut base = end;
let mut has_reph = false;
{
let mut limit = start;
if start + 3 <= end
&& buffer.info[start + 0].indic_category() == category::RA
&& buffer.info[start + 1].indic_category() == category::SYMBOL
&& buffer.info[start + 2].indic_category() == category::H
{
limit += 3;
base = start;
has_reph = true;
}
{
if !has_reph {
base = limit;
}
for i in limit..end {
if buffer.info[i].is_consonant() {
base = i;
break;
}
}
}
}
// Reorder!
{
let mut i = start;
while i < start + if has_reph { 3 } else { 0 } {
buffer.info[i].set_indic_position(position::AFTER_MAIN);
i += 1;
}
while i < base {
buffer.info[i].set_indic_position(position::PRE_C);
i += 1;
}
if i < end {
buffer.info[i].set_indic_position(position::BASE_C);
i += 1;
}
let mut pos = position::AFTER_MAIN;
// The following loop may be ugly, but it implements all of
// Myanmar reordering!
for i in i..end {
// Pre-base reordering
if buffer.info[i].indic_category() == category::Y_GROUP {
buffer.info[i].set_indic_position(position::PRE_C);
continue;
}
// Left matra
if buffer.info[i].indic_position() < position::BASE_C {
continue;
}
if buffer.info[i].indic_category() == category::VS {
let t = buffer.info[i - 1].indic_position();
buffer.info[i].set_indic_position(t);
continue;
}
if pos == position::AFTER_MAIN && buffer.info[i].indic_category() == category::V_BLW {
pos = position::BELOW_C;
buffer.info[i].set_indic_position(pos);
continue;
}
if pos == position::BELOW_C && buffer.info[i].indic_category() == category::A {
buffer.info[i].set_indic_position(position::BEFORE_SUB);
continue;
}
if pos == position::BELOW_C && buffer.info[i].indic_category() == category::V_BLW {
buffer.info[i].set_indic_position(pos);
continue;
}
if pos == position::BELOW_C && buffer.info[i].indic_category() != category::A {
pos = position::AFTER_SUB;
buffer.info[i].set_indic_position(pos);
continue;
}
buffer.info[i].set_indic_position(pos);
}
}
buffer.sort(start, end, |a, b| {
a.indic_position().cmp(&b.indic_position()) == core::cmp::Ordering::Greater
});
}
fn setup_masks(_: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
// No masks, we just save information about characters.
for info in buffer.info_slice_mut() {
info.set_myanmar_properties();
}
}

View File

@@ -0,0 +1,129 @@
#![allow(
dead_code,
non_upper_case_globals,
unused_assignments,
unused_parens,
while_true,
clippy::assign_op_pattern,
clippy::collapsible_if,
clippy::comparison_chain,
clippy::double_parens,
clippy::unnecessary_cast,
clippy::single_match,
clippy::never_loop
)]
use super::buffer::hb_buffer_t;
%%{
machine myanmar_syllable_machine;
alphtype u8;
write data;
}%%
%%{
A = 10;
As = 18;
C = 1;
D = 32;
D0 = 20;
DB = 3;
GB = 11;
H = 4;
IV = 2;
MH = 21;
ML = 33;
MR = 22;
MW = 23;
MY = 24;
PT = 25;
V = 8;
VAbv = 26;
VBlw = 27;
VPre = 28;
VPst = 29;
VS = 30;
ZWJ = 6;
ZWNJ = 5;
Ra = 16;
P = 31;
CS = 19;
j = ZWJ|ZWNJ; # Joiners
k = (Ra As H); # Kinzi
c = C|Ra; # is_consonant
medial_group = MY? As? MR? ((MW MH? ML? | MH ML? | ML) As?)?;
main_vowel_group = (VPre.VS?)* VAbv* VBlw* A* (DB As?)?;
post_vowel_group = VPst MH? ML? As* VAbv* A* (DB As?)?;
pwo_tone_group = PT A* DB? As?;
complex_syllable_tail = As* medial_group main_vowel_group post_vowel_group* pwo_tone_group* V* j?;
syllable_tail = (H (c|IV).VS?)* (H | complex_syllable_tail);
consonant_syllable = (k|CS)? (c|IV|D|GB).VS? syllable_tail;
punctuation_cluster = P V;
broken_cluster = k? VS? syllable_tail;
other = any;
main := |*
consonant_syllable => { found_syllable!(SyllableType::ConsonantSyllable); };
j => { found_syllable!(SyllableType::NonMyanmarCluster); };
punctuation_cluster => { found_syllable!(SyllableType::PunctuationCluster); };
broken_cluster => { found_syllable!(SyllableType::BrokenCluster); };
other => { found_syllable!(SyllableType::NonMyanmarCluster); };
*|;
}%%
#[derive(Clone, Copy)]
pub enum SyllableType {
ConsonantSyllable = 0,
PunctuationCluster,
BrokenCluster,
NonMyanmarCluster,
}
pub fn find_syllables_myanmar(buffer: &mut hb_buffer_t) {
let mut cs = 0;
let mut ts = 0;
let mut te;
let mut p = 0;
let pe = buffer.len;
let eof = buffer.len;
let mut syllable_serial = 1u8;
macro_rules! found_syllable {
($kind:expr) => {{
found_syllable(ts, te, &mut syllable_serial, $kind, buffer);
}}
}
%%{
write init;
getkey (buffer.info[p].indic_category() as u8);
write exec;
}%%
}
#[inline]
fn found_syllable(
start: usize,
end: usize,
syllable_serial: &mut u8,
kind: SyllableType,
buffer: &mut hb_buffer_t,
) {
for i in start..end {
buffer.info[i].set_syllable((*syllable_serial << 4) | kind as u8);
}
*syllable_serial += 1;
if *syllable_serial == 16 {
*syllable_serial = 1;
}
}

View File

@@ -0,0 +1,310 @@
// This file is autogenerated. Do not edit it!
//
// See docs/ragel.md for details.
#![allow(
dead_code,
non_upper_case_globals,
unused_assignments,
unused_parens,
while_true,
clippy::assign_op_pattern,
clippy::collapsible_if,
clippy::comparison_chain,
clippy::double_parens,
clippy::unnecessary_cast,
clippy::single_match,
clippy::never_loop
)]
use super::buffer::hb_buffer_t;
static _myanmar_syllable_machine_trans_keys: [u8; 114] = [
0, 22, 1, 22, 3, 19, 3, 5, 3, 19, 1, 15, 3, 15, 3, 15, 1, 22, 1, 19, 1, 19, 1, 19, 1, 22, 0, 8,
1, 22, 1, 22, 1, 19, 1, 19, 1, 19, 1, 20, 1, 19, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 3, 19, 3,
5, 3, 19, 1, 15, 3, 15, 3, 15, 1, 22, 1, 19, 1, 19, 1, 19, 1, 22, 0, 8, 1, 22, 1, 22, 1, 22, 1,
19, 1, 19, 1, 19, 1, 20, 1, 19, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 0, 22, 0, 8,
5, 5, 0, 0,
];
static _myanmar_syllable_machine_char_class: [i8; 35] = [
0, 0, 1, 2, 3, 3, 4, 5, 4, 6, 7, 4, 4, 4, 4, 8, 4, 9, 10, 4, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 7, 22, 0, 0,
];
static _myanmar_syllable_machine_index_offsets: [i16; 58] = [
0, 23, 45, 62, 65, 82, 97, 110, 123, 145, 164, 183, 202, 224, 233, 255, 277, 296, 315, 334,
354, 373, 395, 417, 439, 461, 483, 500, 503, 520, 535, 548, 561, 583, 602, 621, 640, 662, 671,
693, 715, 737, 756, 775, 794, 814, 833, 855, 877, 899, 921, 943, 965, 987, 1010, 1019, 0, 0,
];
static _myanmar_syllable_machine_indices: [i8; 1022] = [
2, 3, 4, 5, 1, 6, 7, 2, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26,
23, 27, 28, 23, 23, 29, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 23, 40, 26, 23, 27, 23, 23,
23, 41, 23, 23, 23, 23, 23, 34, 23, 23, 23, 38, 26, 23, 27, 26, 23, 27, 23, 23, 23, 23, 23, 23,
23, 23, 23, 34, 23, 23, 23, 38, 42, 23, 26, 23, 27, 34, 23, 23, 43, 23, 23, 23, 23, 23, 34, 26,
23, 27, 23, 23, 23, 43, 23, 23, 23, 23, 23, 34, 26, 23, 27, 23, 23, 23, 23, 23, 23, 23, 23, 23,
34, 24, 23, 26, 23, 27, 28, 23, 23, 44, 23, 45, 23, 23, 23, 34, 46, 23, 23, 38, 23, 23, 44, 24,
23, 26, 23, 27, 28, 23, 23, 23, 23, 23, 23, 23, 23, 34, 23, 23, 23, 38, 24, 23, 26, 23, 27, 28,
23, 23, 44, 23, 23, 23, 23, 23, 34, 46, 23, 23, 38, 24, 23, 26, 23, 27, 28, 23, 23, 23, 23, 23,
23, 23, 23, 34, 46, 23, 23, 38, 24, 23, 26, 23, 27, 28, 23, 23, 44, 23, 23, 23, 23, 23, 34, 46,
23, 23, 38, 23, 23, 44, 2, 23, 23, 23, 23, 23, 23, 23, 2, 24, 23, 26, 23, 27, 28, 23, 23, 29,
23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 23, 23, 40, 24, 23, 26, 23, 27, 28, 23, 23, 47, 23, 23,
23, 23, 23, 34, 35, 36, 37, 38, 23, 23, 40, 24, 23, 26, 23, 27, 28, 23, 23, 23, 23, 23, 23, 23,
23, 34, 35, 36, 37, 38, 24, 23, 26, 23, 27, 28, 23, 23, 23, 23, 23, 23, 23, 23, 34, 35, 36, 23,
38, 24, 23, 26, 23, 27, 28, 23, 23, 23, 23, 23, 23, 23, 23, 34, 23, 36, 23, 38, 24, 23, 26, 23,
27, 28, 23, 23, 23, 23, 23, 23, 23, 23, 34, 35, 36, 37, 38, 47, 24, 23, 26, 23, 27, 28, 23, 23,
47, 23, 23, 23, 23, 23, 34, 35, 36, 37, 38, 24, 23, 26, 23, 27, 28, 23, 23, 23, 23, 30, 23, 32,
23, 34, 35, 36, 37, 38, 23, 23, 40, 24, 23, 26, 23, 27, 28, 23, 23, 47, 23, 30, 23, 23, 23, 34,
35, 36, 37, 38, 23, 23, 40, 24, 23, 26, 23, 27, 28, 23, 23, 48, 23, 30, 31, 32, 23, 34, 35, 36,
37, 38, 23, 23, 40, 24, 23, 26, 23, 27, 28, 23, 23, 23, 23, 30, 31, 32, 23, 34, 35, 36, 37, 38,
23, 23, 40, 24, 25, 26, 23, 27, 28, 23, 23, 29, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 23, 23,
40, 50, 49, 6, 49, 49, 49, 51, 49, 49, 49, 49, 49, 15, 49, 49, 49, 19, 50, 49, 6, 50, 49, 6,
49, 49, 49, 49, 49, 49, 49, 49, 49, 15, 49, 49, 49, 19, 52, 49, 50, 49, 6, 15, 49, 49, 53, 49,
49, 49, 49, 49, 15, 50, 49, 6, 49, 49, 49, 53, 49, 49, 49, 49, 49, 15, 50, 49, 6, 49, 49, 49,
49, 49, 49, 49, 49, 49, 15, 3, 49, 50, 49, 6, 7, 49, 49, 54, 49, 55, 49, 49, 49, 15, 56, 49,
49, 19, 49, 49, 54, 3, 49, 50, 49, 6, 7, 49, 49, 49, 49, 49, 49, 49, 49, 15, 49, 49, 49, 19, 3,
49, 50, 49, 6, 7, 49, 49, 54, 49, 49, 49, 49, 49, 15, 56, 49, 49, 19, 3, 49, 50, 49, 6, 7, 49,
49, 49, 49, 49, 49, 49, 49, 15, 56, 49, 49, 19, 3, 49, 50, 49, 6, 7, 49, 49, 54, 49, 49, 49,
49, 49, 15, 56, 49, 49, 19, 49, 49, 54, 57, 49, 49, 49, 49, 49, 49, 49, 57, 3, 4, 50, 49, 6, 7,
49, 49, 9, 49, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 49, 22, 3, 49, 50, 49, 6, 7, 49, 49, 9,
49, 11, 12, 13, 14, 15, 16, 17, 18, 19, 49, 49, 22, 3, 49, 50, 49, 6, 7, 49, 49, 58, 49, 49,
49, 49, 49, 15, 16, 17, 18, 19, 49, 49, 22, 3, 49, 50, 49, 6, 7, 49, 49, 49, 49, 49, 49, 49,
49, 15, 16, 17, 18, 19, 3, 49, 50, 49, 6, 7, 49, 49, 49, 49, 49, 49, 49, 49, 15, 16, 17, 49,
19, 3, 49, 50, 49, 6, 7, 49, 49, 49, 49, 49, 49, 49, 49, 15, 49, 17, 49, 19, 3, 49, 50, 49, 6,
7, 49, 49, 49, 49, 49, 49, 49, 49, 15, 16, 17, 18, 19, 58, 3, 49, 50, 49, 6, 7, 49, 49, 58, 49,
49, 49, 49, 49, 15, 16, 17, 18, 19, 3, 49, 50, 49, 6, 7, 49, 49, 49, 49, 11, 49, 13, 49, 15,
16, 17, 18, 19, 49, 49, 22, 3, 49, 50, 49, 6, 7, 49, 49, 58, 49, 11, 49, 49, 49, 15, 16, 17,
18, 19, 49, 49, 22, 3, 49, 50, 49, 6, 7, 49, 49, 59, 49, 11, 12, 13, 49, 15, 16, 17, 18, 19,
49, 49, 22, 3, 49, 50, 49, 6, 7, 49, 49, 49, 49, 11, 12, 13, 49, 15, 16, 17, 18, 19, 49, 49,
22, 3, 4, 50, 49, 6, 7, 49, 49, 9, 49, 11, 12, 13, 14, 15, 16, 17, 18, 19, 49, 49, 22, 24, 25,
26, 23, 27, 28, 23, 23, 60, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 23, 40, 24, 61, 26, 23,
27, 28, 23, 23, 29, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 23, 23, 40, 2, 3, 4, 50, 49, 6, 7,
2, 2, 9, 49, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 49, 22, 2, 62, 62, 62, 62, 62, 62, 2, 2,
63, 0, 0,
];
static _myanmar_syllable_machine_index_defaults: [i8; 58] = [
1, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
49, 49, 49, 23, 23, 49, 62, 62, 0, 0,
];
static _myanmar_syllable_machine_cond_targs: [i8; 66] = [
0, 0, 1, 26, 37, 0, 27, 33, 51, 39, 54, 40, 46, 47, 48, 29, 42, 43, 44, 32, 50, 55, 45, 0, 2,
13, 0, 3, 9, 14, 15, 21, 22, 23, 5, 17, 18, 19, 8, 25, 20, 4, 6, 7, 10, 12, 11, 16, 24, 0, 0,
28, 30, 31, 34, 36, 35, 38, 41, 49, 52, 53, 0, 0, 0, 0,
];
static _myanmar_syllable_machine_cond_actions: [i8; 66] = [
0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 6, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9,
10, 0, 0,
];
static _myanmar_syllable_machine_to_state_actions: [i8; 58] = [
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
static _myanmar_syllable_machine_from_state_actions: [i8; 58] = [
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
static _myanmar_syllable_machine_eof_trans: [i8; 58] = [
1, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
50, 50, 50, 24, 24, 50, 63, 63, 0, 0,
];
static myanmar_syllable_machine_start: i32 = 0;
static myanmar_syllable_machine_first_final: i32 = 0;
static myanmar_syllable_machine_error: i32 = -1;
static myanmar_syllable_machine_en_main: i32 = 0;
#[derive(Clone, Copy)]
pub enum SyllableType {
ConsonantSyllable = 0,
PunctuationCluster,
BrokenCluster,
NonMyanmarCluster,
}
pub fn find_syllables_myanmar(buffer: &mut hb_buffer_t) {
let mut cs = 0;
let mut ts = 0;
let mut te;
let mut p = 0;
let pe = buffer.len;
let eof = buffer.len;
let mut syllable_serial = 1u8;
macro_rules! found_syllable {
($kind:expr) => {{
found_syllable(ts, te, &mut syllable_serial, $kind, buffer);
}};
}
{
cs = (myanmar_syllable_machine_start) as i32;
ts = 0;
te = 0;
}
{
let mut _trans = 0;
let mut _keys: i32 = 0;
let mut _inds: i32 = 0;
let mut _ic = 0;
'_resume: while (p != pe || p == eof) {
'_again: while (true) {
match (_myanmar_syllable_machine_from_state_actions[(cs) as usize]) {
2 => {
ts = p;
}
_ => {}
}
if (p == eof) {
{
if (_myanmar_syllable_machine_eof_trans[(cs) as usize] > 0) {
{
_trans =
(_myanmar_syllable_machine_eof_trans[(cs) as usize]) as u32 - 1;
}
}
}
} else {
{
_keys = (cs << 1) as i32;
_inds = (_myanmar_syllable_machine_index_offsets[(cs) as usize]) as i32;
if ((buffer.info[p].indic_category() as u8) <= 33
&& (buffer.info[p].indic_category() as u8) >= 1)
{
{
_ic = (_myanmar_syllable_machine_char_class
[((buffer.info[p].indic_category() as u8) as i32 - 1) as usize])
as i32;
if (_ic
<= (_myanmar_syllable_machine_trans_keys[(_keys + 1) as usize])
as i32
&& _ic
>= (_myanmar_syllable_machine_trans_keys[(_keys) as usize])
as i32)
{
_trans = (_myanmar_syllable_machine_indices[(_inds
+ (_ic
- (_myanmar_syllable_machine_trans_keys
[(_keys) as usize])
as i32)
as i32)
as usize])
as u32;
} else {
_trans = (_myanmar_syllable_machine_index_defaults
[(cs) as usize])
as u32;
}
}
} else {
{
_trans = (_myanmar_syllable_machine_index_defaults[(cs) as usize])
as u32;
}
}
}
}
cs = (_myanmar_syllable_machine_cond_targs[(_trans) as usize]) as i32;
if (_myanmar_syllable_machine_cond_actions[(_trans) as usize] != 0) {
{
match (_myanmar_syllable_machine_cond_actions[(_trans) as usize]) {
6 => {
te = p + 1;
{
found_syllable!(SyllableType::ConsonantSyllable);
}
}
4 => {
te = p + 1;
{
found_syllable!(SyllableType::NonMyanmarCluster);
}
}
10 => {
te = p + 1;
{
found_syllable!(SyllableType::PunctuationCluster);
}
}
8 => {
te = p + 1;
{
found_syllable!(SyllableType::BrokenCluster);
}
}
3 => {
te = p + 1;
{
found_syllable!(SyllableType::NonMyanmarCluster);
}
}
5 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::ConsonantSyllable);
}
}
7 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::BrokenCluster);
}
}
9 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::NonMyanmarCluster);
}
}
_ => {}
}
}
}
break '_again;
}
if (p == eof) {
{
if (cs >= 0) {
break '_resume;
}
}
} else {
{
match (_myanmar_syllable_machine_to_state_actions[(cs) as usize]) {
1 => {
ts = 0;
}
_ => {}
}
p += 1;
continue '_resume;
}
}
break '_resume;
}
}
}
#[inline]
fn found_syllable(
start: usize,
end: usize,
syllable_serial: &mut u8,
kind: SyllableType,
buffer: &mut hb_buffer_t,
) {
for i in start..end {
buffer.info[i].set_syllable((*syllable_serial << 4) | kind as u8);
}
*syllable_serial += 1;
if *syllable_serial == 16 {
*syllable_serial = 1;
}
}

View File

@@ -0,0 +1,77 @@
use super::buffer::hb_buffer_t;
use super::{hb_font_t, hb_glyph_info_t};
use crate::BufferFlags;
pub fn insert_dotted_circles(
face: &hb_font_t,
buffer: &mut hb_buffer_t,
broken_syllable_type: u8,
dottedcircle_category: u8,
repha_category: Option<u8>,
dottedcircle_position: Option<u8>,
) {
if buffer
.flags
.contains(BufferFlags::DO_NOT_INSERT_DOTTED_CIRCLE)
{
return;
}
// Note: This loop is extra overhead, but should not be measurable.
// TODO Use a buffer scratch flag to remove the loop.
let has_broken_syllables = buffer
.info_slice()
.iter()
.any(|info| info.syllable() & 0x0F == broken_syllable_type);
if !has_broken_syllables {
return;
}
let dottedcircle_glyph = match face.get_nominal_glyph(0x25CC) {
Some(g) => g.0 as u32,
None => return,
};
let mut dottedcircle = hb_glyph_info_t {
glyph_id: 0x25CC,
..hb_glyph_info_t::default()
};
dottedcircle.set_complex_var_u8_category(dottedcircle_category);
if let Some(dottedcircle_position) = dottedcircle_position {
dottedcircle.set_complex_var_u8_auxiliary(dottedcircle_position);
}
dottedcircle.glyph_id = dottedcircle_glyph;
buffer.clear_output();
buffer.idx = 0;
let mut last_syllable = 0;
while buffer.idx < buffer.len {
let syllable = buffer.cur(0).syllable();
if last_syllable != syllable && (syllable & 0x0F) == broken_syllable_type {
last_syllable = syllable;
let mut ginfo = dottedcircle;
ginfo.cluster = buffer.cur(0).cluster;
ginfo.mask = buffer.cur(0).mask;
ginfo.set_syllable(buffer.cur(0).syllable());
// Insert dottedcircle after possible Repha.
if let Some(repha_category) = repha_category {
while buffer.idx < buffer.len
&& last_syllable == buffer.cur(0).syllable()
&& buffer.cur(0).complex_var_u8_category() == repha_category
{
buffer.next_glyph();
}
}
buffer.output_info(ginfo);
} else {
buffer.next_glyph();
}
}
buffer.sync();
}

View File

@@ -0,0 +1,428 @@
use super::buffer::*;
use super::ot_layout::*;
use super::ot_shape_complex::*;
use super::ot_shape_normalize::HB_OT_SHAPE_NORMALIZATION_MODE_AUTO;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::unicode::hb_unicode_general_category_t;
use super::{hb_font_t, script};
pub const THAI_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
collect_features: None,
override_features: None,
create_data: None,
preprocess_text: Some(preprocess_text),
postprocess_glyphs: None,
normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_AUTO,
decompose: None,
compose: None,
setup_masks: None,
gpos_tag: None,
reorder_marks: None,
zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE,
fallback_position: false,
};
#[derive(Clone, Copy, PartialEq)]
enum Consonant {
NC = 0,
AC,
RC,
DC,
NotConsonant,
}
fn get_consonant_type(u: u32) -> Consonant {
match u {
0x0E1B | 0x0E1D | 0x0E1F => Consonant::AC,
0x0E0D | 0x0E10 => Consonant::RC,
0x0E0E | 0x0E0F => Consonant::DC,
0x0E01..=0x0E2E => Consonant::NC,
_ => Consonant::NotConsonant,
}
}
#[derive(Clone, Copy, PartialEq)]
enum Mark {
AV,
BV,
T,
NotMark,
}
fn get_mark_type(u: u32) -> Mark {
match u {
0x0E31 | 0x0E34..=0x0E37 | 0x0E47 | 0x0E4D..=0x0E4E => Mark::AV,
0x0E38..=0x0E3A => Mark::BV,
0x0E48..=0x0E4C => Mark::T,
_ => Mark::NotMark,
}
}
#[derive(Clone, Copy, PartialEq)]
enum Action {
NOP,
/// Shift combining-mark down.
SD,
/// Shift combining-mark left.
SL,
/// Shift combining-mark down-left.
SDL,
/// Remove descender from base.
RD,
}
#[derive(Clone, Copy)]
struct PuaMapping {
u: u32,
win_pua: u32,
mac_pua: u32,
}
impl PuaMapping {
const fn new(u: u32, win_pua: u32, mac_pua: u32) -> Self {
PuaMapping {
u,
win_pua,
mac_pua,
}
}
}
const SD_MAPPINGS: &[PuaMapping] = &[
PuaMapping::new(0x0E48, 0xF70A, 0xF88B), // MAI EK
PuaMapping::new(0x0E49, 0xF70B, 0xF88E), // MAI THO
PuaMapping::new(0x0E4A, 0xF70C, 0xF891), // MAI TRI
PuaMapping::new(0x0E4B, 0xF70D, 0xF894), // MAI CHATTAWA
PuaMapping::new(0x0E4C, 0xF70E, 0xF897), // THANTHAKHAT
PuaMapping::new(0x0E38, 0xF718, 0xF89B), // SARA U
PuaMapping::new(0x0E39, 0xF719, 0xF89C), // SARA UU
PuaMapping::new(0x0E3A, 0xF71A, 0xF89D), // PHINTHU
PuaMapping::new(0x0000, 0x0000, 0x0000),
];
const SDL_MAPPINGS: &[PuaMapping] = &[
PuaMapping::new(0x0E48, 0xF705, 0xF88C), // MAI EK
PuaMapping::new(0x0E49, 0xF706, 0xF88F), // MAI THO
PuaMapping::new(0x0E4A, 0xF707, 0xF892), // MAI TRI
PuaMapping::new(0x0E4B, 0xF708, 0xF895), // MAI CHATTAWA
PuaMapping::new(0x0E4C, 0xF709, 0xF898), // THANTHAKHAT
PuaMapping::new(0x0000, 0x0000, 0x0000),
];
const SL_MAPPINGS: &[PuaMapping] = &[
PuaMapping::new(0x0E48, 0xF713, 0xF88A), // MAI EK
PuaMapping::new(0x0E49, 0xF714, 0xF88D), // MAI THO
PuaMapping::new(0x0E4A, 0xF715, 0xF890), // MAI TRI
PuaMapping::new(0x0E4B, 0xF716, 0xF893), // MAI CHATTAWA
PuaMapping::new(0x0E4C, 0xF717, 0xF896), // THANTHAKHAT
PuaMapping::new(0x0E31, 0xF710, 0xF884), // MAI HAN-AKAT
PuaMapping::new(0x0E34, 0xF701, 0xF885), // SARA I
PuaMapping::new(0x0E35, 0xF702, 0xF886), // SARA II
PuaMapping::new(0x0E36, 0xF703, 0xF887), // SARA UE
PuaMapping::new(0x0E37, 0xF704, 0xF888), // SARA UEE
PuaMapping::new(0x0E47, 0xF712, 0xF889), // MAITAIKHU
PuaMapping::new(0x0E4D, 0xF711, 0xF899), // NIKHAHIT
PuaMapping::new(0x0000, 0x0000, 0x0000),
];
const RD_MAPPINGS: &[PuaMapping] = &[
PuaMapping::new(0x0E0D, 0xF70F, 0xF89A), // YO YING
PuaMapping::new(0x0E10, 0xF700, 0xF89E), // THO THAN
PuaMapping::new(0x0000, 0x0000, 0x0000),
];
fn pua_shape(u: u32, action: Action, face: &hb_font_t) -> u32 {
let mappings = match action {
Action::NOP => return u,
Action::SD => SD_MAPPINGS,
Action::SL => SL_MAPPINGS,
Action::SDL => SDL_MAPPINGS,
Action::RD => RD_MAPPINGS,
};
for m in mappings {
if m.u == u {
if face.get_nominal_glyph(m.win_pua).is_some() {
return m.win_pua;
}
if face.get_nominal_glyph(m.mac_pua).is_some() {
return m.mac_pua;
}
break;
}
}
u
}
#[derive(Clone, Copy)]
enum AboveState {
// Cluster above looks like:
T0, // ⣤
T1, // ⣼
T2, // ⣾
T3, // ⣿
}
const ABOVE_START_STATE: &[AboveState] = &[
AboveState::T0, // NC
AboveState::T1, // AC
AboveState::T0, // RC
AboveState::T0, // DC
AboveState::T3, // NotConsonant
];
#[derive(Clone, Copy)]
struct AboveStateMachineEdge {
action: Action,
next_state: AboveState,
}
impl AboveStateMachineEdge {
const fn new(action: Action, next_state: AboveState) -> Self {
AboveStateMachineEdge { action, next_state }
}
}
type ASME = AboveStateMachineEdge;
const ABOVE_STATE_MACHINE: &[[ASME; 3]] = &[
// AV BV T
/* T0 */
[
ASME::new(Action::NOP, AboveState::T3),
ASME::new(Action::NOP, AboveState::T0),
ASME::new(Action::SD, AboveState::T3),
],
/* T1 */
[
ASME::new(Action::SL, AboveState::T2),
ASME::new(Action::NOP, AboveState::T1),
ASME::new(Action::SDL, AboveState::T2),
],
/* T2 */
[
ASME::new(Action::NOP, AboveState::T3),
ASME::new(Action::NOP, AboveState::T2),
ASME::new(Action::SL, AboveState::T3),
],
/* T3 */
[
ASME::new(Action::NOP, AboveState::T3),
ASME::new(Action::NOP, AboveState::T3),
ASME::new(Action::NOP, AboveState::T3),
],
];
#[derive(Clone, Copy)]
enum BelowState {
/// No descender.
B0,
/// Removable descender.
B1,
/// Strict descender.
B2,
}
const BELOW_START_STATE: &[BelowState] = &[
BelowState::B0, // NC
BelowState::B0, // AC
BelowState::B1, // RC
BelowState::B2, // DC
BelowState::B2, // NotConsonant
];
#[derive(Clone, Copy)]
struct BelowStateMachineEdge {
action: Action,
next_state: BelowState,
}
impl BelowStateMachineEdge {
const fn new(action: Action, next_state: BelowState) -> Self {
BelowStateMachineEdge { action, next_state }
}
}
type BSME = BelowStateMachineEdge;
const BELOW_STATE_MACHINE: &[[BSME; 3]] = &[
// AV BV T
/* B0 */
[
BSME::new(Action::NOP, BelowState::B0),
BSME::new(Action::NOP, BelowState::B2),
BSME::new(Action::NOP, BelowState::B0),
],
/* B1 */
[
BSME::new(Action::NOP, BelowState::B1),
BSME::new(Action::RD, BelowState::B2),
BSME::new(Action::NOP, BelowState::B1),
],
/* B2 */
[
BSME::new(Action::NOP, BelowState::B2),
BSME::new(Action::SD, BelowState::B2),
BSME::new(Action::NOP, BelowState::B2),
],
];
fn do_pua_shaping(face: &hb_font_t, buffer: &mut hb_buffer_t) {
let mut above_state = ABOVE_START_STATE[Consonant::NotConsonant as usize];
let mut below_state = BELOW_START_STATE[Consonant::NotConsonant as usize];
let mut base = 0;
for i in 0..buffer.len {
let mt = get_mark_type(buffer.info[i].glyph_id);
if mt == Mark::NotMark {
let ct = get_consonant_type(buffer.info[i].glyph_id);
above_state = ABOVE_START_STATE[ct as usize];
below_state = BELOW_START_STATE[ct as usize];
base = i;
continue;
}
let above_edge = ABOVE_STATE_MACHINE[above_state as usize][mt as usize];
let below_edge = BELOW_STATE_MACHINE[below_state as usize][mt as usize];
above_state = above_edge.next_state;
below_state = below_edge.next_state;
// At least one of the above/below actions is NOP.
let action = if above_edge.action != Action::NOP {
above_edge.action
} else {
below_edge.action
};
buffer.unsafe_to_break(Some(base), Some(i));
if action == Action::RD {
buffer.info[base].glyph_id = pua_shape(buffer.info[base].glyph_id, action, face);
} else {
buffer.info[i].glyph_id = pua_shape(buffer.info[i].glyph_id, action, face);
}
}
}
// TODO: more tests
fn preprocess_text(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
// This function implements the shaping logic documented here:
//
// https://linux.thai.net/~thep/th-otf/shaping.html
//
// The first shaping rule listed there is needed even if the font has Thai
// OpenType tables. The rest do fallback positioning based on PUA codepoints.
// We implement that only if there exist no Thai GSUB in the font.
// The following is NOT specified in the MS OT Thai spec, however, it seems
// to be what Uniscribe and other engines implement. According to Eric Muller:
//
// When you have a SARA AM, decompose it in NIKHAHIT + SARA AA, *and* move the
// NIKHAHIT backwards over any tone mark (0E48-0E4B).
//
// <0E14, 0E4B, 0E33> -> <0E14, 0E4D, 0E4B, 0E32>
//
// This reordering is legit only when the NIKHAHIT comes from a SARA AM, not
// when it's there to start with. The string <0E14, 0E4B, 0E4D> is probably
// not what a user wanted, but the rendering is nevertheless nikhahit above
// chattawa.
//
// Same for Lao.
//
// Note:
//
// Uniscribe also does some below-marks reordering. Namely, it positions U+0E3A
// after U+0E38 and U+0E39. We do that by modifying the ccc for U+0E3A.
// See unicode->modified_combining_class (). Lao does NOT have a U+0E3A
// equivalent.
// Here are the characters of significance:
//
// Thai Lao
// SARA AM: U+0E33 U+0EB3
// SARA AA: U+0E32 U+0EB2
// Nikhahit: U+0E4D U+0ECD
//
// Testing shows that Uniscribe reorder the following marks:
// Thai: <0E31,0E34..0E37,0E47..0E4E>
// Lao: <0EB1,0EB4..0EB7,0EC7..0ECE>
//
// Note how the Lao versions are the same as Thai + 0x80.
// We only get one script at a time, so a script-agnostic implementation
// is adequate here.
#[inline]
fn is_sara_am(u: u32) -> bool {
(u & !0x0080) == 0x0E33
}
#[inline]
fn nikhahit_from_sara_am(u: u32) -> u32 {
u - 0x0E33 + 0x0E4D
}
#[inline]
fn sara_aa_from_sara_am(u: u32) -> u32 {
u - 1
}
#[inline]
fn is_tone_mark(u: u32) -> bool {
let u = u & !0x0080;
matches!(u, 0x0E34..=0x0E37 | 0x0E47..=0x0E4E | 0x0E31..=0x0E31)
}
buffer.clear_output();
buffer.idx = 0;
while buffer.idx < buffer.len {
let u = buffer.cur(0).glyph_id;
if !is_sara_am(u) {
buffer.next_glyph();
continue;
}
// Is SARA AM. Decompose and reorder.
buffer.output_glyph(nikhahit_from_sara_am(u));
{
let out_idx = buffer.out_len - 1;
_hb_glyph_info_set_continuation(&mut buffer.out_info_mut()[out_idx]);
}
buffer.replace_glyph(sara_aa_from_sara_am(u));
// Make Nikhahit be recognized as a ccc=0 mark when zeroing widths.
let end = buffer.out_len;
_hb_glyph_info_set_general_category(
&mut buffer.out_info_mut()[end - 2],
hb_unicode_general_category_t::NonspacingMark,
);
// Ok, let's see...
let mut start = end - 2;
while start > 0 && is_tone_mark(buffer.out_info()[start - 1].glyph_id) {
start -= 1;
}
if start + 2 < end {
// Move Nikhahit (end-2) to the beginning
buffer.merge_out_clusters(start, end);
let t = buffer.out_info()[end - 2];
for i in 0..(end - start - 2) {
buffer.out_info_mut()[i + start + 1] = buffer.out_info()[i + start];
}
buffer.out_info_mut()[start] = t;
} else {
// Since we decomposed, and NIKHAHIT is combining, merge clusters with the
// previous cluster.
if start != 0 && buffer.cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES {
buffer.merge_out_clusters(start - 1, end);
}
}
}
buffer.sync();
// If font has Thai GSUB, we are done.
if plan.script == Some(script::THAI) && !plan.ot_map.found_script(TableIndex::GSUB) {
do_pua_shaping(face, buffer);
}
}

View File

@@ -0,0 +1,546 @@
use alloc::boxed::Box;
use super::algs::*;
use super::buffer::hb_buffer_t;
use super::ot_layout::*;
use super::ot_map::*;
use super::ot_shape::*;
use super::ot_shape_complex::*;
use super::ot_shape_complex_arabic::arabic_shape_plan_t;
use super::ot_shape_normalize::*;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::unicode::{CharExt, GeneralCategoryExt};
use super::{hb_font_t, hb_glyph_info_t, hb_mask_t, hb_tag_t, script, Script};
pub const UNIVERSAL_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
collect_features: Some(collect_features),
override_features: None,
create_data: Some(|plan| Box::new(UniversalShapePlan::new(plan))),
preprocess_text: Some(preprocess_text),
postprocess_glyphs: None,
normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT,
decompose: None,
compose: Some(compose),
setup_masks: Some(setup_masks),
gpos_tag: None,
reorder_marks: None,
zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_EARLY,
fallback_position: false,
};
pub type Category = u8;
#[allow(dead_code)]
pub mod category {
pub const O: u8 = 0; // OTHER
pub const B: u8 = 1; // BASE
// pub const IND: u8 = 3; // BASE_IND
pub const N: u8 = 4; // BASE_NUM
pub const GB: u8 = 5; // BASE_OTHER
pub const CGJ: u8 = 6;
// pub const CGJ: u8 = 6; // CGJ
// pub const F: u8 = 7; // CONS_FINAL
// pub const FM: u8 = 8; // CONS_FINAL_MOD
// pub const M: u8 = 9; // CONS_MED
// pub const CM: u8 = 10; // CONS_MOD
pub const SUB: u8 = 11; // CONS_SUB
pub const H: u8 = 12; // HALANT
pub const HN: u8 = 13; // HALANT_NUM
pub const ZWNJ: u8 = 14; // Zero width non-joiner
// pub const ZWJ: u8 = 15; // Zero width joiner
pub const WJ: u8 = 16; // Word joiner
pub const RSV: u8 = 17; // Reserved characters
pub const R: u8 = 18; // REPHA
pub const S: u8 = 19; // SYM
// pub const SM: u8 = 20; // SYM_MOD
// pub const VS: u8 = 21; // VARIATION_SELECTOR
// pub const V: u8 = 36; // VOWEL
// pub const VM: u8 = 40; // VOWEL_MOD
pub const CS: u8 = 43; // CONS_WITH_STACKER
// https://github.com/harfbuzz/harfbuzz/issues/1102
pub const IS: u8 = 44; // HALANT_OR_VOWEL_MODIFIER
pub const SK: u8 = 48; // SAKOT
pub const FABV: u8 = 24; // CONS_FINAL_ABOVE
pub const FBLW: u8 = 25; // CONS_FINAL_BELOW
pub const FPST: u8 = 26; // CONS_FINAL_POST
pub const MABV: u8 = 27; // CONS_MED_ABOVE
pub const MBLW: u8 = 28; // CONS_MED_BELOW
pub const MPST: u8 = 29; // CONS_MED_POST
pub const MPRE: u8 = 30; // CONS_MED_PRE
pub const CMABV: u8 = 31; // CONS_MOD_ABOVE
pub const CMBLW: u8 = 32; // CONS_MOD_BELOW
pub const VABV: u8 = 33; // VOWEL_ABOVE / VOWEL_ABOVE_BELOW / VOWEL_ABOVE_BELOW_POST / VOWEL_ABOVE_POST
pub const VBLW: u8 = 34; // VOWEL_BELOW / VOWEL_BELOW_POST
pub const VPST: u8 = 35; // VOWEL_POST UIPC = Right
pub const VPRE: u8 = 22; // VOWEL_PRE / VOWEL_PRE_ABOVE / VOWEL_PRE_ABOVE_POST / VOWEL_PRE_POST
pub const VMABV: u8 = 37; // VOWEL_MOD_ABOVE
pub const VMBLW: u8 = 38; // VOWEL_MOD_BELOW
pub const VMPST: u8 = 39; // VOWEL_MOD_POST
pub const VMPRE: u8 = 23; // VOWEL_MOD_PRE
pub const SMABV: u8 = 41; // SYM_MOD_ABOVE
pub const SMBLW: u8 = 42; // SYM_MOD_BELOW
pub const FMABV: u8 = 45; // CONS_FINAL_MOD UIPC = Top
pub const FMBLW: u8 = 46; // CONS_FINAL_MOD UIPC = Bottom
pub const FMPST: u8 = 47; // CONS_FINAL_MOD UIPC = Not_Applicable
pub const G: u8 = 49; // HIEROGLYPH
pub const J: u8 = 50; // HIEROGLYPH_JOINER
pub const SB: u8 = 51; // HIEROGLYPH_SEGMENT_BEGIN
pub const SE: u8 = 52; // HIEROGLYPH_SEGMENT_END
}
// These features are applied all at once, before reordering,
// constrained to the syllable.
const BASIC_FEATURES: &[hb_tag_t] = &[
hb_tag_t::from_bytes(b"rkrf"),
hb_tag_t::from_bytes(b"abvf"),
hb_tag_t::from_bytes(b"blwf"),
hb_tag_t::from_bytes(b"half"),
hb_tag_t::from_bytes(b"pstf"),
hb_tag_t::from_bytes(b"vatu"),
hb_tag_t::from_bytes(b"cjct"),
];
const TOPOGRAPHICAL_FEATURES: &[hb_tag_t] = &[
hb_tag_t::from_bytes(b"isol"),
hb_tag_t::from_bytes(b"init"),
hb_tag_t::from_bytes(b"medi"),
hb_tag_t::from_bytes(b"fina"),
];
// Same order as use_topographical_features.
#[derive(Clone, Copy, PartialEq)]
enum JoiningForm {
Isolated = 0,
Initial,
Medial,
Terminal,
}
// These features are applied all at once, after reordering and clearing syllables.
const OTHER_FEATURES: &[hb_tag_t] = &[
hb_tag_t::from_bytes(b"abvs"),
hb_tag_t::from_bytes(b"blws"),
hb_tag_t::from_bytes(b"haln"),
hb_tag_t::from_bytes(b"pres"),
hb_tag_t::from_bytes(b"psts"),
];
impl hb_glyph_info_t {
pub(crate) fn use_category(&self) -> Category {
self.complex_var_u8_category()
}
fn set_use_category(&mut self, c: Category) {
self.set_complex_var_u8_category(c)
}
fn is_halant_use(&self) -> bool {
matches!(self.use_category(), category::H | category::IS) && !_hb_glyph_info_ligated(self)
}
}
struct UniversalShapePlan {
rphf_mask: hb_mask_t,
arabic_plan: Option<arabic_shape_plan_t>,
}
impl UniversalShapePlan {
fn new(plan: &hb_ot_shape_plan_t) -> UniversalShapePlan {
let mut arabic_plan = None;
if plan.script.map_or(false, has_arabic_joining) {
arabic_plan = Some(crate::hb::ot_shape_complex_arabic::data_create_arabic(plan));
}
UniversalShapePlan {
rphf_mask: plan.ot_map.get_1_mask(hb_tag_t::from_bytes(b"rphf")),
arabic_plan,
}
}
}
fn collect_features(planner: &mut hb_ot_shape_planner_t) {
// Do this before any lookups have been applied.
planner.ot_map.add_gsub_pause(Some(setup_syllables));
// Default glyph pre-processing group
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"locl"), F_PER_SYLLABLE, 1);
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"ccmp"), F_PER_SYLLABLE, 1);
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"nukt"), F_PER_SYLLABLE, 1);
planner.ot_map.enable_feature(
hb_tag_t::from_bytes(b"akhn"),
F_MANUAL_ZWJ | F_PER_SYLLABLE,
1,
);
// Reordering group
planner
.ot_map
.add_gsub_pause(Some(crate::hb::ot_layout::_hb_clear_substitution_flags));
planner.ot_map.add_feature(
hb_tag_t::from_bytes(b"rphf"),
F_MANUAL_ZWJ | F_PER_SYLLABLE,
1,
);
planner.ot_map.add_gsub_pause(Some(record_rphf));
planner
.ot_map
.add_gsub_pause(Some(crate::hb::ot_layout::_hb_clear_substitution_flags));
planner.ot_map.enable_feature(
hb_tag_t::from_bytes(b"pref"),
F_MANUAL_ZWJ | F_PER_SYLLABLE,
1,
);
planner.ot_map.add_gsub_pause(Some(record_pref));
// Orthographic unit shaping group
for feature in BASIC_FEATURES {
planner
.ot_map
.enable_feature(*feature, F_MANUAL_ZWJ | F_PER_SYLLABLE, 1);
}
planner.ot_map.add_gsub_pause(Some(reorder));
// Topographical features
for feature in TOPOGRAPHICAL_FEATURES {
planner.ot_map.add_feature(*feature, F_NONE, 1);
}
planner.ot_map.add_gsub_pause(None);
// Standard typographic presentation
for feature in OTHER_FEATURES {
planner.ot_map.enable_feature(*feature, F_NONE, 1);
}
}
fn setup_syllables(plan: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
super::ot_shape_complex_use_machine::find_syllables(buffer);
foreach_syllable!(buffer, start, end, {
buffer.unsafe_to_break(Some(start), Some(end));
});
setup_rphf_mask(plan, buffer);
setup_topographical_masks(plan, buffer);
}
fn setup_rphf_mask(plan: &hb_ot_shape_plan_t, buffer: &mut hb_buffer_t) {
let universal_plan = plan.data::<UniversalShapePlan>();
let mask = universal_plan.rphf_mask;
if mask == 0 {
return;
}
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
let limit = if buffer.info[start].use_category() == category::R {
1
} else {
core::cmp::min(3, end - start)
};
for i in start..start + limit {
buffer.info[i].mask |= mask;
}
start = end;
end = buffer.next_syllable(start);
}
}
fn setup_topographical_masks(plan: &hb_ot_shape_plan_t, buffer: &mut hb_buffer_t) {
use super::ot_shape_complex_use_machine::SyllableType;
if plan.data::<UniversalShapePlan>().arabic_plan.is_some() {
return;
}
let mut masks = [0; 4];
let mut all_masks = 0;
for i in 0..4 {
masks[i] = plan.ot_map.get_1_mask(TOPOGRAPHICAL_FEATURES[i]);
if masks[i] == plan.ot_map.get_global_mask() {
masks[i] = 0;
}
all_masks |= masks[i];
}
if all_masks == 0 {
return;
}
let other_masks = !all_masks;
let mut last_start = 0;
let mut last_form = None;
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
let syllable = buffer.info[start].syllable() & 0x0F;
if syllable == SyllableType::HieroglyphCluster as u8
|| syllable == SyllableType::NonCluster as u8
{
last_form = None;
} else {
let join = last_form == Some(JoiningForm::Terminal)
|| last_form == Some(JoiningForm::Isolated);
if join {
// Fixup previous syllable's form.
let form = if last_form == Some(JoiningForm::Terminal) {
JoiningForm::Medial
} else {
JoiningForm::Initial
};
for i in last_start..start {
buffer.info[i].mask =
(buffer.info[i].mask & other_masks) | masks[form as usize];
}
}
// Form for this syllable.
let form = if join {
JoiningForm::Terminal
} else {
JoiningForm::Isolated
};
last_form = Some(form);
for i in start..end {
buffer.info[i].mask = (buffer.info[i].mask & other_masks) | masks[form as usize];
}
}
last_start = start;
start = end;
end = buffer.next_syllable(start);
}
}
fn record_rphf(plan: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
let universal_plan = plan.data::<UniversalShapePlan>();
let mask = universal_plan.rphf_mask;
if mask == 0 {
return;
}
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
// Mark a substituted repha as USE_R.
for i in start..end {
if buffer.info[i].mask & mask == 0 {
break;
}
if _hb_glyph_info_substituted(&buffer.info[i]) {
buffer.info[i].set_use_category(category::R);
break;
}
}
start = end;
end = buffer.next_syllable(start);
}
}
fn reorder(_: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
use super::ot_shape_complex_use_machine::SyllableType;
crate::hb::ot_shape_complex_syllabic::insert_dotted_circles(
face,
buffer,
SyllableType::BrokenCluster as u8,
category::B,
Some(category::R),
None,
);
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
reorder_syllable(start, end, buffer);
start = end;
end = buffer.next_syllable(start);
}
}
const fn category_flag(c: Category) -> u32 {
rb_flag(c as u32)
}
const fn category_flag64(c: Category) -> u64 {
rb_flag64(c as u32)
}
const BASE_FLAGS: u64 = category_flag64(category::FABV)
| category_flag64(category::FBLW)
| category_flag64(category::FPST)
| category_flag64(category::MABV)
| category_flag64(category::MBLW)
| category_flag64(category::MPST)
| category_flag64(category::MPRE)
| category_flag64(category::VABV)
| category_flag64(category::VBLW)
| category_flag64(category::VPST)
| category_flag64(category::VPRE)
| category_flag64(category::VMABV)
| category_flag64(category::VMBLW)
| category_flag64(category::VMPST)
| category_flag64(category::VMPRE);
fn reorder_syllable(start: usize, end: usize, buffer: &mut hb_buffer_t) {
use super::ot_shape_complex_use_machine::SyllableType;
let syllable_type = (buffer.info[start].syllable() & 0x0F) as u32;
// Only a few syllable types need reordering.
if (rb_flag_unsafe(syllable_type)
& (rb_flag(SyllableType::ViramaTerminatedCluster as u32)
| rb_flag(SyllableType::SakotTerminatedCluster as u32)
| rb_flag(SyllableType::StandardCluster as u32)
| rb_flag(SyllableType::BrokenCluster as u32)
| 0))
== 0
{
return;
}
// Move things forward.
if buffer.info[start].use_category() == category::R && end - start > 1 {
// Got a repha. Reorder it towards the end, but before the first post-base glyph.
for i in start + 1..end {
let is_post_base_glyph =
(rb_flag64_unsafe(buffer.info[i].use_category() as u32) & BASE_FLAGS) != 0
|| buffer.info[i].is_halant_use();
if is_post_base_glyph || i == end - 1 {
// If we hit a post-base glyph, move before it; otherwise move to the
// end. Shift things in between backward.
let mut i = i;
if is_post_base_glyph {
i -= 1;
}
buffer.merge_clusters(start, i + 1);
let t = buffer.info[start];
for k in 0..i - start {
buffer.info[k + start] = buffer.info[k + start + 1];
}
buffer.info[i] = t;
break;
}
}
}
// Move things back.
let mut j = start;
for i in start..end {
let flag = rb_flag_unsafe(buffer.info[i].use_category() as u32);
if buffer.info[i].is_halant_use() {
// If we hit a halant, move after it; otherwise move to the beginning, and
// shift things in between forward.
j = i + 1;
} else if (flag & (category_flag(category::VPRE) | category_flag(category::VMPRE))) != 0
&& _hb_glyph_info_get_lig_comp(&buffer.info[i]) == 0
&& j < i
{
// Only move the first component of a MultipleSubst.
buffer.merge_clusters(j, i + 1);
let t = buffer.info[i];
for k in (0..i - j).rev() {
buffer.info[k + j + 1] = buffer.info[k + j];
}
buffer.info[j] = t;
}
}
}
fn record_pref(_: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
// Mark a substituted pref as VPre, as they behave the same way.
for i in start..end {
if _hb_glyph_info_substituted(&buffer.info[i]) {
buffer.info[i].set_use_category(category::VPRE);
break;
}
}
start = end;
end = buffer.next_syllable(start);
}
}
fn has_arabic_joining(script: Script) -> bool {
// List of scripts that have data in arabic-table.
matches!(
script,
script::ADLAM
| script::ARABIC
| script::CHORASMIAN
| script::HANIFI_ROHINGYA
| script::MANDAIC
| script::MANICHAEAN
| script::MONGOLIAN
| script::NKO
| script::OLD_UYGHUR
| script::PHAGS_PA
| script::PSALTER_PAHLAVI
| script::SOGDIAN
| script::SYRIAC
)
}
fn preprocess_text(_: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
super::ot_shape_complex_vowel_constraints::preprocess_text_vowel_constraints(buffer);
}
fn compose(_: &hb_ot_shape_normalize_context_t, a: char, b: char) -> Option<char> {
// Avoid recomposing split matras.
if a.general_category().is_mark() {
return None;
}
crate::hb::unicode::compose(a, b)
}
fn setup_masks(plan: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
let universal_plan = plan.data::<UniversalShapePlan>();
// Do this before allocating use_category().
if let Some(ref arabic_plan) = universal_plan.arabic_plan {
crate::hb::ot_shape_complex_arabic::setup_masks_inner(arabic_plan, plan.script, buffer);
}
// We cannot setup masks here. We save information about characters
// and setup masks later on in a pause-callback.
for info in buffer.info_slice_mut() {
info.set_use_category(super::ot_shape_complex_use_table::get_category(info));
}
}

View File

@@ -0,0 +1,232 @@
#![allow(
dead_code,
non_upper_case_globals,
unused_assignments,
unused_parens,
while_true,
clippy::assign_op_pattern,
clippy::collapsible_if,
clippy::comparison_chain,
clippy::double_parens,
clippy::unnecessary_cast,
clippy::single_match,
clippy::never_loop
)]
use core::cell::Cell;
use super::buffer::hb_buffer_t;
use super::hb_glyph_info_t;
use super::machine_cursor::MachineCursor;
use super::ot_layout::*;
use super::ot_shape_complex_use::category;
%%{
machine use_syllable_machine;
alphtype u8;
write data;
}%%
%%{
# Categories used in the Universal Shaping Engine spec:
# https://docs.microsoft.com/en-us/typography/script-development/use
O = 0; # OTHER
B = 1; # BASE
N = 4; # BASE_NUM
GB = 5; # BASE_OTHER
CGJ = 6; # CGJ
SUB = 11; # CONS_SUB
H = 12; # HALANT
HN = 13; # HALANT_NUM
ZWNJ = 14; # Zero width non-joiner
WJ = 16; # Word joiner
R = 18; # REPHA
CS = 43; # CONS_WITH_STACKER
IS = 44; # HALANT_OR_VOWEL_MODIFIER
Sk = 48; # SAKOT
G = 49; # HIEROGLYPH
J = 50; # HIEROGLYPH_JOINER
SB = 51; # HIEROGLYPH_SEGMENT_BEGIN
SE = 52; # HIEROGLYPH_SEGMENT_END
FAbv = 24; # CONS_FINAL_ABOVE
FBlw = 25; # CONS_FINAL_BELOW
FPst = 26; # CONS_FINAL_POST
MAbv = 27; # CONS_MED_ABOVE
MBlw = 28; # CONS_MED_BELOW
MPst = 29; # CONS_MED_POST
MPre = 30; # CONS_MED_PRE
CMAbv = 31; # CONS_MOD_ABOVE
CMBlw = 32; # CONS_MOD_BELOW
VAbv = 33; # VOWEL_ABOVE / VOWEL_ABOVE_BELOW / VOWEL_ABOVE_BELOW_POST / VOWEL_ABOVE_POST
VBlw = 34; # VOWEL_BELOW / VOWEL_BELOW_POST
VPst = 35; # VOWEL_POST UIPC = Right
VPre = 22; # VOWEL_PRE / VOWEL_PRE_ABOVE / VOWEL_PRE_ABOVE_POST / VOWEL_PRE_POST
VMAbv = 37; # VOWEL_MOD_ABOVE
VMBlw = 38; # VOWEL_MOD_BELOW
VMPst = 39; # VOWEL_MOD_POST
VMPre = 23; # VOWEL_MOD_PRE
SMAbv = 41; # SYM_MOD_ABOVE
SMBlw = 42; # SYM_MOD_BELOW
FMAbv = 45; # CONS_FINAL_MOD UIPC = Top
FMBlw = 46; # CONS_FINAL_MOD UIPC = Bottom
FMPst = 47; # CONS_FINAL_MOD UIPC = Not_Applicable
h = H | IS | Sk;
consonant_modifiers = CMAbv* CMBlw* ((h B | SUB) CMAbv? CMBlw*)*;
medial_consonants = MPre? MAbv? MBlw? MPst?;
dependent_vowels = VPre* VAbv* VBlw* VPst* | H;
vowel_modifiers = VMPre* VMAbv* VMBlw* VMPst*;
final_consonants = FAbv* FBlw* FPst*;
final_modifiers = FMAbv* FMBlw* | FMPst?;
complex_syllable_start = (R | CS)? (B | GB);
complex_syllable_middle =
consonant_modifiers
medial_consonants
dependent_vowels
vowel_modifiers
(Sk B)*
;
complex_syllable_tail =
complex_syllable_middle
final_consonants
final_modifiers
;
number_joiner_terminated_cluster_tail = (HN N)* HN;
numeral_cluster_tail = (HN N)+;
symbol_cluster_tail = SMAbv+ SMBlw* | SMBlw+;
virama_terminated_cluster_tail =
consonant_modifiers
IS
;
virama_terminated_cluster =
complex_syllable_start
virama_terminated_cluster_tail
;
sakot_terminated_cluster_tail =
complex_syllable_middle
Sk
;
sakot_terminated_cluster =
complex_syllable_start
sakot_terminated_cluster_tail
;
standard_cluster =
complex_syllable_start
complex_syllable_tail
;
tail = complex_syllable_tail | sakot_terminated_cluster_tail | symbol_cluster_tail | virama_terminated_cluster_tail;
broken_cluster =
R?
(tail | number_joiner_terminated_cluster_tail | numeral_cluster_tail)
;
number_joiner_terminated_cluster = N number_joiner_terminated_cluster_tail;
numeral_cluster = N numeral_cluster_tail?;
symbol_cluster = (O | GB) tail?;
hieroglyph_cluster = SB+ | SB* G SE* (J SE* (G SE*)?)*;
other = any;
main := |*
virama_terminated_cluster => { found_syllable!(SyllableType::ViramaTerminatedCluster); };
sakot_terminated_cluster => { found_syllable!(SyllableType::SakotTerminatedCluster); };
standard_cluster => { found_syllable!(SyllableType::StandardCluster); };
number_joiner_terminated_cluster => { found_syllable!(SyllableType::NumberJoinerTerminatedCluster); };
numeral_cluster => { found_syllable!(SyllableType::NumeralCluster); };
symbol_cluster => { found_syllable!(SyllableType::SymbolCluster); };
hieroglyph_cluster => { found_syllable! (SyllableType::HieroglyphCluster); };
broken_cluster => { found_syllable!(SyllableType::BrokenCluster); };
other => { found_syllable!(SyllableType::NonCluster); };
*|;
}%%
#[derive(Clone, Copy)]
pub enum SyllableType {
IndependentCluster,
ViramaTerminatedCluster,
SakotTerminatedCluster,
StandardCluster,
NumberJoinerTerminatedCluster,
NumeralCluster,
SymbolCluster,
HieroglyphCluster,
BrokenCluster,
NonCluster,
}
pub fn find_syllables(buffer: &mut hb_buffer_t) {
let mut cs = 0;
let infos = Cell::as_slice_of_cells(Cell::from_mut(&mut buffer.info));
let p0 = MachineCursor::new(infos, included);
let mut p = p0;
let mut ts = p0;
let mut te = p0;
let mut act = p0;
let pe = p.end();
let eof = p.end();
let mut syllable_serial = 1u8;
// Please manually replace assignments of 0 to p, ts, and te
// to use p0 instead
macro_rules! found_syllable {
($kind:expr) => {{
found_syllable(ts.index(), te.index(), &mut syllable_serial, $kind, infos);
}}
}
%%{
write init;
getkey (infos[p.index()].get().use_category() as u8);
write exec;
}%%
}
#[inline]
fn found_syllable(
start: usize,
end: usize,
syllable_serial: &mut u8,
kind: SyllableType,
buffer: &[Cell<hb_glyph_info_t>],
) {
for i in start..end {
let mut glyph = buffer[i].get();
glyph.set_syllable((*syllable_serial << 4) | kind as u8);
buffer[i].set(glyph);
}
*syllable_serial += 1;
if *syllable_serial == 16 {
*syllable_serial = 1;
}
}
fn not_ccs_default_ignorable(i: &hb_glyph_info_t) -> bool {
i.use_category() != category::CGJ
}
fn included(infos: &[Cell<hb_glyph_info_t>], i: usize) -> bool {
let glyph = infos[i].get();
if !not_ccs_default_ignorable(&glyph) {
return false;
}
if glyph.use_category() == category::ZWNJ {
for glyph2 in &infos[i + 1..] {
if not_ccs_default_ignorable(&glyph2.get()) {
return !_hb_glyph_info_is_unicode_mark(&glyph2.get());
}
}
}
true
}

View File

@@ -0,0 +1,498 @@
// This file is autogenerated. Do not edit it!
//
// See docs/ragel.md for details.
#![allow(
dead_code,
non_upper_case_globals,
unused_assignments,
unused_parens,
while_true,
clippy::assign_op_pattern,
clippy::collapsible_if,
clippy::comparison_chain,
clippy::double_parens,
clippy::unnecessary_cast,
clippy::single_match,
clippy::never_loop
)]
use core::cell::Cell;
use super::buffer::hb_buffer_t;
use super::hb_glyph_info_t;
use super::machine_cursor::MachineCursor;
use super::ot_layout::*;
use super::ot_shape_complex_use::category;
static _use_syllable_machine_trans_keys: [u8; 226] = [
0, 36, 5, 33, 5, 33, 1, 33, 10, 33, 11, 32, 12, 32, 13, 32, 30, 31, 31, 31, 11, 33, 11, 33, 11,
33, 1, 1, 11, 33, 9, 33, 10, 33, 10, 33, 10, 33, 6, 33, 6, 33, 6, 33, 6, 33, 5, 33, 1, 1, 5,
33, 26, 27, 27, 27, 5, 33, 5, 33, 1, 33, 10, 33, 11, 32, 12, 32, 13, 32, 30, 31, 31, 31, 11,
33, 11, 33, 11, 33, 1, 1, 11, 33, 9, 33, 10, 33, 10, 33, 10, 33, 6, 33, 6, 33, 6, 33, 6, 33, 5,
33, 1, 1, 7, 7, 3, 3, 5, 33, 5, 33, 1, 33, 10, 33, 11, 32, 12, 32, 13, 32, 30, 31, 31, 31, 11,
33, 11, 33, 11, 33, 1, 1, 11, 33, 9, 33, 10, 33, 10, 33, 10, 33, 6, 33, 6, 33, 6, 33, 6, 33, 5,
33, 1, 1, 5, 33, 5, 33, 1, 33, 10, 33, 11, 32, 12, 32, 13, 32, 30, 31, 31, 31, 11, 33, 11, 33,
11, 33, 1, 1, 11, 33, 9, 33, 10, 33, 10, 33, 10, 33, 6, 33, 6, 33, 6, 33, 6, 33, 5, 33, 1, 1,
3, 3, 7, 7, 1, 33, 5, 33, 26, 27, 27, 27, 1, 4, 35, 37, 34, 37, 34, 36, 0, 0,
];
static _use_syllable_machine_char_class: [i8; 55] = [
0, 1, 2, 2, 3, 4, 2, 2, 2, 2, 2, 5, 6, 7, 2, 2, 2, 2, 8, 2, 2, 2, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 2, 23, 24, 25, 2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
0, 0,
];
static _use_syllable_machine_index_offsets: [i16; 114] = [
0, 37, 66, 95, 128, 152, 174, 195, 215, 217, 218, 241, 264, 287, 288, 311, 336, 360, 384, 408,
436, 464, 492, 520, 549, 550, 579, 581, 582, 611, 640, 673, 697, 719, 740, 760, 762, 763, 786,
809, 832, 833, 856, 881, 905, 929, 953, 981, 1009, 1037, 1065, 1094, 1095, 1096, 1097, 1126,
1155, 1188, 1212, 1234, 1255, 1275, 1277, 1278, 1301, 1324, 1347, 1348, 1371, 1396, 1420, 1444,
1468, 1496, 1524, 1552, 1580, 1609, 1610, 1639, 1668, 1701, 1725, 1747, 1768, 1788, 1790, 1791,
1814, 1837, 1860, 1861, 1884, 1909, 1933, 1957, 1981, 2009, 2037, 2065, 2093, 2122, 2123, 2124,
2125, 2158, 2187, 2189, 2190, 2194, 2197, 2201, 0, 0,
];
static _use_syllable_machine_indices: [i8; 2206] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 30, 31, 32, 33, 30, 34, 3, 35, 37, 38, 36, 36, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 36, 58, 59, 60, 61, 58, 37, 38, 36, 36, 39, 40, 41, 42,
43, 44, 45, 46, 47, 49, 49, 50, 51, 52, 53, 54, 55, 36, 36, 36, 58, 59, 60, 61, 58, 37, 36, 36,
36, 36, 36, 36, 36, 36, 40, 41, 42, 43, 36, 36, 36, 36, 36, 36, 36, 36, 36, 53, 54, 55, 36, 36,
36, 36, 59, 60, 61, 62, 40, 41, 42, 43, 36, 36, 36, 36, 36, 36, 36, 36, 36, 53, 54, 55, 36, 36,
36, 36, 59, 60, 61, 62, 41, 42, 43, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
36, 59, 60, 61, 42, 43, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 59, 60,
61, 43, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 59, 60, 61, 59, 60, 60,
41, 42, 43, 36, 36, 36, 36, 36, 36, 36, 36, 36, 53, 54, 55, 36, 36, 36, 36, 59, 60, 61, 62, 41,
42, 43, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 54, 55, 36, 36, 36, 36, 59, 60, 61, 62, 41, 42,
43, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 55, 36, 36, 36, 36, 59, 60, 61, 62, 63, 41, 42,
43, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 59, 60, 61, 62, 39, 40, 41,
42, 43, 36, 36, 36, 36, 36, 36, 50, 51, 52, 53, 54, 55, 36, 36, 36, 36, 59, 60, 61, 62, 40, 41,
42, 43, 36, 36, 36, 36, 36, 36, 50, 51, 52, 53, 54, 55, 36, 36, 36, 36, 59, 60, 61, 62, 40, 41,
42, 43, 36, 36, 36, 36, 36, 36, 36, 51, 52, 53, 54, 55, 36, 36, 36, 36, 59, 60, 61, 62, 40, 41,
42, 43, 36, 36, 36, 36, 36, 36, 36, 36, 52, 53, 54, 55, 36, 36, 36, 36, 59, 60, 61, 62, 40, 36,
36, 39, 40, 41, 42, 43, 36, 45, 46, 36, 36, 36, 50, 51, 52, 53, 54, 55, 36, 36, 36, 36, 59, 60,
61, 62, 40, 36, 36, 39, 40, 41, 42, 43, 36, 36, 46, 36, 36, 36, 50, 51, 52, 53, 54, 55, 36, 36,
36, 36, 59, 60, 61, 62, 40, 36, 36, 39, 40, 41, 42, 43, 36, 36, 36, 36, 36, 36, 50, 51, 52, 53,
54, 55, 36, 36, 36, 36, 59, 60, 61, 62, 40, 36, 36, 39, 40, 41, 42, 43, 44, 45, 46, 36, 36, 36,
50, 51, 52, 53, 54, 55, 36, 36, 36, 36, 59, 60, 61, 62, 37, 38, 36, 36, 39, 40, 41, 42, 43, 44,
45, 46, 47, 36, 49, 50, 51, 52, 53, 54, 55, 36, 36, 36, 58, 59, 60, 61, 58, 37, 37, 38, 36, 36,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 36, 36, 36, 58, 59, 60, 61,
58, 56, 57, 57, 65, 66, 64, 64, 67, 68, 69, 70, 71, 72, 73, 74, 75, 2, 76, 77, 78, 79, 80, 81,
82, 64, 64, 64, 83, 84, 85, 86, 87, 65, 66, 64, 64, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 76,
77, 78, 79, 80, 81, 82, 64, 64, 64, 83, 84, 85, 86, 87, 65, 64, 64, 64, 64, 64, 64, 64, 64, 68,
69, 70, 71, 64, 64, 64, 64, 64, 64, 64, 64, 64, 80, 81, 82, 64, 64, 64, 64, 84, 85, 86, 88, 68,
69, 70, 71, 64, 64, 64, 64, 64, 64, 64, 64, 64, 80, 81, 82, 64, 64, 64, 64, 84, 85, 86, 88, 69,
70, 71, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 84, 85, 86, 70, 71, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 84, 85, 86, 71, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 84, 85, 86, 84, 85, 85, 69, 70, 71, 64, 64, 64, 64,
64, 64, 64, 64, 64, 80, 81, 82, 64, 64, 64, 64, 84, 85, 86, 88, 69, 70, 71, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 81, 82, 64, 64, 64, 64, 84, 85, 86, 88, 69, 70, 71, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 82, 64, 64, 64, 64, 84, 85, 86, 88, 90, 69, 70, 71, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 84, 85, 86, 88, 67, 68, 69, 70, 71, 64, 64, 64, 64, 64,
64, 77, 78, 79, 80, 81, 82, 64, 64, 64, 64, 84, 85, 86, 88, 68, 69, 70, 71, 64, 64, 64, 64, 64,
64, 77, 78, 79, 80, 81, 82, 64, 64, 64, 64, 84, 85, 86, 88, 68, 69, 70, 71, 64, 64, 64, 64, 64,
64, 64, 78, 79, 80, 81, 82, 64, 64, 64, 64, 84, 85, 86, 88, 68, 69, 70, 71, 64, 64, 64, 64, 64,
64, 64, 64, 79, 80, 81, 82, 64, 64, 64, 64, 84, 85, 86, 88, 68, 64, 64, 67, 68, 69, 70, 71, 64,
73, 74, 64, 64, 64, 77, 78, 79, 80, 81, 82, 64, 64, 64, 64, 84, 85, 86, 88, 68, 64, 64, 67, 68,
69, 70, 71, 64, 64, 74, 64, 64, 64, 77, 78, 79, 80, 81, 82, 64, 64, 64, 64, 84, 85, 86, 88, 68,
64, 64, 67, 68, 69, 70, 71, 64, 64, 64, 64, 64, 64, 77, 78, 79, 80, 81, 82, 64, 64, 64, 64, 84,
85, 86, 88, 68, 64, 64, 67, 68, 69, 70, 71, 72, 73, 74, 64, 64, 64, 77, 78, 79, 80, 81, 82, 64,
64, 64, 64, 84, 85, 86, 88, 65, 66, 64, 64, 67, 68, 69, 70, 71, 72, 73, 74, 75, 64, 76, 77, 78,
79, 80, 81, 82, 64, 64, 64, 83, 84, 85, 86, 87, 65, 93, 4, 95, 96, 64, 64, 97, 98, 99, 100,
101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 56, 57, 64, 114, 115, 116, 86,
117, 95, 96, 64, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 107, 107, 108, 109, 110, 111,
112, 113, 64, 64, 64, 114, 115, 116, 86, 117, 95, 64, 64, 64, 64, 64, 64, 64, 64, 98, 99, 100,
101, 64, 64, 64, 64, 64, 64, 64, 64, 64, 111, 112, 113, 64, 64, 64, 64, 115, 116, 86, 118, 98,
99, 100, 101, 64, 64, 64, 64, 64, 64, 64, 64, 64, 111, 112, 113, 64, 64, 64, 64, 115, 116, 86,
118, 99, 100, 101, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 115, 116,
86, 100, 101, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 115, 116, 86,
101, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 115, 116, 86, 115, 116,
116, 99, 100, 101, 64, 64, 64, 64, 64, 64, 64, 64, 64, 111, 112, 113, 64, 64, 64, 64, 115, 116,
86, 118, 99, 100, 101, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 112, 113, 64, 64, 64, 64, 115,
116, 86, 118, 99, 100, 101, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 113, 64, 64, 64, 64,
115, 116, 86, 118, 119, 99, 100, 101, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 115, 116, 86, 118, 97, 98, 99, 100, 101, 64, 64, 64, 64, 64, 64, 108, 109, 110, 111,
112, 113, 64, 64, 64, 64, 115, 116, 86, 118, 98, 99, 100, 101, 64, 64, 64, 64, 64, 64, 108,
109, 110, 111, 112, 113, 64, 64, 64, 64, 115, 116, 86, 118, 98, 99, 100, 101, 64, 64, 64, 64,
64, 64, 64, 109, 110, 111, 112, 113, 64, 64, 64, 64, 115, 116, 86, 118, 98, 99, 100, 101, 64,
64, 64, 64, 64, 64, 64, 64, 110, 111, 112, 113, 64, 64, 64, 64, 115, 116, 86, 118, 98, 64, 64,
97, 98, 99, 100, 101, 64, 103, 104, 64, 64, 64, 108, 109, 110, 111, 112, 113, 64, 64, 64, 64,
115, 116, 86, 118, 98, 64, 64, 97, 98, 99, 100, 101, 64, 64, 104, 64, 64, 64, 108, 109, 110,
111, 112, 113, 64, 64, 64, 64, 115, 116, 86, 118, 98, 64, 64, 97, 98, 99, 100, 101, 64, 64, 64,
64, 64, 64, 108, 109, 110, 111, 112, 113, 64, 64, 64, 64, 115, 116, 86, 118, 98, 64, 64, 97,
98, 99, 100, 101, 102, 103, 104, 64, 64, 64, 108, 109, 110, 111, 112, 113, 64, 64, 64, 64, 115,
116, 86, 118, 95, 96, 64, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 64, 107, 108, 109, 110,
111, 112, 113, 64, 64, 64, 114, 115, 116, 86, 117, 95, 95, 96, 64, 64, 97, 98, 99, 100, 101,
102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 64, 64, 64, 114, 115, 116, 86, 117,
6, 7, 120, 120, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 20, 21, 22, 23, 24, 25, 26, 120, 120,
120, 30, 31, 32, 33, 30, 6, 120, 120, 120, 120, 120, 120, 120, 120, 11, 12, 13, 14, 120, 120,
120, 120, 120, 120, 120, 120, 120, 24, 25, 26, 120, 120, 120, 120, 31, 32, 33, 121, 11, 12, 13,
14, 120, 120, 120, 120, 120, 120, 120, 120, 120, 24, 25, 26, 120, 120, 120, 120, 31, 32, 33,
121, 12, 13, 14, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120,
120, 31, 32, 33, 13, 14, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 31, 32, 33, 14, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 31, 32, 33, 31, 32, 32, 12, 13, 14, 120, 120, 120, 120, 120, 120, 120, 120, 120, 24,
25, 26, 120, 120, 120, 120, 31, 32, 33, 121, 12, 13, 14, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 25, 26, 120, 120, 120, 120, 31, 32, 33, 121, 12, 13, 14, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 26, 120, 120, 120, 120, 31, 32, 33, 121, 122, 12, 13, 14,
120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 31, 32, 33,
121, 10, 11, 12, 13, 14, 120, 120, 120, 120, 120, 120, 21, 22, 23, 24, 25, 26, 120, 120, 120,
120, 31, 32, 33, 121, 11, 12, 13, 14, 120, 120, 120, 120, 120, 120, 21, 22, 23, 24, 25, 26,
120, 120, 120, 120, 31, 32, 33, 121, 11, 12, 13, 14, 120, 120, 120, 120, 120, 120, 120, 22, 23,
24, 25, 26, 120, 120, 120, 120, 31, 32, 33, 121, 11, 12, 13, 14, 120, 120, 120, 120, 120, 120,
120, 120, 23, 24, 25, 26, 120, 120, 120, 120, 31, 32, 33, 121, 11, 120, 120, 10, 11, 12, 13,
14, 120, 16, 17, 120, 120, 120, 21, 22, 23, 24, 25, 26, 120, 120, 120, 120, 31, 32, 33, 121,
11, 120, 120, 10, 11, 12, 13, 14, 120, 120, 17, 120, 120, 120, 21, 22, 23, 24, 25, 26, 120,
120, 120, 120, 31, 32, 33, 121, 11, 120, 120, 10, 11, 12, 13, 14, 120, 120, 120, 120, 120, 120,
21, 22, 23, 24, 25, 26, 120, 120, 120, 120, 31, 32, 33, 121, 11, 120, 120, 10, 11, 12, 13, 14,
15, 16, 17, 120, 120, 120, 21, 22, 23, 24, 25, 26, 120, 120, 120, 120, 31, 32, 33, 121, 6, 7,
120, 120, 10, 11, 12, 13, 14, 15, 16, 17, 18, 120, 20, 21, 22, 23, 24, 25, 26, 120, 120, 120,
30, 31, 32, 33, 30, 6, 123, 8, 2, 120, 120, 2, 6, 7, 8, 120, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 120, 30, 31, 32, 33, 30, 6, 7, 120, 120, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 120, 120, 120, 30, 31, 32, 33, 30,
27, 28, 28, 2, 124, 124, 2, 126, 125, 34, 34, 126, 125, 126, 34, 125, 35, 0, 0,
];
static _use_syllable_machine_index_defaults: [i8; 114] = [
3, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
36, 36, 36, 36, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 89, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 91, 92, 94, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 89, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 91, 64, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 124, 125, 125,
125, 0, 0,
];
static _use_syllable_machine_cond_targs: [i8; 129] = [
0, 1, 28, 0, 52, 54, 79, 80, 102, 104, 92, 81, 82, 83, 84, 96, 97, 98, 99, 105, 100, 93, 94,
95, 87, 88, 89, 106, 107, 108, 101, 85, 86, 0, 109, 111, 0, 2, 3, 15, 4, 5, 6, 7, 19, 20, 21,
22, 25, 23, 16, 17, 18, 10, 11, 12, 26, 27, 24, 8, 9, 0, 13, 14, 0, 29, 30, 42, 31, 32, 33, 34,
46, 47, 48, 49, 50, 43, 44, 45, 37, 38, 39, 51, 35, 36, 0, 51, 40, 0, 41, 0, 0, 53, 0, 55, 56,
68, 57, 58, 59, 60, 72, 73, 74, 75, 78, 76, 69, 70, 71, 63, 64, 65, 77, 61, 62, 77, 66, 67, 0,
90, 91, 103, 0, 0, 110, 0, 0,
];
static _use_syllable_machine_cond_actions: [i8; 129] = [
0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 4, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0,
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 9, 10, 0, 11, 0, 12, 13, 0,
14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 10, 0, 0, 15, 0, 0, 0,
16, 17, 0, 0, 0,
];
static _use_syllable_machine_to_state_actions: [i8; 114] = [
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
static _use_syllable_machine_from_state_actions: [i8; 114] = [
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
static _use_syllable_machine_eof_trans: [i8; 114] = [
1, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 90, 65, 65, 65, 65, 65, 65, 65,
65, 65, 65, 92, 93, 95, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 90, 65, 65, 65, 65, 65,
65, 65, 65, 65, 65, 92, 65, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121,
121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 125, 126, 126,
126, 0, 0,
];
static use_syllable_machine_start: i32 = 0;
static use_syllable_machine_first_final: i32 = 0;
static use_syllable_machine_error: i32 = -1;
static use_syllable_machine_en_main: i32 = 0;
#[derive(Clone, Copy)]
pub enum SyllableType {
IndependentCluster,
ViramaTerminatedCluster,
SakotTerminatedCluster,
StandardCluster,
NumberJoinerTerminatedCluster,
NumeralCluster,
SymbolCluster,
HieroglyphCluster,
BrokenCluster,
NonCluster,
}
pub fn find_syllables(buffer: &mut hb_buffer_t) {
let mut cs = 0;
let infos = Cell::as_slice_of_cells(Cell::from_mut(&mut buffer.info));
let p0 = MachineCursor::new(infos, included);
let mut p = p0;
let mut ts = p0;
let mut te = p0;
let mut act = p0;
let pe = p.end();
let eof = p.end();
let mut syllable_serial = 1u8;
// Please manually replace assignments of 0 to p, ts, and te
// to use p0 instead
macro_rules! found_syllable {
($kind:expr) => {{
found_syllable(ts.index(), te.index(), &mut syllable_serial, $kind, infos);
}};
}
{
cs = (use_syllable_machine_start) as i32;
ts = p0;
te = p0;
act = p0;
}
{
let mut _trans = 0;
let mut _keys: i32 = 0;
let mut _inds: i32 = 0;
let mut _ic = 0;
'_resume: while (p != pe || p == eof) {
'_again: while (true) {
match (_use_syllable_machine_from_state_actions[(cs) as usize]) {
2 => {
ts = p;
}
_ => {}
}
if (p == eof) {
{
if (_use_syllable_machine_eof_trans[(cs) as usize] > 0) {
{
_trans =
(_use_syllable_machine_eof_trans[(cs) as usize]) as u32 - 1;
}
}
}
} else {
{
_keys = (cs << 1) as i32;
_inds = (_use_syllable_machine_index_offsets[(cs) as usize]) as i32;
if ((infos[p.index()].get().use_category() as u8) <= 52) {
{
_ic = (_use_syllable_machine_char_class[((infos[p.index()]
.get()
.use_category()
as u8)
as i32
- 0)
as usize]) as i32;
if (_ic
<= (_use_syllable_machine_trans_keys[(_keys + 1) as usize])
as i32
&& _ic
>= (_use_syllable_machine_trans_keys[(_keys) as usize])
as i32)
{
_trans = (_use_syllable_machine_indices[(_inds
+ (_ic
- (_use_syllable_machine_trans_keys[(_keys) as usize])
as i32)
as i32)
as usize])
as u32;
} else {
_trans = (_use_syllable_machine_index_defaults[(cs) as usize])
as u32;
}
}
} else {
{
_trans =
(_use_syllable_machine_index_defaults[(cs) as usize]) as u32;
}
}
}
}
cs = (_use_syllable_machine_cond_targs[(_trans) as usize]) as i32;
if (_use_syllable_machine_cond_actions[(_trans) as usize] != 0) {
{
match (_use_syllable_machine_cond_actions[(_trans) as usize]) {
9 => {
te = p + 1;
{
found_syllable!(SyllableType::StandardCluster);
}
}
6 => {
te = p + 1;
{
found_syllable!(SyllableType::SymbolCluster);
}
}
4 => {
te = p + 1;
{
found_syllable!(SyllableType::BrokenCluster);
}
}
3 => {
te = p + 1;
{
found_syllable!(SyllableType::NonCluster);
}
}
11 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::SakotTerminatedCluster);
}
}
7 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::StandardCluster);
}
}
14 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::NumberJoinerTerminatedCluster);
}
}
13 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::NumeralCluster);
}
}
5 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::SymbolCluster);
}
}
17 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::HieroglyphCluster);
}
}
15 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::BrokenCluster);
}
}
16 => {
te = p;
p = p - 1;
{
found_syllable!(SyllableType::NonCluster);
}
}
12 => match (act).index() {
1 => {
p = (te) - 1;
{
found_syllable!(SyllableType::ViramaTerminatedCluster);
}
}
2 => {
p = (te) - 1;
{
found_syllable!(SyllableType::SakotTerminatedCluster);
}
}
_ => {}
},
8 => {
{
{
te = p + 1;
}
}
{
{
act = p + 1;
}
}
}
10 => {
{
{
te = p + 1;
}
}
{
{
act = p + 2;
}
}
}
_ => {}
}
}
}
break '_again;
}
if (p == eof) {
{
if (cs >= 0) {
break '_resume;
}
}
} else {
{
match (_use_syllable_machine_to_state_actions[(cs) as usize]) {
1 => {
ts = p0;
}
_ => {}
}
p += 1;
continue '_resume;
}
}
break '_resume;
}
}
}
#[inline]
fn found_syllable(
start: usize,
end: usize,
syllable_serial: &mut u8,
kind: SyllableType,
buffer: &[Cell<hb_glyph_info_t>],
) {
for i in start..end {
let mut glyph = buffer[i].get();
glyph.set_syllable((*syllable_serial << 4) | kind as u8);
buffer[i].set(glyph);
}
*syllable_serial += 1;
if *syllable_serial == 16 {
*syllable_serial = 1;
}
}
fn not_ccs_default_ignorable(i: &hb_glyph_info_t) -> bool {
i.use_category() != category::CGJ
}
fn included(infos: &[Cell<hb_glyph_info_t>], i: usize) -> bool {
let glyph = infos[i].get();
if !not_ccs_default_ignorable(&glyph) {
return false;
}
if glyph.use_category() == category::ZWNJ {
for glyph2 in &infos[i + 1..] {
if not_ccs_default_ignorable(&glyph2.get()) {
return !_hb_glyph_info_is_unicode_mark(&glyph2.get());
}
}
}
true
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,426 @@
// WARNING: this file was generated by scripts/gen-vowel-constraints.py
use super::buffer::hb_buffer_t;
use super::ot_layout::*;
use super::script;
use crate::BufferFlags;
fn output_dotted_circle(buffer: &mut hb_buffer_t) {
buffer.output_glyph(0x25CC);
{
let out_idx = buffer.out_len - 1;
_hb_glyph_info_reset_continuation(&mut buffer.out_info_mut()[out_idx]);
}
}
fn output_with_dotted_circle(buffer: &mut hb_buffer_t) {
output_dotted_circle(buffer);
buffer.next_glyph();
}
pub fn preprocess_text_vowel_constraints(buffer: &mut hb_buffer_t) {
if buffer
.flags
.contains(BufferFlags::DO_NOT_INSERT_DOTTED_CIRCLE)
{
return;
}
// UGLY UGLY UGLY business of adding dotted-circle in the middle of
// vowel-sequences that look like another vowel. Data for each script
// collected from the USE script development spec.
//
// https://github.com/harfbuzz/harfbuzz/issues/1019
buffer.clear_output();
match buffer.script {
Some(script::DEVANAGARI) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x0905 => match buffer.cur(1).glyph_id {
0x093A | 0x093B | 0x093E | 0x0945 | 0x0946 | 0x0949 | 0x094A | 0x094B
| 0x094C | 0x094F | 0x0956 | 0x0957 => {
matched = true;
}
_ => {}
},
0x0906 => match buffer.cur(1).glyph_id {
0x093A | 0x0945 | 0x0946 | 0x0947 | 0x0948 => {
matched = true;
}
_ => {}
},
0x0909 => {
matched = 0x0941 == buffer.cur(1).glyph_id;
}
0x090F => match buffer.cur(1).glyph_id {
0x0945 | 0x0946 | 0x0947 => {
matched = true;
}
_ => {}
},
0x0930 => {
if 0x094D == buffer.cur(1).glyph_id
&& buffer.idx + 2 < buffer.len
&& 0x0907 == buffer.cur(2).glyph_id
{
buffer.next_glyph();
buffer.next_glyph();
output_dotted_circle(buffer);
}
}
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::BENGALI) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x0985 => {
matched = 0x09BE == buffer.cur(1).glyph_id;
}
0x098B => {
matched = 0x09C3 == buffer.cur(1).glyph_id;
}
0x098C => {
matched = 0x09E2 == buffer.cur(1).glyph_id;
}
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::GURMUKHI) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x0A05 => match buffer.cur(1).glyph_id {
0x0A3E | 0x0A48 | 0x0A4C => {
matched = true;
}
_ => {}
},
0x0A72 => match buffer.cur(1).glyph_id {
0x0A3F | 0x0A40 | 0x0A47 => {
matched = true;
}
_ => {}
},
0x0A73 => match buffer.cur(1).glyph_id {
0x0A41 | 0x0A42 | 0x0A4B => {
matched = true;
}
_ => {}
},
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::GUJARATI) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x0A85 => match buffer.cur(1).glyph_id {
0x0ABE | 0x0AC5 | 0x0AC7 | 0x0AC8 | 0x0AC9 | 0x0ACB | 0x0ACC => {
matched = true;
}
_ => {}
},
0x0AC5 => {
matched = 0x0ABE == buffer.cur(1).glyph_id;
}
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::ORIYA) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x0B05 => {
matched = 0x0B3E == buffer.cur(1).glyph_id;
}
0x0B0F | 0x0B13 => {
matched = 0x0B57 == buffer.cur(1).glyph_id;
}
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::TAMIL) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
if 0x0B85 == buffer.cur(0).glyph_id && 0x0BC2 == buffer.cur(1).glyph_id {
buffer.next_glyph();
output_dotted_circle(buffer);
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::TELUGU) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x0C12 => match buffer.cur(1).glyph_id {
0x0C4C | 0x0C55 => {
matched = true;
}
_ => {}
},
0x0C3F | 0x0C46 | 0x0C4A => {
matched = 0x0C55 == buffer.cur(1).glyph_id;
}
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::KANNADA) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x0C89 | 0x0C8B => {
matched = 0x0CBE == buffer.cur(1).glyph_id;
}
0x0C92 => {
matched = 0x0CCC == buffer.cur(1).glyph_id;
}
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::MALAYALAM) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x0D07 | 0x0D09 => {
matched = 0x0D57 == buffer.cur(1).glyph_id;
}
0x0D0E => {
matched = 0x0D46 == buffer.cur(1).glyph_id;
}
0x0D12 => match buffer.cur(1).glyph_id {
0x0D3E | 0x0D57 => {
matched = true;
}
_ => {}
},
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::SINHALA) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x0D85 => match buffer.cur(1).glyph_id {
0x0DCF | 0x0DD0 | 0x0DD1 => {
matched = true;
}
_ => {}
},
0x0D8B | 0x0D8F | 0x0D94 => {
matched = 0x0DDF == buffer.cur(1).glyph_id;
}
0x0D8D => {
matched = 0x0DD8 == buffer.cur(1).glyph_id;
}
0x0D91 => match buffer.cur(1).glyph_id {
0x0DCA | 0x0DD9 | 0x0DDA | 0x0DDC | 0x0DDD | 0x0DDE => {
matched = true;
}
_ => {}
},
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::BRAHMI) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x11005 => {
matched = 0x11038 == buffer.cur(1).glyph_id;
}
0x1100B => {
matched = 0x1103E == buffer.cur(1).glyph_id;
}
0x1100F => {
matched = 0x11042 == buffer.cur(1).glyph_id;
}
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::KHUDAWADI) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x112B0 => match buffer.cur(1).glyph_id {
0x112E0 | 0x112E5 | 0x112E6 | 0x112E7 | 0x112E8 => {
matched = true;
}
_ => {}
},
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::TIRHUTA) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x11481 => {
matched = 0x114B0 == buffer.cur(1).glyph_id;
}
0x1148B | 0x1148D => {
matched = 0x114BA == buffer.cur(1).glyph_id;
}
0x114AA => match buffer.cur(1).glyph_id {
0x114B5 | 0x114B6 => {
matched = true;
}
_ => {}
},
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::MODI) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x11600 | 0x11601 => match buffer.cur(1).glyph_id {
0x11639 | 0x1163A => {
matched = true;
}
_ => {}
},
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
Some(script::TAKRI) => {
buffer.idx = 0;
while buffer.idx + 1 < buffer.len {
#[allow(unused_mut)]
let mut matched = false;
match buffer.cur(0).glyph_id {
0x11680 => match buffer.cur(1).glyph_id {
0x116AD | 0x116B4 | 0x116B5 => {
matched = true;
}
_ => {}
},
0x11686 => {
matched = 0x116B2 == buffer.cur(1).glyph_id;
}
_ => {}
}
buffer.next_glyph();
if matched {
output_with_dotted_circle(buffer);
}
}
}
_ => {}
}
buffer.sync();
}

View File

@@ -0,0 +1,561 @@
use ttf_parser::GlyphId;
use super::buffer::{hb_buffer_t, GlyphPosition};
use super::face::GlyphExtents;
use super::ot_layout::*;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::unicode::*;
use super::{hb_font_t, Direction};
fn recategorize_combining_class(u: u32, mut class: u8) -> u8 {
use modified_combining_class as mcc;
use CanonicalCombiningClass as Class;
if class >= 200 {
return class;
}
// Thai / Lao need some per-character work.
if u & !0xFF == 0x0E00 {
if class == 0 {
match u {
0x0E31 | 0x0E34 | 0x0E35 | 0x0E36 | 0x0E37 | 0x0E47 | 0x0E4C | 0x0E4D | 0x0E4E => {
class = Class::AboveRight as u8
}
0x0EB1 | 0x0EB4 | 0x0EB5 | 0x0EB6 | 0x0EB7 | 0x0EBB | 0x0ECC | 0x0ECD => {
class = Class::Above as u8
}
0x0EBC => class = Class::Below as u8,
_ => {}
}
} else {
// Thai virama is below-right
if u == 0x0E3A {
class = Class::BelowRight as u8;
}
}
}
match class {
// Hebrew
mcc::CCC10 => Class::Below as u8, // sheva
mcc::CCC11 => Class::Below as u8, // hataf segol
mcc::CCC12 => Class::Below as u8, // hataf patah
mcc::CCC13 => Class::Below as u8, // hataf qamats
mcc::CCC14 => Class::Below as u8, // hiriq
mcc::CCC15 => Class::Below as u8, // tsere
mcc::CCC16 => Class::Below as u8, // segol
mcc::CCC17 => Class::Below as u8, // patah
mcc::CCC18 => Class::Below as u8, // qamats & qamats qatan
mcc::CCC20 => Class::Below as u8, // qubuts
mcc::CCC22 => Class::Below as u8, // meteg
mcc::CCC23 => Class::AttachedAbove as u8, // rafe
mcc::CCC24 => Class::AboveRight as u8, // shin dot
mcc::CCC25 => Class::AboveLeft as u8, // sin dot
mcc::CCC19 => Class::AboveLeft as u8, // holam & holam haser for vav
mcc::CCC26 => Class::Above as u8, // point varika
mcc::CCC21 => class, // dagesh
// Arabic and Syriac
mcc::CCC27 => Class::Above as u8, // fathatan
mcc::CCC28 => Class::Above as u8, // dammatan
mcc::CCC30 => Class::Above as u8, // fatha
mcc::CCC31 => Class::Above as u8, // damma
mcc::CCC33 => Class::Above as u8, // shadda
mcc::CCC34 => Class::Above as u8, // sukun
mcc::CCC35 => Class::Above as u8, // superscript alef
mcc::CCC36 => Class::Above as u8, // superscript alaph
mcc::CCC29 => Class::Below as u8, // kasratan
mcc::CCC32 => Class::Below as u8, // kasra
// Thai
mcc::CCC103 => Class::BelowRight as u8, // sara u / sara uu
mcc::CCC107 => Class::AboveRight as u8, // mai
// Lao
mcc::CCC118 => Class::Below as u8, // sign u / sign uu
mcc::CCC122 => Class::Above as u8, // mai
// Tibetian
mcc::CCC129 => Class::Below as u8, // sign aa
mcc::CCC130 => Class::Above as u8, // sign i
mcc::CCC132 => Class::Below as u8, // sign u
_ => class,
}
}
pub fn _hb_ot_shape_fallback_mark_position_recategorize_marks(
_: &hb_ot_shape_plan_t,
_: &hb_font_t,
buffer: &mut hb_buffer_t,
) {
let len = buffer.len;
for info in &mut buffer.info[..len] {
if _hb_glyph_info_get_general_category(info)
== hb_unicode_general_category_t::NonspacingMark
{
let mut class = _hb_glyph_info_get_modified_combining_class(info);
class = recategorize_combining_class(info.glyph_id, class);
_hb_glyph_info_set_modified_combining_class(info, class);
}
}
}
fn zero_mark_advances(
buffer: &mut hb_buffer_t,
start: usize,
end: usize,
adjust_offsets_when_zeroing: bool,
) {
for (info, pos) in buffer.info[start..end]
.iter()
.zip(&mut buffer.pos[start..end])
{
if _hb_glyph_info_get_general_category(info)
== hb_unicode_general_category_t::NonspacingMark
{
if adjust_offsets_when_zeroing {
pos.x_offset -= pos.x_advance;
pos.y_offset -= pos.y_advance;
}
pos.x_advance = 0;
pos.y_advance = 0;
}
}
}
fn position_mark(
_: &hb_ot_shape_plan_t,
face: &hb_font_t,
direction: Direction,
glyph: GlyphId,
pos: &mut GlyphPosition,
base_extents: &mut GlyphExtents,
combining_class: CanonicalCombiningClass,
) {
use CanonicalCombiningClass as Class;
let mut mark_extents = GlyphExtents::default();
if !face.glyph_extents(glyph, &mut mark_extents) {
return;
};
let y_gap = face.units_per_em as i32 / 16;
pos.x_offset = 0;
pos.y_offset = 0;
// We don't position LEFT and RIGHT marks.
// X positioning
match combining_class {
Class::DoubleBelow | Class::DoubleAbove if direction.is_horizontal() => {
pos.x_offset += base_extents.x_bearing
+ if direction.is_forward() {
base_extents.width
} else {
0
}
- mark_extents.width / 2
- mark_extents.x_bearing;
}
Class::AttachedBelowLeft | Class::BelowLeft | Class::AboveLeft => {
// Left align.
pos.x_offset += base_extents.x_bearing - mark_extents.x_bearing;
}
Class::AttachedAboveRight | Class::BelowRight | Class::AboveRight => {
// Right align.
pos.x_offset += base_extents.x_bearing + base_extents.width
- mark_extents.width
- mark_extents.x_bearing;
}
Class::AttachedBelow | Class::AttachedAbove | Class::Below | Class::Above | _ => {
// Center align.
pos.x_offset += base_extents.x_bearing + (base_extents.width - mark_extents.width) / 2
- mark_extents.x_bearing;
}
}
let is_attached = matches!(
combining_class,
Class::AttachedBelowLeft
| Class::AttachedBelow
| Class::AttachedAbove
| Class::AttachedAboveRight
);
// Y positioning.
match combining_class {
Class::DoubleBelow
| Class::BelowLeft
| Class::Below
| Class::BelowRight
| Class::AttachedBelowLeft
| Class::AttachedBelow => {
if !is_attached {
// Add gap.
base_extents.height -= y_gap;
}
pos.y_offset = base_extents.y_bearing + base_extents.height - mark_extents.y_bearing;
// Never shift up "below" marks.
if (y_gap > 0) == (pos.y_offset > 0) {
base_extents.height -= pos.y_offset;
pos.y_offset = 0;
}
base_extents.height += mark_extents.height;
}
Class::DoubleAbove
| Class::AboveLeft
| Class::Above
| Class::AboveRight
| Class::AttachedAbove
| Class::AttachedAboveRight => {
if !is_attached {
// Add gap.
base_extents.y_bearing += y_gap;
base_extents.height -= y_gap;
}
pos.y_offset = base_extents.y_bearing - (mark_extents.y_bearing + mark_extents.height);
// Don't shift down "above" marks too much.
if (y_gap > 0) != (pos.y_offset > 0) {
let correction = -pos.y_offset / 2;
base_extents.y_bearing += correction;
base_extents.height -= correction;
pos.y_offset += correction;
}
base_extents.y_bearing -= mark_extents.height;
base_extents.height += mark_extents.height;
}
_ => {}
}
}
fn position_around_base(
plan: &hb_ot_shape_plan_t,
face: &hb_font_t,
buffer: &mut hb_buffer_t,
base: usize,
end: usize,
adjust_offsets_when_zeroing: bool,
) {
let mut horizontal_dir = Direction::Invalid;
buffer.unsafe_to_break(Some(base), Some(end));
let base_info = &buffer.info[base];
let base_pos = &buffer.pos[base];
let base_glyph = base_info.as_glyph();
let mut base_extents = GlyphExtents::default();
if !face.glyph_extents(base_glyph, &mut base_extents) {
zero_mark_advances(buffer, base + 1, end, adjust_offsets_when_zeroing);
return;
};
base_extents.y_bearing += base_pos.y_offset;
base_extents.x_bearing = 0;
// Use horizontal advance for horizontal positioning.
// Generally a better idea. Also works for zero-ink glyphs. See:
// https://github.com/harfbuzz/harfbuzz/issues/1532
base_extents.width = face.glyph_h_advance(base_glyph) as i32;
let lig_id = _hb_glyph_info_get_lig_id(base_info) as u32;
let num_lig_components = _hb_glyph_info_get_lig_num_comps(base_info) as i32;
let mut x_offset = 0;
let mut y_offset = 0;
if buffer.direction.is_forward() {
x_offset -= base_pos.x_advance;
y_offset -= base_pos.y_advance;
}
let mut last_lig_component: i32 = -1;
let mut last_combining_class: u8 = 255;
let mut component_extents = base_extents;
let mut cluster_extents = base_extents;
for (info, pos) in buffer.info[base + 1..end]
.iter()
.zip(&mut buffer.pos[base + 1..end])
{
if _hb_glyph_info_get_modified_combining_class(info) != 0 {
if num_lig_components > 1 {
let this_lig_id = _hb_glyph_info_get_lig_id(info) as u32;
let mut this_lig_component = _hb_glyph_info_get_lig_comp(info) as i32 - 1;
// Conditions for attaching to the last component.
if lig_id == 0 || lig_id != this_lig_id || this_lig_component >= num_lig_components
{
this_lig_component = num_lig_components - 1;
}
if last_lig_component != this_lig_component {
last_lig_component = this_lig_component;
last_combining_class = 255;
component_extents = base_extents;
if horizontal_dir == Direction::Invalid {
horizontal_dir = if plan.direction.is_horizontal() {
plan.direction
} else {
plan.script
.and_then(Direction::from_script)
.unwrap_or(Direction::LeftToRight)
};
}
component_extents.x_bearing += (if horizontal_dir == Direction::LeftToRight {
this_lig_component
} else {
num_lig_components - 1 - this_lig_component
} * component_extents.width)
/ num_lig_components;
component_extents.width /= num_lig_components;
}
}
let this_combining_class = _hb_glyph_info_get_modified_combining_class(info);
if last_combining_class != this_combining_class {
last_combining_class = this_combining_class;
cluster_extents = component_extents;
}
position_mark(
plan,
face,
buffer.direction,
info.as_glyph(),
pos,
&mut cluster_extents,
conv_combining_class(this_combining_class),
);
pos.x_advance = 0;
pos.y_advance = 0;
pos.x_offset += x_offset;
pos.y_offset += y_offset;
} else {
if buffer.direction.is_forward() {
x_offset -= pos.x_advance;
y_offset -= pos.y_advance;
} else {
x_offset += pos.x_advance;
y_offset += pos.y_advance;
}
}
}
}
fn position_cluster(
plan: &hb_ot_shape_plan_t,
face: &hb_font_t,
buffer: &mut hb_buffer_t,
start: usize,
end: usize,
adjust_offsets_when_zeroing: bool,
) {
if end - start < 2 {
return;
}
// Find the base glyph
let mut i = start;
while i < end {
if !_hb_glyph_info_is_unicode_mark(&buffer.info[i]) {
// Find mark glyphs
let mut j = i + 1;
while j < end && _hb_glyph_info_is_unicode_mark(&buffer.info[j]) {
j += 1;
}
position_around_base(plan, face, buffer, i, j, adjust_offsets_when_zeroing);
i = j - 1;
}
i += 1;
}
}
pub fn position_marks(
plan: &hb_ot_shape_plan_t,
face: &hb_font_t,
buffer: &mut hb_buffer_t,
adjust_offsets_when_zeroing: bool,
) {
let mut start = 0;
let len = buffer.len;
for i in 1..len {
if !_hb_glyph_info_is_unicode_mark(&buffer.info[i]) {
position_cluster(plan, face, buffer, start, i, adjust_offsets_when_zeroing);
start = i;
}
}
position_cluster(plan, face, buffer, start, len, adjust_offsets_when_zeroing);
}
pub fn _hb_ot_shape_fallback_kern(_: &hb_ot_shape_plan_t, _: &hb_font_t, _: &mut hb_buffer_t) {
// STUB: this is deprecated in HarfBuzz
}
pub fn _hb_ot_shape_fallback_spaces(
_: &hb_ot_shape_plan_t,
face: &hb_font_t,
buffer: &mut hb_buffer_t,
) {
use super::unicode::hb_unicode_funcs_t as t;
let len = buffer.len;
let horizontal = buffer.direction.is_horizontal();
for (info, pos) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) {
if _hb_glyph_info_is_unicode_space(&info) && !_hb_glyph_info_ligated(info) {
let space_type = _hb_glyph_info_get_unicode_space_fallback_type(info);
match space_type {
t::SPACE_EM
| t::SPACE_EM_2
| t::SPACE_EM_3
| t::SPACE_EM_4
| t::SPACE_EM_5
| t::SPACE_EM_6
| t::SPACE_EM_16 => {
let length =
(face.units_per_em as i32 + (space_type as i32) / 2) / space_type as i32;
if horizontal {
pos.x_advance = length;
} else {
pos.y_advance = -length;
}
}
t::SPACE_4_EM_18 => {
let length = ((face.units_per_em as i64) * 4 / 18) as i32;
if horizontal {
pos.x_advance = length
} else {
pos.y_advance = -length;
}
}
t::SPACE_FIGURE => {
for u in '0'..='9' {
if let Some(glyph) = face.get_nominal_glyph(u as u32) {
if horizontal {
pos.x_advance = face.glyph_h_advance(glyph) as i32;
} else {
pos.y_advance = face.glyph_v_advance(glyph);
}
break;
}
}
}
t::SPACE_PUNCTUATION => {
let punct = face
.get_nominal_glyph('.' as u32)
.or_else(|| face.get_nominal_glyph(',' as u32));
if let Some(glyph) = punct {
if horizontal {
pos.x_advance = face.glyph_h_advance(glyph) as i32;
} else {
pos.y_advance = face.glyph_v_advance(glyph);
}
}
}
t::SPACE_NARROW => {
// Half-space?
// Unicode doc https://unicode.org/charts/PDF/U2000.pdf says ~1/4 or 1/5 of EM.
// However, in my testing, many fonts have their regular space being about that
// size. To me, a percentage of the space width makes more sense. Half is as
// good as any.
if horizontal {
pos.x_advance /= 2;
} else {
pos.y_advance /= 2;
}
}
_ => {}
}
}
}
}
// TODO: can we cast directly?
fn conv_combining_class(n: u8) -> CanonicalCombiningClass {
use CanonicalCombiningClass as Class;
match n {
1 => Class::Overlay,
6 => Class::HanReading,
7 => Class::Nukta,
8 => Class::KanaVoicing,
9 => Class::Virama,
10 => Class::CCC10,
11 => Class::CCC11,
12 => Class::CCC12,
13 => Class::CCC13,
14 => Class::CCC14,
15 => Class::CCC15,
16 => Class::CCC16,
17 => Class::CCC17,
18 => Class::CCC18,
19 => Class::CCC19,
20 => Class::CCC20,
21 => Class::CCC21,
22 => Class::CCC22,
23 => Class::CCC23,
24 => Class::CCC24,
25 => Class::CCC25,
26 => Class::CCC26,
27 => Class::CCC27,
28 => Class::CCC28,
29 => Class::CCC29,
30 => Class::CCC30,
31 => Class::CCC31,
32 => Class::CCC32,
33 => Class::CCC33,
34 => Class::CCC34,
35 => Class::CCC35,
36 => Class::CCC36,
84 => Class::CCC84,
91 => Class::CCC91,
103 => Class::CCC103,
107 => Class::CCC107,
118 => Class::CCC118,
122 => Class::CCC122,
129 => Class::CCC129,
130 => Class::CCC130,
132 => Class::CCC132,
200 => Class::AttachedBelowLeft,
202 => Class::AttachedBelow,
214 => Class::AttachedAbove,
216 => Class::AttachedAboveRight,
218 => Class::BelowLeft,
220 => Class::Below,
222 => Class::BelowRight,
224 => Class::Left,
226 => Class::Right,
228 => Class::AboveLeft,
230 => Class::Above,
232 => Class::AboveRight,
233 => Class::DoubleBelow,
234 => Class::DoubleAbove,
240 => Class::IotaSubscript,
_ => Class::NotReordered,
}
}

View File

@@ -0,0 +1,468 @@
use super::buffer::*;
use super::common::hb_codepoint_t;
use super::hb_font_t;
use super::ot_layout::*;
use super::ot_shape_complex::MAX_COMBINING_MARKS;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::unicode::{hb_unicode_funcs_t, CharExt};
pub struct hb_ot_shape_normalize_context_t<'a> {
pub plan: &'a hb_ot_shape_plan_t,
pub buffer: &'a mut hb_buffer_t,
pub face: &'a hb_font_t<'a>,
pub decompose: fn(&hb_ot_shape_normalize_context_t, char) -> Option<(char, char)>,
pub compose: fn(&hb_ot_shape_normalize_context_t, char, char) -> Option<char>,
}
pub type hb_ot_shape_normalization_mode_t = i32;
pub const HB_OT_SHAPE_NORMALIZATION_MODE_NONE: i32 = 0;
pub const HB_OT_SHAPE_NORMALIZATION_MODE_DECOMPOSED: i32 = 1;
pub const HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS: i32 = 2; /* Never composes base-to-base */
pub const HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT: i32 = 3; /* Always fully decomposes and then recompose back */
pub const HB_OT_SHAPE_NORMALIZATION_MODE_AUTO: i32 = 4; /* See hb-ot-shape-normalize.cc for logic. */
#[allow(dead_code)]
pub const HB_OT_SHAPE_NORMALIZATION_MODE_DEFAULT: i32 = HB_OT_SHAPE_NORMALIZATION_MODE_AUTO;
// HIGHLEVEL DESIGN:
//
// This file exports one main function: normalize().
//
// This function closely reflects the Unicode Normalization Algorithm,
// yet it's different.
//
// Each shaper specifies whether it prefers decomposed (NFD) or composed (NFC).
// The logic however tries to use whatever the font can support.
//
// In general what happens is that: each grapheme is decomposed in a chain
// of 1:2 decompositions, marks reordered, and then recomposed if desired,
// so far it's like Unicode Normalization. However, the decomposition and
// recomposition only happens if the font supports the resulting characters.
//
// The goals are:
//
// - Try to render all canonically equivalent strings similarly. To really
// achieve this we have to always do the full decomposition and then
// selectively recompose from there. It's kinda too expensive though, so
// we skip some cases. For example, if composed is desired, we simply
// don't touch 1-character clusters that are supported by the font, even
// though their NFC may be different.
//
// - When a font has a precomposed character for a sequence but the 'ccmp'
// feature in the font is not adequate, use the precomposed character
// which typically has better mark positioning.
//
// - When a font does not support a combining mark, but supports it precomposed
// with previous base, use that. This needs the itemizer to have this
// knowledge too. We need to provide assistance to the itemizer.
//
// - When a font does not support a character but supports its canonical
// decomposition, well, use the decomposition.
//
// - The complex shapers can customize the compose and decompose functions to
// offload some of their requirements to the normalizer. For example, the
// Indic shaper may want to disallow recomposing of two matras.
fn decompose_unicode(
_: &hb_ot_shape_normalize_context_t,
ab: hb_codepoint_t,
) -> Option<(hb_codepoint_t, hb_codepoint_t)> {
super::unicode::decompose(ab)
}
fn compose_unicode(
_: &hb_ot_shape_normalize_context_t,
a: hb_codepoint_t,
b: hb_codepoint_t,
) -> Option<hb_codepoint_t> {
super::unicode::compose(a, b)
}
fn set_glyph(info: &mut hb_glyph_info_t, font: &hb_font_t) {
if let Some(glyph_id) = font.get_nominal_glyph(info.glyph_id) {
info.set_glyph_index(u32::from(glyph_id.0));
}
}
fn output_char(buffer: &mut hb_buffer_t, unichar: u32, glyph: u32) {
// This is very confusing indeed.
buffer.cur_mut(0).set_glyph_index(glyph);
buffer.output_glyph(unichar);
// TODO: should be _hb_glyph_info_set_unicode_props (&buffer->prev(), buffer);
let mut flags = buffer.scratch_flags;
buffer.prev_mut().init_unicode_props(&mut flags);
buffer.scratch_flags = flags;
}
fn next_char(buffer: &mut hb_buffer_t, glyph: u32) {
buffer.cur_mut(0).set_glyph_index(glyph);
buffer.next_glyph();
}
fn skip_char(buffer: &mut hb_buffer_t) {
buffer.skip_glyph();
}
/// Returns 0 if didn't decompose, number of resulting characters otherwise.
fn decompose(ctx: &mut hb_ot_shape_normalize_context_t, shortest: bool, ab: hb_codepoint_t) -> u32 {
let (a, b) = match (ctx.decompose)(ctx, ab) {
Some(decomposed) => decomposed,
_ => return 0,
};
let a_glyph = ctx.face.get_nominal_glyph(u32::from(a));
let b_glyph = if b != '\0' {
match ctx.face.get_nominal_glyph(u32::from(b)) {
Some(glyph_id) => Some(glyph_id),
None => return 0,
}
} else {
None
};
if !shortest || a_glyph.is_none() {
let ret = decompose(ctx, shortest, a);
if ret != 0 {
if let Some(b_glyph) = b_glyph {
output_char(ctx.buffer, u32::from(b), u32::from(b_glyph.0));
return ret + 1;
}
return ret;
}
}
if let Some(a_glyph) = a_glyph {
// Output a and b.
output_char(ctx.buffer, u32::from(a), u32::from(a_glyph.0));
if let Some(b_glyph) = b_glyph {
output_char(ctx.buffer, u32::from(b), u32::from(b_glyph.0));
return 2;
}
return 1;
}
0
}
fn decompose_current_character(ctx: &mut hb_ot_shape_normalize_context_t, shortest: bool) {
let u = ctx.buffer.cur(0).as_char();
let glyph = ctx.face.get_nominal_glyph(u32::from(u));
// TODO: different to harfbuzz, sync
if !shortest || glyph.is_none() {
if decompose(ctx, shortest, u) > 0 {
skip_char(ctx.buffer);
return;
}
}
// TODO: different to harfbuzz, sync
if let Some(glyph) = glyph {
next_char(ctx.buffer, u32::from(glyph.0));
return;
}
if _hb_glyph_info_is_unicode_space(ctx.buffer.cur(0)) {
let space_type = u.space_fallback();
if space_type != hb_unicode_funcs_t::NOT_SPACE {
let space_glyph = ctx.face.get_nominal_glyph(0x0020).or(ctx.buffer.invisible);
if let Some(space_glyph) = space_glyph {
_hb_glyph_info_set_unicode_space_fallback_type(ctx.buffer.cur_mut(0), space_type);
next_char(ctx.buffer, u32::from(space_glyph.0));
ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_SPACE_FALLBACK;
return;
}
}
}
// U+2011 is the only sensible character that is a no-break version of another character
// and not a space. The space ones are handled already. Handle this lone one.
if u == '\u{2011}' {
if let Some(other_glyph) = ctx.face.get_nominal_glyph(0x2010) {
next_char(ctx.buffer, u32::from(other_glyph.0));
return;
}
}
// Insert a .notdef glyph if decomposition failed.
next_char(ctx.buffer, 0);
}
fn handle_variation_selector_cluster(
ctx: &mut hb_ot_shape_normalize_context_t,
end: usize,
_: bool,
) {
let face = ctx.face;
// TODO: Currently if there's a variation-selector we give-up, it's just too hard.
let buffer = &mut ctx.buffer;
while buffer.idx < end - 1 && buffer.successful {
if buffer.cur(1).as_char().is_variation_selector() {
if let Some(glyph_id) =
face.glyph_variation_index(buffer.cur(0).as_char(), buffer.cur(1).as_char())
{
buffer.cur_mut(0).set_glyph_index(u32::from(glyph_id.0));
let unicode = buffer.cur(0).glyph_id;
buffer.replace_glyphs(2, 1, &[unicode]);
} else {
// Just pass on the two characters separately, let GSUB do its magic.
set_glyph(buffer.cur_mut(0), face);
buffer.next_glyph();
set_glyph(buffer.cur_mut(0), face);
buffer.next_glyph();
}
// Skip any further variation selectors.
while buffer.idx < end && buffer.cur(0).as_char().is_variation_selector() {
set_glyph(buffer.cur_mut(0), face);
buffer.next_glyph();
}
} else {
set_glyph(buffer.cur_mut(0), face);
buffer.next_glyph();
}
}
if ctx.buffer.idx < end {
set_glyph(ctx.buffer.cur_mut(0), face);
ctx.buffer.next_glyph();
}
}
fn decompose_multi_char_cluster(
ctx: &mut hb_ot_shape_normalize_context_t,
end: usize,
short_circuit: bool,
) {
let mut i = ctx.buffer.idx;
while i < end && ctx.buffer.successful {
if ctx.buffer.info[i].as_char().is_variation_selector() {
handle_variation_selector_cluster(ctx, end, short_circuit);
return;
}
i += 1;
}
while ctx.buffer.idx < end && ctx.buffer.successful {
decompose_current_character(ctx, short_circuit);
}
}
fn compare_combining_class(pa: &hb_glyph_info_t, pb: &hb_glyph_info_t) -> bool {
let a = _hb_glyph_info_get_modified_combining_class(pa);
let b = _hb_glyph_info_get_modified_combining_class(pb);
a > b
}
pub fn _hb_ot_shape_normalize(
plan: &hb_ot_shape_plan_t,
buffer: &mut hb_buffer_t,
face: &hb_font_t,
) {
if buffer.is_empty() {
return;
}
let mut mode = plan.shaper.normalization_preference;
if mode == HB_OT_SHAPE_NORMALIZATION_MODE_AUTO {
if plan.has_gpos_mark {
// https://github.com/harfbuzz/harfbuzz/issues/653#issuecomment-423905920
// mode = Some(HB_OT_SHAPE_NORMALIZATION_MODE_DECOMPOSED);
mode = HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS;
} else {
mode = HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS;
}
}
let mut ctx = hb_ot_shape_normalize_context_t {
plan,
buffer,
face,
decompose: plan.shaper.decompose.unwrap_or(decompose_unicode),
compose: plan.shaper.compose.unwrap_or(compose_unicode),
};
let mut buffer = &mut ctx.buffer;
let always_short_circuit = mode == HB_OT_SHAPE_NORMALIZATION_MODE_NONE;
let might_short_circuit = always_short_circuit
|| (mode != HB_OT_SHAPE_NORMALIZATION_MODE_DECOMPOSED
&& mode != HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT);
// We do a fairly straightforward yet custom normalization process in three
// separate rounds: decompose, reorder, recompose (if desired). Currently
// this makes two buffer swaps. We can make it faster by moving the last
// two rounds into the inner loop for the first round, but it's more readable
// this way.
// First round, decompose
let mut all_simple = true;
{
buffer.clear_output();
let count = buffer.len;
buffer.idx = 0;
loop {
let mut end = buffer.idx + 1;
while end < count && !_hb_glyph_info_is_unicode_mark(&buffer.info[end]) {
end += 1;
}
if end < count {
// Leave one base for the marks to cluster with.
end -= 1;
}
// From idx to end are simple clusters.
if might_short_circuit {
let len = end - buffer.idx;
let mut done = 0;
while done < len {
let cur = buffer.cur_mut(done);
cur.set_glyph_index(match face.get_nominal_glyph(cur.glyph_id) {
Some(glyph_id) => u32::from(glyph_id.0),
None => break,
});
done += 1;
}
buffer.next_glyphs(done);
}
while buffer.idx < end && buffer.successful {
decompose_current_character(&mut ctx, might_short_circuit);
buffer = &mut ctx.buffer;
}
if buffer.idx == count || !buffer.successful {
break;
}
all_simple = false;
// Find all the marks now.
end = buffer.idx + 1;
while end < count && _hb_glyph_info_is_unicode_mark(&buffer.info[end]) {
end += 1;
}
// idx to end is one non-simple cluster.
decompose_multi_char_cluster(&mut ctx, end, always_short_circuit);
buffer = &mut ctx.buffer;
if buffer.idx >= count || !buffer.successful {
break;
}
}
buffer.sync();
}
// Second round, reorder (inplace)
if !all_simple {
let count = buffer.len;
let mut i = 0;
while i < count {
if _hb_glyph_info_get_modified_combining_class(&buffer.info[i]) == 0 {
i += 1;
continue;
}
let mut end = i + 1;
while end < count && _hb_glyph_info_get_modified_combining_class(&buffer.info[end]) != 0
{
end += 1;
}
// We are going to do a O(n^2). Only do this if the sequence is short.
if end - i <= MAX_COMBINING_MARKS {
buffer.sort(i, end, compare_combining_class);
if let Some(reorder_marks) = ctx.plan.shaper.reorder_marks {
reorder_marks(ctx.plan, buffer, i, end);
}
}
i = end + 1;
}
}
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_CGJ != 0 {
// For all CGJ, check if it prevented any reordering at all.
// If it did NOT, then make it skippable.
// https://github.com/harfbuzz/harfbuzz/issues/554
for i in 1..buffer.len.saturating_sub(1) {
if buffer.info[i].glyph_id == 0x034F
/* CGJ */
{
let last = _hb_glyph_info_get_modified_combining_class(&buffer.info[i - 1]);
let next = _hb_glyph_info_get_modified_combining_class(&buffer.info[i + 1]);
if next == 0 || last <= next {
buffer.info[i].unhide();
}
}
}
}
// Third round, recompose
if !all_simple
&& buffer.successful
&& (mode == HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS
|| mode == HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT)
{
// As noted in the comment earlier, we don't try to combine
// ccc=0 chars with their previous Starter.
let count = buffer.len;
let mut starter = 0;
buffer.clear_output();
buffer.next_glyph();
while buffer.idx < count && buffer.successful {
// We don't try to compose a non-mark character with it's preceding starter.
// This is both an optimization to avoid trying to compose every two neighboring
// glyphs in most scripts AND a desired feature for Hangul. Apparently Hangul
// fonts are not designed to mix-and-match pre-composed syllables and Jamo.
let cur = buffer.cur(0);
if _hb_glyph_info_is_unicode_mark(cur) &&
// If there's anything between the starter and this char, they should have CCC
// smaller than this character's.
(starter == buffer.out_len - 1
|| _hb_glyph_info_get_modified_combining_class(buffer.prev()) < _hb_glyph_info_get_modified_combining_class(cur))
{
let a = buffer.out_info()[starter].as_char();
let b = cur.as_char();
if let Some(composed) = (ctx.compose)(&ctx, a, b) {
if let Some(glyph_id) = face.get_nominal_glyph(u32::from(composed)) {
// Copy to out-buffer.
buffer = &mut ctx.buffer;
buffer.next_glyph();
if !buffer.successful {
return;
}
// Merge and remove the second composable.
buffer.merge_out_clusters(starter, buffer.out_len);
buffer.out_len -= 1;
// Modify starter and carry on.
let mut flags = buffer.scratch_flags;
let info = &mut buffer.out_info_mut()[starter];
info.glyph_id = u32::from(composed);
info.set_glyph_index(u32::from(glyph_id.0));
info.init_unicode_props(&mut flags);
buffer.scratch_flags = flags;
continue;
}
}
}
// Blocked, or doesn't compose.
buffer = &mut ctx.buffer;
buffer.next_glyph();
if _hb_glyph_info_get_modified_combining_class(buffer.prev()) == 0 {
starter = buffer.out_len - 1;
}
}
buffer.sync();
}
}

View File

@@ -0,0 +1,75 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::any::Any;
use super::ot_map::*;
use super::ot_shape::*;
use super::ot_shape_complex::*;
use super::{aat_map, hb_font_t, hb_mask_t, Direction, Feature, Language, Script};
/// A reusable plan for shaping a text buffer.
pub struct hb_ot_shape_plan_t {
pub(crate) direction: Direction,
pub(crate) script: Option<Script>,
pub(crate) shaper: &'static hb_ot_complex_shaper_t,
pub(crate) ot_map: hb_ot_map_t,
pub(crate) aat_map: aat_map::hb_aat_map_t,
pub(crate) data: Option<Box<dyn Any + Send + Sync>>,
pub(crate) frac_mask: hb_mask_t,
pub(crate) numr_mask: hb_mask_t,
pub(crate) dnom_mask: hb_mask_t,
pub(crate) rtlm_mask: hb_mask_t,
pub(crate) kern_mask: hb_mask_t,
pub(crate) trak_mask: hb_mask_t,
pub(crate) requested_kerning: bool,
pub(crate) has_frac: bool,
pub(crate) has_vert: bool,
pub(crate) has_gpos_mark: bool,
pub(crate) zero_marks: bool,
pub(crate) fallback_glyph_classes: bool,
pub(crate) fallback_mark_positioning: bool,
pub(crate) adjust_mark_positioning_when_zeroing: bool,
pub(crate) apply_gpos: bool,
pub(crate) apply_fallback_kern: bool,
pub(crate) apply_kern: bool,
pub(crate) apply_kerx: bool,
pub(crate) apply_morx: bool,
pub(crate) apply_trak: bool,
pub(crate) user_features: Vec<Feature>,
}
impl hb_ot_shape_plan_t {
/// Returns a plan that can be used for shaping any buffer with the
/// provided properties.
pub fn new(
face: &hb_font_t,
direction: Direction,
script: Option<Script>,
language: Option<&Language>,
user_features: &[Feature],
) -> Self {
assert_ne!(direction, Direction::Invalid);
let mut planner = hb_ot_shape_planner_t::new(face, direction, script, language);
planner.collect_features(user_features);
planner.compile(user_features)
}
pub(crate) fn data<T: 'static>(&self) -> &T {
self.data.as_ref().unwrap().downcast_ref().unwrap()
}
}
#[cfg(test)]
mod tests {
use super::hb_ot_shape_plan_t;
#[test]
fn test_shape_plan_is_send_and_sync() {
fn ensure_send_and_sync<T: Send + Sync>() {}
ensure_send_and_sync::<hb_ot_shape_plan_t>();
}
}

64
vendor/rustybuzz/src/hb/shape.rs vendored Normal file
View File

@@ -0,0 +1,64 @@
use super::hb_font_t;
use super::ot_shape::{shape_internal, ShapeContext};
use super::ot_shape_plan::hb_ot_shape_plan_t;
use crate::{script, Feature, GlyphBuffer, UnicodeBuffer};
/// Shapes the buffer content using provided font and features.
///
/// Consumes the buffer. You can then run [`GlyphBuffer::clear`] to get the [`UnicodeBuffer`] back
/// without allocating a new one.
///
/// If you plan to shape multiple strings using the same [`Face`] prefer [`shape_with_plan`].
/// This is because [`ShapePlan`] initialization is pretty slow and should preferably be called
/// once for each [`Face`].
pub fn shape(face: &hb_font_t, features: &[Feature], mut buffer: UnicodeBuffer) -> GlyphBuffer {
buffer.0.guess_segment_properties();
let plan = hb_ot_shape_plan_t::new(
face,
buffer.0.direction,
buffer.0.script,
buffer.0.language.as_ref(),
features,
);
shape_with_plan(face, &plan, buffer)
}
/// Shapes the buffer content using the provided font and plan.
///
/// Consumes the buffer. You can then run [`GlyphBuffer::clear`] to get the [`UnicodeBuffer`] back
/// without allocating a new one.
///
/// It is up to the caller to ensure that the shape plan matches the properties of the provided
/// buffer, otherwise the shaping result will likely be incorrect.
///
/// # Panics
///
/// Will panic when debugging assertions are enabled if the buffer and plan have mismatched
/// properties.
pub fn shape_with_plan(
face: &hb_font_t,
plan: &hb_ot_shape_plan_t,
buffer: UnicodeBuffer,
) -> GlyphBuffer {
let mut buffer = buffer.0;
buffer.guess_segment_properties();
debug_assert_eq!(buffer.direction, plan.direction);
debug_assert_eq!(
buffer.script.unwrap_or(script::UNKNOWN),
plan.script.unwrap_or(script::UNKNOWN)
);
if buffer.len > 0 {
// Save the original direction, we use it later.
let target_direction = buffer.direction;
shape_internal(&mut ShapeContext {
plan,
face,
buffer: &mut buffer,
target_direction,
});
}
GlyphBuffer(buffer)
}

654
vendor/rustybuzz/src/hb/tag.rs vendored Normal file
View File

@@ -0,0 +1,654 @@
use core::str::FromStr;
use smallvec::SmallVec;
use super::common::TagExt;
use super::{hb_tag_t, script, tag_table, Language, Script};
type ThreeTags = SmallVec<[hb_tag_t; 3]>;
trait SmallVecExt {
fn left(&self) -> usize;
fn is_full(&self) -> bool;
}
impl<A: smallvec::Array> SmallVecExt for SmallVec<A> {
fn left(&self) -> usize {
self.inline_size() - self.len()
}
fn is_full(&self) -> bool {
self.len() == self.inline_size()
}
}
/// Converts an `Script` and an `Language` to script and language tags.
pub fn tags_from_script_and_language(
script: Option<Script>,
language: Option<&Language>,
) -> (ThreeTags, ThreeTags) {
let mut needs_script = true;
let mut scripts = SmallVec::new();
let mut languages = SmallVec::new();
let mut private_use_subtag = None;
let mut prefix = "";
if let Some(language) = language {
let language = language.as_str();
if language.starts_with("x-") {
private_use_subtag = Some(language);
} else {
let bytes = language.as_bytes();
let mut i = 1;
while i < bytes.len() {
if bytes.get(i - 1) == Some(&b'-') && bytes.get(i + 1) == Some(&b'-') {
if bytes[i] == b'x' {
private_use_subtag = Some(&language[i..]);
if prefix.is_empty() {
prefix = &language[..i - 1];
}
break;
} else {
prefix = &language[..i - 1];
}
}
i += 1;
}
if prefix.is_empty() {
prefix = &language[..i];
}
}
needs_script = !parse_private_use_subtag(
private_use_subtag,
"-hbsc",
u8::to_ascii_lowercase,
&mut scripts,
);
let needs_language = !parse_private_use_subtag(
private_use_subtag,
"-hbot",
u8::to_ascii_uppercase,
&mut languages,
);
if needs_language {
if let Ok(prefix) = Language::from_str(prefix) {
tags_from_language(&prefix, &mut languages);
}
}
}
if needs_script {
all_tags_from_script(script, &mut scripts);
}
(scripts, languages)
}
fn parse_private_use_subtag(
private_use_subtag: Option<&str>,
prefix: &str,
normalize: fn(&u8) -> u8,
tags: &mut ThreeTags,
) -> bool {
let private_use_subtag = match private_use_subtag {
Some(v) => v,
None => return false,
};
let private_use_subtag = match private_use_subtag.find(prefix) {
Some(idx) => &private_use_subtag[idx + prefix.len()..],
None => return false,
};
let mut tag = SmallVec::<[u8; 4]>::new();
for c in private_use_subtag.bytes().take(4) {
if c.is_ascii_alphanumeric() {
tag.push((normalize)(&c));
} else {
break;
}
}
if tag.is_empty() {
return false;
}
let mut tag = hb_tag_t::from_bytes_lossy(tag.as_slice());
// Some bits magic from HarfBuzz...
if tag.as_u32() & 0xDFDFDFDF == hb_tag_t::default_script().as_u32() {
tag = hb_tag_t(tag.as_u32() ^ !0xDFDFDFDF);
}
tags.push(tag);
true
}
fn lang_cmp(s1: &str, s2: &str) -> core::cmp::Ordering {
let da = s1.find('-').unwrap_or(s1.len());
let db = s2.find('-').unwrap_or(s2.len());
let n = core::cmp::max(da, db);
let ea = core::cmp::min(n, s1.len());
let eb = core::cmp::min(n, s2.len());
s1[..ea].cmp(&s2[..eb])
}
fn tags_from_language(language: &Language, tags: &mut ThreeTags) {
let language = language.as_str();
// Check for matches of multiple subtags.
if tag_table::tags_from_complex_language(language, tags) {
return;
}
let mut sublang = language;
// Find a language matching in the first component.
if let Some(i) = language.find('-') {
// If there is an extended language tag, use it.
if language.len() >= 6 {
let extlang = match language[i + 1..].find('-') {
Some(idx) => idx == 3,
None => language.len() - i - 1 == 3,
};
if extlang && language.as_bytes()[i + 1].is_ascii_alphabetic() {
sublang = &language[i + 1..];
}
}
}
use tag_table::OPEN_TYPE_LANGUAGES as LANGUAGES;
if let Ok(mut idx) = LANGUAGES.binary_search_by(|v| lang_cmp(v.language, sublang)) {
while idx != 0 && LANGUAGES[idx].language == LANGUAGES[idx - 1].language {
idx -= 1;
}
let len = core::cmp::min(tags.left(), LANGUAGES.len() - idx - 1);
for i in 0..len {
if LANGUAGES[idx + i].language != LANGUAGES[idx].language {
break;
}
if LANGUAGES[idx + i].tag.is_null() {
break;
}
if tags.is_full() {
break;
}
tags.push(LANGUAGES[idx + i].tag);
}
return;
}
if language.len() == 3 {
tags.push(hb_tag_t::from_bytes_lossy(language.as_bytes()).to_uppercase());
}
}
fn all_tags_from_script(script: Option<Script>, tags: &mut ThreeTags) {
if let Some(script) = script {
if let Some(tag) = new_tag_from_script(script) {
// Script::Myanmar maps to 'mym2', but there is no 'mym3'.
if tag != hb_tag_t::from_bytes(b"mym2") {
let mut tag3 = tag.to_bytes();
tag3[3] = b'3';
tags.push(hb_tag_t::from_bytes(&tag3));
}
if !tags.is_full() {
tags.push(tag);
}
}
if !tags.is_full() {
tags.push(old_tag_from_script(script));
}
}
}
fn new_tag_from_script(script: Script) -> Option<hb_tag_t> {
match script {
script::BENGALI => Some(hb_tag_t::from_bytes(b"bng2")),
script::DEVANAGARI => Some(hb_tag_t::from_bytes(b"dev2")),
script::GUJARATI => Some(hb_tag_t::from_bytes(b"gjr2")),
script::GURMUKHI => Some(hb_tag_t::from_bytes(b"gur2")),
script::KANNADA => Some(hb_tag_t::from_bytes(b"knd2")),
script::MALAYALAM => Some(hb_tag_t::from_bytes(b"mlm2")),
script::ORIYA => Some(hb_tag_t::from_bytes(b"ory2")),
script::TAMIL => Some(hb_tag_t::from_bytes(b"tml2")),
script::TELUGU => Some(hb_tag_t::from_bytes(b"tel2")),
script::MYANMAR => Some(hb_tag_t::from_bytes(b"mym2")),
_ => None,
}
}
fn old_tag_from_script(script: Script) -> hb_tag_t {
// This seems to be accurate as of end of 2012.
match script {
// Katakana and Hiragana both map to 'kana'.
script::HIRAGANA => hb_tag_t::from_bytes(b"kana"),
// Spaces at the end are preserved, unlike ISO 15924.
script::LAO => hb_tag_t::from_bytes(b"lao "),
script::YI => hb_tag_t::from_bytes(b"yi "),
// Unicode-5.0 additions.
script::NKO => hb_tag_t::from_bytes(b"nko "),
// Unicode-5.1 additions.
script::VAI => hb_tag_t::from_bytes(b"vai "),
// Else, just change first char to lowercase and return.
_ => hb_tag_t(script.tag().as_u32() | 0x20000000),
}
}
#[rustfmt::skip]
#[cfg(test)]
mod tests {
#![allow(non_snake_case)]
use super::*;
use core::str::FromStr;
use alloc::vec::Vec;
fn new_tag_to_script(tag: hb_tag_t) -> Option<Script> {
match &tag.to_bytes() {
b"bng2" => Some(script::BENGALI),
b"dev2" => Some(script::DEVANAGARI),
b"gjr2" => Some(script::GUJARATI),
b"gur2" => Some(script::GURMUKHI),
b"knd2" => Some(script::KANNADA),
b"mlm2" => Some(script::MALAYALAM),
b"ory2" => Some(script::ORIYA),
b"tml2" => Some(script::TAMIL),
b"tel2" => Some(script::TELUGU),
b"mym2" => Some(script::MYANMAR),
_ => Some(script::UNKNOWN),
}
}
fn old_tag_to_script(tag: hb_tag_t) -> Option<Script> {
if tag == hb_tag_t::default_script() {
return None;
}
let mut bytes = tag.to_bytes();
// This side of the conversion is fully algorithmic.
// Any spaces at the end of the tag are replaced by repeating the last
// letter. Eg 'nko ' -> 'Nkoo'
if bytes[2] == b' ' {
bytes[2] = bytes[1];
}
if bytes[3] == b' ' {
bytes[3] = bytes[2];
}
// Change first char to uppercase.
bytes[0] = bytes[0].to_ascii_uppercase();
Some(Script(hb_tag_t::from_bytes(&bytes)))
}
fn tag_to_script(tag: hb_tag_t) -> Option<Script> {
let bytes = tag.to_bytes();
if bytes[3] == b'2' || bytes[3] == b'3' {
let mut tag2 = bytes;
tag2[3] = b'2';
return new_tag_to_script(hb_tag_t::from_bytes(&tag2));
}
old_tag_to_script(tag)
}
fn test_simple_tags(tag: &str, script: Script) {
let tag = hb_tag_t::from_bytes_lossy(tag.as_bytes());
let (scripts, _) = tags_from_script_and_language(Some(script), None);
if !scripts.is_empty() {
assert_eq!(tag, scripts[0]);
} else {
assert_eq!(tag, hb_tag_t::default_script());
}
assert_eq!(tag_to_script(tag), Some(script));
}
#[test]
fn tag_to_uppercase() {
assert_eq!(hb_tag_t::from_bytes(b"abcd").to_uppercase(), hb_tag_t::from_bytes(b"ABCD"));
assert_eq!(hb_tag_t::from_bytes(b"abc ").to_uppercase(), hb_tag_t::from_bytes(b"ABC "));
assert_eq!(hb_tag_t::from_bytes(b"ABCD").to_uppercase(), hb_tag_t::from_bytes(b"ABCD"));
}
#[test]
fn tag_to_lowercase() {
assert_eq!(hb_tag_t::from_bytes(b"abcd").to_lowercase(), hb_tag_t::from_bytes(b"abcd"));
assert_eq!(hb_tag_t::from_bytes(b"abc ").to_lowercase(), hb_tag_t::from_bytes(b"abc "));
assert_eq!(hb_tag_t::from_bytes(b"ABCD").to_lowercase(), hb_tag_t::from_bytes(b"abcd"));
}
#[test]
fn script_degenerate() {
assert_eq!(hb_tag_t::from_bytes(b"DFLT"), hb_tag_t::default_script());
// Hiragana and Katakana both map to 'kana'.
test_simple_tags("kana", script::KATAKANA);
let (scripts, _) = tags_from_script_and_language(Some(script::HIRAGANA), None);
assert_eq!(scripts.as_slice(), &[hb_tag_t::from_bytes(b"kana")]);
// Spaces are replaced
assert_eq!(tag_to_script(hb_tag_t::from_bytes(b"be ")), Script::from_iso15924_tag(hb_tag_t::from_bytes(b"Beee")));
}
#[test]
fn script_simple() {
// Arbitrary non-existent script.
test_simple_tags("wwyz", Script::from_iso15924_tag(hb_tag_t::from_bytes(b"wWyZ")).unwrap());
// These we don't really care about.
test_simple_tags("zyyy", script::COMMON);
test_simple_tags("zinh", script::INHERITED);
test_simple_tags("zzzz", script::UNKNOWN);
test_simple_tags("arab", script::ARABIC);
test_simple_tags("copt", script::COPTIC);
test_simple_tags("kana", script::KATAKANA);
test_simple_tags("latn", script::LATIN);
// These are trickier since their OT script tags have space.
test_simple_tags("lao ", script::LAO);
test_simple_tags("yi ", script::YI);
// Unicode-5.0 additions.
test_simple_tags("nko ", script::NKO);
// Unicode-5.1 additions.
test_simple_tags("vai ", script::VAI);
// https://docs.microsoft.com/en-us/typography/opentype/spec/scripttags
// Unicode-5.2 additions.
test_simple_tags("mtei", script::MEETEI_MAYEK);
// Unicode-6.0 additions.
test_simple_tags("mand", script::MANDAIC);
}
macro_rules! test_script_from_language {
($name:ident, $tag:expr, $lang:expr, $script:expr) => {
#[test]
fn $name() {
let tag = hb_tag_t::from_bytes_lossy($tag.as_bytes());
let (scripts, _) = tags_from_script_and_language(
$script, Language::from_str($lang).ok().as_ref(),
);
if !scripts.is_empty() {
assert_eq!(scripts.as_slice(), &[tag]);
}
}
};
}
test_script_from_language!(script_from_language_01, "", "", None);
test_script_from_language!(script_from_language_02, "", "en", None);
test_script_from_language!(script_from_language_03, "copt", "en", Some(script::COPTIC));
test_script_from_language!(script_from_language_04, "", "x-hbsc", None);
test_script_from_language!(script_from_language_05, "copt", "x-hbsc", Some(script::COPTIC));
test_script_from_language!(script_from_language_06, "abc ", "x-hbscabc", None);
test_script_from_language!(script_from_language_07, "deva", "x-hbscdeva", None);
test_script_from_language!(script_from_language_08, "dev2", "x-hbscdev2", None);
test_script_from_language!(script_from_language_09, "dev3", "x-hbscdev3", None);
test_script_from_language!(script_from_language_10, "copt", "x-hbotpap0-hbsccopt", None);
test_script_from_language!(script_from_language_11, "", "en-x-hbsc", None);
test_script_from_language!(script_from_language_12, "copt", "en-x-hbsc", Some(script::COPTIC));
test_script_from_language!(script_from_language_13, "abc ", "en-x-hbscabc", None);
test_script_from_language!(script_from_language_14, "deva", "en-x-hbscdeva", None);
test_script_from_language!(script_from_language_15, "dev2", "en-x-hbscdev2", None);
test_script_from_language!(script_from_language_16, "dev3", "en-x-hbscdev3", None);
test_script_from_language!(script_from_language_17, "copt", "en-x-hbotpap0-hbsccopt", None);
#[test]
fn script_indic() {
fn check(tag1: &str, tag2: &str, tag3: &str, script: Script) {
let tag1 = hb_tag_t::from_bytes_lossy(tag1.as_bytes());
let tag2 = hb_tag_t::from_bytes_lossy(tag2.as_bytes());
let tag3 = hb_tag_t::from_bytes_lossy(tag3.as_bytes());
let (scripts, _) = tags_from_script_and_language(Some(script), None);
assert_eq!(scripts.as_slice(), &[tag1, tag2, tag3]);
assert_eq!(tag_to_script(tag1), Some(script));
assert_eq!(tag_to_script(tag2), Some(script));
assert_eq!(tag_to_script(tag3), Some(script));
}
check("bng3", "bng2", "beng", script::BENGALI);
check("dev3", "dev2", "deva", script::DEVANAGARI);
check("gjr3", "gjr2", "gujr", script::GUJARATI);
check("gur3", "gur2", "guru", script::GURMUKHI);
check("knd3", "knd2", "knda", script::KANNADA);
check("mlm3", "mlm2", "mlym", script::MALAYALAM);
check("ory3", "ory2", "orya", script::ORIYA);
check("tml3", "tml2", "taml", script::TAMIL);
check("tel3", "tel2", "telu", script::TELUGU);
}
// TODO: swap tag and lang
macro_rules! test_tag_from_language {
($name:ident, $tag:expr, $lang:expr) => {
#[test]
fn $name() {
let tag = hb_tag_t::from_bytes_lossy($tag.as_bytes());
let (_, languages) = tags_from_script_and_language(
None, Language::from_str(&$lang.to_lowercase()).ok().as_ref(),
);
if !languages.is_empty() {
assert_eq!(languages[0], tag);
}
}
};
}
test_tag_from_language!(tag_from_language_dflt, "dflt", "");
test_tag_from_language!(tag_from_language_ALT, "ALT", "alt");
test_tag_from_language!(tag_from_language_ARA, "ARA", "ar");
test_tag_from_language!(tag_from_language_AZE, "AZE", "az");
test_tag_from_language!(tag_from_language_az_ir, "AZE", "az-ir");
test_tag_from_language!(tag_from_language_az_az, "AZE", "az-az");
test_tag_from_language!(tag_from_language_ENG, "ENG", "en");
test_tag_from_language!(tag_from_language_en_US, "ENG", "en_US");
test_tag_from_language!(tag_from_language_CJA, "CJA", "cja"); /* Western Cham */
test_tag_from_language!(tag_from_language_CJM, "CJM", "cjm"); /* Eastern Cham */
test_tag_from_language!(tag_from_language_ENV, "EVN", "eve");
test_tag_from_language!(tag_from_language_HAL, "HAL", "cfm"); /* BCP47 and current ISO639-3 code for Halam/Falam Chin */
test_tag_from_language!(tag_from_language_flm, "HAL", "flm"); /* Retired ISO639-3 code for Halam/Falam Chin */
test_tag_from_language!(tag_from_language_hy, "HYE0", "hy");
test_tag_from_language!(tag_from_language_hyw, "HYE", "hyw");
test_tag_from_language!(tag_from_language_bgr, "QIN", "bgr"); /* Bawm Chin */
test_tag_from_language!(tag_from_language_cbl, "QIN", "cbl"); /* Bualkhaw Chin */
test_tag_from_language!(tag_from_language_cka, "QIN", "cka"); /* Khumi Awa Chin */
test_tag_from_language!(tag_from_language_cmr, "QIN", "cmr"); /* Mro-Khimi Chin */
test_tag_from_language!(tag_from_language_cnb, "QIN", "cnb"); /* Chinbon Chin */
test_tag_from_language!(tag_from_language_cnh, "QIN", "cnh"); /* Hakha Chin */
test_tag_from_language!(tag_from_language_cnk, "QIN", "cnk"); /* Khumi Chin */
test_tag_from_language!(tag_from_language_cnw, "QIN", "cnw"); /* Ngawn Chin */
test_tag_from_language!(tag_from_language_csh, "QIN", "csh"); /* Asho Chin */
test_tag_from_language!(tag_from_language_csy, "QIN", "csy"); /* Siyin Chin */
test_tag_from_language!(tag_from_language_ctd, "QIN", "ctd"); /* Tedim Chin */
test_tag_from_language!(tag_from_language_czt, "QIN", "czt"); /* Zotung Chin */
test_tag_from_language!(tag_from_language_dao, "QIN", "dao"); /* Daai Chin */
test_tag_from_language!(tag_from_language_htl, "QIN", "hlt"); /* Matu Chin */
test_tag_from_language!(tag_from_language_mrh, "QIN", "mrh"); /* Mara Chin */
test_tag_from_language!(tag_from_language_pck, "QIN", "pck"); /* Paite Chin */
test_tag_from_language!(tag_from_language_sez, "QIN", "sez"); /* Senthang Chin */
test_tag_from_language!(tag_from_language_tcp, "QIN", "tcp"); /* Tawr Chin */
test_tag_from_language!(tag_from_language_tcz, "QIN", "tcz"); /* Thado Chin */
test_tag_from_language!(tag_from_language_yos, "QIN", "yos"); /* Yos, deprecated by IANA in favor of Zou [zom] */
test_tag_from_language!(tag_from_language_zom, "QIN", "zom"); /* Zou */
test_tag_from_language!(tag_from_language_FAR, "FAR", "fa");
test_tag_from_language!(tag_from_language_fa_IR, "FAR", "fa_IR");
test_tag_from_language!(tag_from_language_man, "MNK", "man");
test_tag_from_language!(tag_from_language_SWA, "SWA", "aii"); /* Swadaya Aramaic */
test_tag_from_language!(tag_from_language_SYR, "SYR", "syr"); /* Syriac [macrolanguage] */
test_tag_from_language!(tag_from_language_amw, "SYR", "amw"); /* Western Neo-Aramaic */
test_tag_from_language!(tag_from_language_cld, "SYR", "cld"); /* Chaldean Neo-Aramaic */
test_tag_from_language!(tag_from_language_syc, "SYR", "syc"); /* Classical Syriac */
test_tag_from_language!(tag_from_language_TUA, "TUA", "tru"); /* Turoyo Aramaic */
test_tag_from_language!(tag_from_language_zh, "ZHS", "zh"); /* Chinese */
test_tag_from_language!(tag_from_language_zh_cn, "ZHS", "zh-cn"); /* Chinese (China) */
test_tag_from_language!(tag_from_language_zh_sg, "ZHS", "zh-sg"); /* Chinese (Singapore) */
test_tag_from_language!(tag_from_language_zh_mo, "ZHTM", "zh-mo"); /* Chinese (Macao) */
test_tag_from_language!(tag_from_language_zh_hant_mo, "ZHTM", "zh-hant-mo"); /* Chinese (Macao) */
test_tag_from_language!(tag_from_language_zh_hans_mo, "ZHS", "zh-hans-mo"); /* Chinese (Simplified, Macao) */
test_tag_from_language!(tag_from_language_ZHH, "ZHH", "zh-HK"); /* Chinese (Hong Kong) */
test_tag_from_language!(tag_from_language_zh_HanT_hK, "ZHH", "zH-HanT-hK"); /* Chinese (Hong Kong) */
test_tag_from_language!(tag_from_language_zh_HanS_hK, "ZHS", "zH-HanS-hK"); /* Chinese (Simplified, Hong Kong) */
test_tag_from_language!(tag_from_language_zh_tw, "ZHT", "zh-tw"); /* Chinese (Taiwan) */
test_tag_from_language!(tag_from_language_ZHS, "ZHS", "zh-Hans"); /* Chinese (Simplified) */
test_tag_from_language!(tag_from_language_ZHT, "ZHT", "zh-Hant"); /* Chinese (Traditional) */
test_tag_from_language!(tag_from_language_zh_xx, "ZHS", "zh-xx"); /* Chinese (Other) */
test_tag_from_language!(tag_from_language_zh_Hans_TW, "ZHS", "zh-Hans-TW");
test_tag_from_language!(tag_from_language_yue, "ZHH", "yue");
test_tag_from_language!(tag_from_language_yue_Hant, "ZHH", "yue-Hant");
test_tag_from_language!(tag_from_language_yue_Hans, "ZHS", "yue-Hans");
test_tag_from_language!(tag_from_language_ABC, "ABC", "abc");
test_tag_from_language!(tag_from_language_ABCD, "ABCD", "x-hbotabcd");
test_tag_from_language!(tag_from_language_asdf_asdf_wer_x_hbotabc_zxc, "ABC", "asdf-asdf-wer-x-hbotabc-zxc");
test_tag_from_language!(tag_from_language_asdf_asdf_wer_x_hbotabc, "ABC", "asdf-asdf-wer-x-hbotabc");
test_tag_from_language!(tag_from_language_asdf_asdf_wer_x_hbotabcd, "ABCD", "asdf-asdf-wer-x-hbotabcd");
test_tag_from_language!(tag_from_language_asdf_asdf_wer_x_hbot_zxc, "dflt", "asdf-asdf-wer-x-hbot-zxc");
test_tag_from_language!(tag_from_language_xy, "dflt", "xy");
test_tag_from_language!(tag_from_language_xyz, "XYZ", "xyz"); /* Unknown ISO 639-3 */
test_tag_from_language!(tag_from_language_xyz_qw, "XYZ", "xyz-qw"); /* Unknown ISO 639-3 */
/*
* Invalid input. The precise answer does not matter, as long as it
* does not crash or get into an infinite loop.
*/
test_tag_from_language!(tag_from_language__fonipa, "IPPH", "-fonipa");
/*
* Tags that contain "-fonipa" as a substring but which do not contain
* the subtag "fonipa".
*/
test_tag_from_language!(tag_from_language_en_fonipax, "ENG", "en-fonipax");
test_tag_from_language!(tag_from_language_en_x_fonipa, "ENG", "en-x-fonipa");
test_tag_from_language!(tag_from_language_en_a_fonipa, "ENG", "en-a-fonipa");
test_tag_from_language!(tag_from_language_en_a_qwe_b_fonipa, "ENG", "en-a-qwe-b-fonipa");
/* International Phonetic Alphabet */
test_tag_from_language!(tag_from_language_en_fonipa, "IPPH", "en-fonipa");
test_tag_from_language!(tag_from_language_en_fonipax_fonipa, "IPPH", "en-fonipax-fonipa");
test_tag_from_language!(tag_from_language_rm_ch_fonipa_sursilv_x_foobar, "IPPH", "rm-CH-fonipa-sursilv-x-foobar");
test_tag_from_language!(tag_from_language_IPPH, "IPPH", "und-fonipa");
test_tag_from_language!(tag_from_language_zh_fonipa, "IPPH", "zh-fonipa");
/* North American Phonetic Alphabet (Americanist Phonetic Notation) */
test_tag_from_language!(tag_from_language_en_fonnapa, "APPH", "en-fonnapa");
test_tag_from_language!(tag_from_language_chr_fonnapa, "APPH", "chr-fonnapa");
test_tag_from_language!(tag_from_language_APPH, "APPH", "und-fonnapa");
/* Khutsuri Georgian */
test_tag_from_language!(tag_from_language_ka_geok, "KGE", "ka-Geok");
test_tag_from_language!(tag_from_language_KGE, "KGE", "und-Geok");
/* Irish Traditional */
test_tag_from_language!(tag_from_language_IRT, "IRT", "ga-Latg");
/* Moldavian */
test_tag_from_language!(tag_from_language_MOL, "MOL", "ro-MD");
/* Polytonic Greek */
test_tag_from_language!(tag_from_language_PGR, "PGR", "el-polyton");
test_tag_from_language!(tag_from_language_el_CY_polyton, "PGR", "el-CY-polyton");
/* Estrangela Syriac */
test_tag_from_language!(tag_from_language_aii_Syre, "SYRE", "aii-Syre");
test_tag_from_language!(tag_from_language_de_Syre, "SYRE", "de-Syre");
test_tag_from_language!(tag_from_language_syr_Syre, "SYRE", "syr-Syre");
test_tag_from_language!(tag_from_language_und_Syre, "SYRE", "und-Syre");
/* Western Syriac */
test_tag_from_language!(tag_from_language_aii_Syrj, "SYRJ", "aii-Syrj");
test_tag_from_language!(tag_from_language_de_Syrj, "SYRJ", "de-Syrj");
test_tag_from_language!(tag_from_language_syr_Syrj, "SYRJ", "syr-Syrj");
test_tag_from_language!(tag_from_language_SYRJ, "SYRJ", "und-Syrj");
/* Eastern Syriac */
test_tag_from_language!(tag_from_language_aii_Syrn, "SYRN", "aii-Syrn");
test_tag_from_language!(tag_from_language_de_Syrn, "SYRN", "de-Syrn");
test_tag_from_language!(tag_from_language_syr_Syrn, "SYRN", "syr-Syrn");
test_tag_from_language!(tag_from_language_SYRN, "SYRN", "und-Syrn");
/* Test that x-hbot overrides the base language */
test_tag_from_language!(tag_from_language_fa_x_hbotabc_zxc, "ABC", "fa-x-hbotabc-zxc");
test_tag_from_language!(tag_from_language_fa_ir_x_hbotabc_zxc, "ABC", "fa-ir-x-hbotabc-zxc");
test_tag_from_language!(tag_from_language_zh_x_hbotabc_zxc, "ABC", "zh-x-hbotabc-zxc");
test_tag_from_language!(tag_from_language_zh_cn_x_hbotabc_zxc, "ABC", "zh-cn-x-hbotabc-zxc");
test_tag_from_language!(tag_from_language_zh_xy_x_hbotabc_zxc, "ABC", "zh-xy-x-hbotabc-zxc");
test_tag_from_language!(tag_from_language_xyz_xy_x_hbotabc_zxc, "ABC", "xyz-xy-x-hbotabc-zxc");
/* Unnormalized BCP 47 tags */
test_tag_from_language!(tag_from_language_ar_aao, "ARA", "ar-aao");
test_tag_from_language!(tag_from_language_art_lojban, "JBO", "art-lojban");
test_tag_from_language!(tag_from_language_kok_gom, "KOK", "kok-gom");
test_tag_from_language!(tag_from_language_i_lux, "LTZ", "i-lux");
test_tag_from_language!(tag_from_language_drh, "MNG", "drh");
test_tag_from_language!(tag_from_language_ar_ary1, "MOR", "ar-ary");
test_tag_from_language!(tag_from_language_ar_ary_DZ, "MOR", "ar-ary-DZ");
test_tag_from_language!(tag_from_language_no_bok, "NOR", "no-bok");
test_tag_from_language!(tag_from_language_no_nyn, "NYN", "no-nyn");
test_tag_from_language!(tag_from_language_i_hak, "ZHS", "i-hak");
test_tag_from_language!(tag_from_language_zh_guoyu, "ZHS", "zh-guoyu");
test_tag_from_language!(tag_from_language_zh_min, "ZHS", "zh-min");
test_tag_from_language!(tag_from_language_zh_min_nan, "ZHS", "zh-min-nan");
test_tag_from_language!(tag_from_language_zh_xiang, "ZHS", "zh-xiang");
/* BCP 47 tags that look similar to unrelated language system tags */
test_tag_from_language!(tag_from_language_als, "SQI", "als");
test_tag_from_language!(tag_from_language_far, "dflt", "far");
/* A UN M.49 region code, not an extended language subtag */
test_tag_from_language!(tag_from_language_ar_001, "ARA", "ar-001");
/* An invalid tag */
test_tag_from_language!(tag_from_language_invalid, "TRK", "tr@foo=bar");
macro_rules! test_tags {
($name:ident, $script:expr, $lang:expr, $scripts:expr, $langs:expr) => {
#[test]
fn $name() {
let (scripts, languages) = tags_from_script_and_language(
$script, Language::from_str($lang).ok().as_ref(),
);
let exp_scripts: Vec<hb_tag_t> = $scripts.iter().map(|v| hb_tag_t::from_bytes_lossy(*v)).collect();
let exp_langs: Vec<hb_tag_t> = $langs.iter().map(|v| hb_tag_t::from_bytes_lossy(*v)).collect();
assert_eq!(exp_scripts, scripts.as_slice());
assert_eq!(exp_langs, languages.as_slice());
}
};
}
test_tags!(tag_full_en, None, "en", &[], &[b"ENG"]);
test_tags!(tag_full_en_x_hbscdflt, None, "en-x-hbscdflt", &[b"DFLT"], &[b"ENG"]);
test_tags!(tag_full_en_latin, Some(script::LATIN), "en", &[b"latn"], &[b"ENG"]);
test_tags!(tag_full_und_fonnapa, None, "und-fonnapa", &[], &[b"APPH"]);
test_tags!(tag_full_en_fonnapa, None, "en-fonnapa", &[], &[b"APPH"]);
test_tags!(tag_full_x_hbot1234_hbsc5678, None, "x-hbot1234-hbsc5678", &[b"5678"], &[b"1234"]);
test_tags!(tag_full_x_hbsc5678_hbot1234, None, "x-hbsc5678-hbot1234", &[b"5678"], &[b"1234"]);
test_tags!(tag_full_ml, Some(script::MALAYALAM), "ml", &[b"mlm3", b"mlm2", b"mlym"], &[b"MAL", b"MLR"]);
test_tags!(tag_full_xyz, None, "xyz", &[], &[b"XYZ"]);
test_tags!(tag_full_xy, None, "xy", &[], &[]);
}

2441
vendor/rustybuzz/src/hb/tag_table.rs vendored Normal file

File diff suppressed because it is too large Load Diff

143
vendor/rustybuzz/src/hb/text_parser.rs vendored Normal file
View File

@@ -0,0 +1,143 @@
use super::hb_tag_t;
pub struct TextParser<'a> {
pos: usize,
text: &'a str,
}
impl<'a> TextParser<'a> {
#[inline]
pub fn new(text: &'a str) -> Self {
TextParser { pos: 0, text }
}
#[inline]
pub fn at_end(&self) -> bool {
self.pos >= self.text.len()
}
#[inline]
pub fn curr_byte(&self) -> Option<u8> {
if !self.at_end() {
Some(self.curr_byte_unchecked())
} else {
None
}
}
#[inline]
fn curr_byte_unchecked(&self) -> u8 {
self.text.as_bytes()[self.pos]
}
#[inline]
pub fn advance(&mut self, n: usize) {
debug_assert!(self.pos + n <= self.text.len());
self.pos += n;
}
pub fn consume_byte(&mut self, c: u8) -> Option<()> {
let curr = self.curr_byte()?;
if curr != c {
return None;
}
self.advance(1);
Some(())
}
#[inline]
pub fn skip_spaces(&mut self) {
// Unlike harfbuzz::ISSPACE, is_ascii_whitespace doesn't includes `\v`, but whatever.
while !self.at_end() && self.curr_byte_unchecked().is_ascii_whitespace() {
self.advance(1);
}
}
pub fn consume_quote(&mut self) -> Option<u8> {
let c = self.curr_byte()?;
if matches!(c, b'\'' | b'"') {
self.advance(1);
Some(c)
} else {
None
}
}
#[inline]
pub fn consume_bytes<F>(&mut self, f: F) -> &'a str
where
F: Fn(u8) -> bool,
{
let start = self.pos;
self.skip_bytes(f);
&self.text[start..self.pos]
}
pub fn skip_bytes<F>(&mut self, f: F)
where
F: Fn(u8) -> bool,
{
while !self.at_end() && f(self.curr_byte_unchecked()) {
self.advance(1);
}
}
pub fn consume_tag(&mut self) -> Option<hb_tag_t> {
let tag = self.consume_bytes(|c| c.is_ascii_alphanumeric() || c == b'_');
if tag.len() > 4 {
return None;
}
Some(hb_tag_t::from_bytes_lossy(tag.as_bytes()))
}
pub fn consume_i32(&mut self) -> Option<i32> {
let start = self.pos;
if matches!(self.curr_byte(), Some(b'-') | Some(b'+')) {
self.advance(1);
}
self.skip_bytes(|c| c.is_ascii_digit());
self.text[start..self.pos].parse::<i32>().ok()
}
pub fn consume_f32(&mut self) -> Option<f32> {
let start = self.pos;
// TODO: does number like 1-e2 required?
if matches!(self.curr_byte(), Some(b'-') | Some(b'+')) {
self.advance(1);
}
self.skip_bytes(|c| c.is_ascii_digit());
if self.consume_byte(b'.').is_some() {
self.skip_bytes(|c| c.is_ascii_digit());
}
self.text[start..self.pos].parse::<f32>().ok()
}
pub fn consume_bool(&mut self) -> Option<bool> {
self.skip_spaces();
let value = self.consume_bytes(|c| c.is_ascii_alphabetic()).as_bytes();
if value.len() == 2 {
if value[0].to_ascii_lowercase() == b'o' && value[1].to_ascii_lowercase() == b'n' {
return Some(true);
}
} else if value.len() == 3 {
if value[0].to_ascii_lowercase() == b'o'
&& value[1].to_ascii_lowercase() == b'f'
&& value[2].to_ascii_lowercase() == b'f'
{
return Some(false);
}
}
None
}
}

894
vendor/rustybuzz/src/hb/unicode.rs vendored Normal file
View File

@@ -0,0 +1,894 @@
use core::convert::TryFrom;
pub use unicode_ccc::CanonicalCombiningClass;
// TODO: prefer unic-ucd-normal::CanonicalCombiningClass
pub use unicode_properties::GeneralCategory as hb_unicode_general_category_t;
use crate::Script;
// Space estimates based on:
// https://unicode.org/charts/PDF/U2000.pdf
// https://docs.microsoft.com/en-us/typography/develop/character-design-standards/whitespace
pub mod hb_unicode_funcs_t {
pub type space_t = u8;
pub const NOT_SPACE: u8 = 0;
pub const SPACE_EM: u8 = 1;
pub const SPACE_EM_2: u8 = 2;
pub const SPACE_EM_3: u8 = 3;
pub const SPACE_EM_4: u8 = 4;
pub const SPACE_EM_5: u8 = 5;
pub const SPACE_EM_6: u8 = 6;
pub const SPACE_EM_16: u8 = 16;
pub const SPACE_4_EM_18: u8 = 17; // 4/18th of an EM!
pub const SPACE: u8 = 18;
pub const SPACE_FIGURE: u8 = 19;
pub const SPACE_PUNCTUATION: u8 = 20;
pub const SPACE_NARROW: u8 = 21;
}
#[allow(dead_code)]
pub mod modified_combining_class {
// Hebrew
//
// We permute the "fixed-position" classes 10-26 into the order
// described in the SBL Hebrew manual:
//
// https://www.sbl-site.org/Fonts/SBLHebrewUserManual1.5x.pdf
//
// (as recommended by:
// https://forum.fontlab.com/archive-old-microsoft-volt-group/vista-and-diacritic-ordering/msg22823/)
//
// More details here:
// https://bugzilla.mozilla.org/show_bug.cgi?id=662055
pub const CCC10: u8 = 22; // sheva
pub const CCC11: u8 = 15; // hataf segol
pub const CCC12: u8 = 16; // hataf patah
pub const CCC13: u8 = 17; // hataf qamats
pub const CCC14: u8 = 23; // hiriq
pub const CCC15: u8 = 18; // tsere
pub const CCC16: u8 = 19; // segol
pub const CCC17: u8 = 20; // patah
pub const CCC18: u8 = 21; // qamats & qamats qatan
pub const CCC19: u8 = 14; // holam & holam haser for vav
pub const CCC20: u8 = 24; // qubuts
pub const CCC21: u8 = 12; // dagesh
pub const CCC22: u8 = 25; // meteg
pub const CCC23: u8 = 13; // rafe
pub const CCC24: u8 = 10; // shin dot
pub const CCC25: u8 = 11; // sin dot
pub const CCC26: u8 = 26; // point varika
// Arabic
//
// Modify to move Shadda (ccc=33) before other marks. See:
// https://unicode.org/faq/normalization.html#8
// https://unicode.org/faq/normalization.html#9
pub const CCC27: u8 = 28; // fathatan
pub const CCC28: u8 = 29; // dammatan
pub const CCC29: u8 = 30; // kasratan
pub const CCC30: u8 = 31; // fatha
pub const CCC31: u8 = 32; // damma
pub const CCC32: u8 = 33; // kasra
pub const CCC33: u8 = 27; // shadda
pub const CCC34: u8 = 34; // sukun
pub const CCC35: u8 = 35; // superscript alef
// Syriac
pub const CCC36: u8 = 36; // superscript alaph
// Telugu
//
// Modify Telugu length marks (ccc=84, ccc=91).
// These are the only matras in the main Indic scripts range that have
// a non-zero ccc. That makes them reorder with the Halant that is
// ccc=9. Just zero them, we don't need them in our Indic shaper.
pub const CCC84: u8 = 0; // length mark
pub const CCC91: u8 = 0; // ai length mark
// Thai
//
// Modify U+0E38 and U+0E39 (ccc=103) to be reordered before U+0E3A (ccc=9).
// Assign 3, which is unassigned otherwise.
// Uniscribe does this reordering too.
pub const CCC103: u8 = 3; // sara u / sara uu
pub const CCC107: u8 = 107; // mai *
// Lao
pub const CCC118: u8 = 118; // sign u / sign uu
pub const CCC122: u8 = 122; // mai *
// Tibetan
//
// In case of multiple vowel-signs, use u first (but after achung)
// this allows Dzongkha multi-vowel shortcuts to render correctly
pub const CCC129: u8 = 129; // sign aa
pub const CCC130: u8 = 132; // sign i
pub const CCC132: u8 = 131; // sign u
}
#[rustfmt::skip]
const MODIFIED_COMBINING_CLASS: &[u8; 256] = &[
CanonicalCombiningClass::NotReordered as u8,
CanonicalCombiningClass::Overlay as u8,
2, 3, 4, 5, 6,
CanonicalCombiningClass::Nukta as u8,
CanonicalCombiningClass::KanaVoicing as u8,
CanonicalCombiningClass::Virama as u8,
// Hebrew
modified_combining_class::CCC10,
modified_combining_class::CCC11,
modified_combining_class::CCC12,
modified_combining_class::CCC13,
modified_combining_class::CCC14,
modified_combining_class::CCC15,
modified_combining_class::CCC16,
modified_combining_class::CCC17,
modified_combining_class::CCC18,
modified_combining_class::CCC19,
modified_combining_class::CCC20,
modified_combining_class::CCC21,
modified_combining_class::CCC22,
modified_combining_class::CCC23,
modified_combining_class::CCC24,
modified_combining_class::CCC25,
modified_combining_class::CCC26,
// Arabic
modified_combining_class::CCC27,
modified_combining_class::CCC28,
modified_combining_class::CCC29,
modified_combining_class::CCC30,
modified_combining_class::CCC31,
modified_combining_class::CCC32,
modified_combining_class::CCC33,
modified_combining_class::CCC34,
modified_combining_class::CCC35,
// Syriac
modified_combining_class::CCC36,
37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83,
// Telugu
modified_combining_class::CCC84,
85, 86, 87, 88, 89, 90,
modified_combining_class::CCC91,
92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102,
// Thai
modified_combining_class::CCC103,
104, 105, 106,
modified_combining_class::CCC107,
108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
// Lao
modified_combining_class::CCC118,
119, 120, 121,
modified_combining_class::CCC122,
123, 124, 125, 126, 127, 128,
// Tibetan
modified_combining_class::CCC129,
modified_combining_class::CCC130,
131,
modified_combining_class::CCC132,
133, 134, 135, 136, 137, 138, 139,
140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
160, 161, 162, 163, 164, 165, 166, 167, 168, 169,
170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
180, 181, 182, 183, 184, 185, 186, 187, 188, 189,
190, 191, 192, 193, 194, 195, 196, 197, 198, 199,
CanonicalCombiningClass::AttachedBelowLeft as u8,
201,
CanonicalCombiningClass::AttachedBelow as u8,
203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213,
CanonicalCombiningClass::AttachedAbove as u8,
215,
CanonicalCombiningClass::AttachedAboveRight as u8,
217,
CanonicalCombiningClass::BelowLeft as u8,
219,
CanonicalCombiningClass::Below as u8,
221,
CanonicalCombiningClass::BelowRight as u8,
223,
CanonicalCombiningClass::Left as u8,
225,
CanonicalCombiningClass::Right as u8,
227,
CanonicalCombiningClass::AboveLeft as u8,
229,
CanonicalCombiningClass::Above as u8,
231,
CanonicalCombiningClass::AboveRight as u8,
CanonicalCombiningClass::DoubleBelow as u8,
CanonicalCombiningClass::DoubleAbove as u8,
235, 236, 237, 238, 239,
CanonicalCombiningClass::IotaSubscript as u8,
241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
255, // RB_UNICODE_COMBINING_CLASS_INVALID
];
pub trait GeneralCategoryExt {
fn to_rb(&self) -> u32;
fn from_rb(gc: u32) -> Self;
fn is_mark(&self) -> bool;
fn is_letter(&self) -> bool;
}
#[rustfmt::skip]
impl GeneralCategoryExt for hb_unicode_general_category_t {
fn to_rb(&self) -> u32 {
match *self {
hb_unicode_general_category_t::ClosePunctuation => hb_gc::RB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION,
hb_unicode_general_category_t::ConnectorPunctuation => hb_gc::RB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION,
hb_unicode_general_category_t::Control => hb_gc::RB_UNICODE_GENERAL_CATEGORY_CONTROL,
hb_unicode_general_category_t::CurrencySymbol => hb_gc::RB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL,
hb_unicode_general_category_t::DashPunctuation => hb_gc::RB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION,
hb_unicode_general_category_t::DecimalNumber => hb_gc::RB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER,
hb_unicode_general_category_t::EnclosingMark => hb_gc::RB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK,
hb_unicode_general_category_t::FinalPunctuation => hb_gc::RB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION,
hb_unicode_general_category_t::Format => hb_gc::RB_UNICODE_GENERAL_CATEGORY_FORMAT,
hb_unicode_general_category_t::InitialPunctuation => hb_gc::RB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION,
hb_unicode_general_category_t::LetterNumber => hb_gc::RB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER,
hb_unicode_general_category_t::LineSeparator => hb_gc::RB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR,
hb_unicode_general_category_t::LowercaseLetter => hb_gc::RB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER,
hb_unicode_general_category_t::MathSymbol => hb_gc::RB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL,
hb_unicode_general_category_t::ModifierLetter => hb_gc::RB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER,
hb_unicode_general_category_t::ModifierSymbol => hb_gc::RB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL,
hb_unicode_general_category_t::NonspacingMark => hb_gc::RB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK,
hb_unicode_general_category_t::OpenPunctuation => hb_gc::RB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION,
hb_unicode_general_category_t::OtherLetter => hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER,
hb_unicode_general_category_t::OtherNumber => hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER,
hb_unicode_general_category_t::OtherPunctuation => hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION,
hb_unicode_general_category_t::OtherSymbol => hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL,
hb_unicode_general_category_t::ParagraphSeparator => hb_gc::RB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR,
hb_unicode_general_category_t::PrivateUse => hb_gc::RB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE,
hb_unicode_general_category_t::SpaceSeparator => hb_gc::RB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR,
hb_unicode_general_category_t::SpacingMark => hb_gc::RB_UNICODE_GENERAL_CATEGORY_SPACING_MARK,
hb_unicode_general_category_t::Surrogate => hb_gc::RB_UNICODE_GENERAL_CATEGORY_SURROGATE,
hb_unicode_general_category_t::TitlecaseLetter => hb_gc::RB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER,
hb_unicode_general_category_t::Unassigned => hb_gc::RB_UNICODE_GENERAL_CATEGORY_UNASSIGNED,
hb_unicode_general_category_t::UppercaseLetter => hb_gc::RB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER,
}
}
fn from_rb(gc: u32) -> Self {
match gc {
hb_gc::RB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION => hb_unicode_general_category_t::ClosePunctuation,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION => hb_unicode_general_category_t::ConnectorPunctuation,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_CONTROL => hb_unicode_general_category_t::Control,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL => hb_unicode_general_category_t::CurrencySymbol,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION => hb_unicode_general_category_t::DashPunctuation,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER => hb_unicode_general_category_t::DecimalNumber,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK => hb_unicode_general_category_t::EnclosingMark,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION => hb_unicode_general_category_t::FinalPunctuation,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_FORMAT => hb_unicode_general_category_t::Format,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION => hb_unicode_general_category_t::InitialPunctuation,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER => hb_unicode_general_category_t::LetterNumber,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR => hb_unicode_general_category_t::LineSeparator,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER => hb_unicode_general_category_t::LowercaseLetter,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL => hb_unicode_general_category_t::MathSymbol,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER => hb_unicode_general_category_t::ModifierLetter,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL => hb_unicode_general_category_t::ModifierSymbol,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK => hb_unicode_general_category_t::NonspacingMark,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION => hb_unicode_general_category_t::OpenPunctuation,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER => hb_unicode_general_category_t::OtherLetter,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER => hb_unicode_general_category_t::OtherNumber,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION => hb_unicode_general_category_t::OtherPunctuation,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL => hb_unicode_general_category_t::OtherSymbol,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR => hb_unicode_general_category_t::ParagraphSeparator,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE => hb_unicode_general_category_t::PrivateUse,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR => hb_unicode_general_category_t::SpaceSeparator,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_SPACING_MARK => hb_unicode_general_category_t::SpacingMark,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_SURROGATE => hb_unicode_general_category_t::Surrogate,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER => hb_unicode_general_category_t::TitlecaseLetter,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_UNASSIGNED => hb_unicode_general_category_t::Unassigned,
hb_gc::RB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER => hb_unicode_general_category_t::UppercaseLetter,
_ => unreachable!(),
}
}
fn is_mark(&self) -> bool {
match *self {
hb_unicode_general_category_t::SpacingMark |
hb_unicode_general_category_t::EnclosingMark |
hb_unicode_general_category_t::NonspacingMark => true,
_ => false,
}
}
fn is_letter(&self) -> bool {
match *self {
hb_unicode_general_category_t::LowercaseLetter |
hb_unicode_general_category_t::ModifierLetter |
hb_unicode_general_category_t::OtherLetter |
hb_unicode_general_category_t::TitlecaseLetter |
hb_unicode_general_category_t::UppercaseLetter => true,
_ => false,
}
}
}
pub trait CharExt {
fn script(self) -> Script;
fn general_category(self) -> hb_unicode_general_category_t;
fn combining_class(self) -> CanonicalCombiningClass;
fn space_fallback(self) -> hb_unicode_funcs_t::space_t;
fn modified_combining_class(self) -> u8;
fn mirrored(self) -> Option<char>;
fn is_emoji_extended_pictographic(self) -> bool;
fn is_default_ignorable(self) -> bool;
fn is_variation_selector(self) -> bool;
fn vertical(self) -> Option<char>;
}
impl CharExt for char {
fn script(self) -> Script {
use crate::script;
use unicode_script as us;
match unicode_script::UnicodeScript::script(&self) {
us::Script::Common => script::COMMON,
us::Script::Inherited => script::INHERITED,
us::Script::Adlam => script::ADLAM,
us::Script::Ahom => script::AHOM,
us::Script::Anatolian_Hieroglyphs => script::ANATOLIAN_HIEROGLYPHS,
us::Script::Arabic => script::ARABIC,
us::Script::Armenian => script::ARMENIAN,
us::Script::Avestan => script::AVESTAN,
us::Script::Balinese => script::BALINESE,
us::Script::Bamum => script::BAMUM,
us::Script::Bassa_Vah => script::BASSA_VAH,
us::Script::Batak => script::BATAK,
us::Script::Bengali => script::BENGALI,
us::Script::Bhaiksuki => script::BHAIKSUKI,
us::Script::Bopomofo => script::BOPOMOFO,
us::Script::Brahmi => script::BRAHMI,
us::Script::Braille => script::BRAILLE,
us::Script::Buginese => script::BUGINESE,
us::Script::Buhid => script::BUHID,
us::Script::Canadian_Aboriginal => script::CANADIAN_SYLLABICS,
us::Script::Carian => script::CARIAN,
us::Script::Caucasian_Albanian => script::CAUCASIAN_ALBANIAN,
us::Script::Chakma => script::CHAKMA,
us::Script::Cham => script::CHAM,
us::Script::Cherokee => script::CHEROKEE,
us::Script::Chorasmian => script::CHORASMIAN,
us::Script::Coptic => script::COPTIC,
us::Script::Cuneiform => script::CUNEIFORM,
us::Script::Cypriot => script::CYPRIOT,
us::Script::Cyrillic => script::CYRILLIC,
us::Script::Deseret => script::DESERET,
us::Script::Devanagari => script::DEVANAGARI,
us::Script::Dives_Akuru => script::DIVES_AKURU,
us::Script::Dogra => script::DOGRA,
us::Script::Duployan => script::DUPLOYAN,
us::Script::Egyptian_Hieroglyphs => script::EGYPTIAN_HIEROGLYPHS,
us::Script::Elbasan => script::ELBASAN,
us::Script::Elymaic => script::ELYMAIC,
us::Script::Ethiopic => script::ETHIOPIC,
us::Script::Georgian => script::GEORGIAN,
us::Script::Glagolitic => script::GLAGOLITIC,
us::Script::Gothic => script::GOTHIC,
us::Script::Grantha => script::GRANTHA,
us::Script::Greek => script::GREEK,
us::Script::Gujarati => script::GUJARATI,
us::Script::Gunjala_Gondi => script::GUNJALA_GONDI,
us::Script::Gurmukhi => script::GURMUKHI,
us::Script::Han => script::HAN,
us::Script::Hangul => script::HANGUL,
us::Script::Hanifi_Rohingya => script::HANIFI_ROHINGYA,
us::Script::Hanunoo => script::HANUNOO,
us::Script::Hatran => script::HATRAN,
us::Script::Hebrew => script::HEBREW,
us::Script::Hiragana => script::HIRAGANA,
us::Script::Imperial_Aramaic => script::IMPERIAL_ARAMAIC,
us::Script::Inscriptional_Pahlavi => script::INSCRIPTIONAL_PAHLAVI,
us::Script::Inscriptional_Parthian => script::INSCRIPTIONAL_PARTHIAN,
us::Script::Javanese => script::JAVANESE,
us::Script::Kaithi => script::KAITHI,
us::Script::Kannada => script::KANNADA,
us::Script::Katakana => script::KATAKANA,
us::Script::Kayah_Li => script::KAYAH_LI,
us::Script::Kharoshthi => script::KHAROSHTHI,
us::Script::Khitan_Small_Script => script::KHITAN_SMALL_SCRIPT,
us::Script::Khmer => script::KHMER,
us::Script::Khojki => script::KHOJKI,
us::Script::Khudawadi => script::KHUDAWADI,
us::Script::Lao => script::LAO,
us::Script::Latin => script::LATIN,
us::Script::Lepcha => script::LEPCHA,
us::Script::Limbu => script::LIMBU,
us::Script::Linear_A => script::LINEAR_A,
us::Script::Linear_B => script::LINEAR_B,
us::Script::Lisu => script::LISU,
us::Script::Lycian => script::LYCIAN,
us::Script::Lydian => script::LYDIAN,
us::Script::Mahajani => script::MAHAJANI,
us::Script::Makasar => script::MAKASAR,
us::Script::Malayalam => script::MALAYALAM,
us::Script::Mandaic => script::MANDAIC,
us::Script::Manichaean => script::MANICHAEAN,
us::Script::Marchen => script::MARCHEN,
us::Script::Masaram_Gondi => script::MASARAM_GONDI,
us::Script::Medefaidrin => script::MEDEFAIDRIN,
us::Script::Meetei_Mayek => script::MEETEI_MAYEK,
us::Script::Mende_Kikakui => script::MENDE_KIKAKUI,
us::Script::Meroitic_Cursive => script::MEROITIC_CURSIVE,
us::Script::Meroitic_Hieroglyphs => script::MEROITIC_HIEROGLYPHS,
us::Script::Miao => script::MIAO,
us::Script::Modi => script::MODI,
us::Script::Mongolian => script::MONGOLIAN,
us::Script::Mro => script::MRO,
us::Script::Multani => script::MULTANI,
us::Script::Myanmar => script::MYANMAR,
us::Script::Nabataean => script::NABATAEAN,
us::Script::Nandinagari => script::NANDINAGARI,
us::Script::New_Tai_Lue => script::NEW_TAI_LUE,
us::Script::Newa => script::NEWA,
us::Script::Nko => script::NKO,
us::Script::Nushu => script::NUSHU,
us::Script::Nyiakeng_Puachue_Hmong => script::NYIAKENG_PUACHUE_HMONG,
us::Script::Ogham => script::OGHAM,
us::Script::Ol_Chiki => script::OL_CHIKI,
us::Script::Old_Hungarian => script::OLD_HUNGARIAN,
us::Script::Old_Italic => script::OLD_ITALIC,
us::Script::Old_North_Arabian => script::OLD_NORTH_ARABIAN,
us::Script::Old_Permic => script::OLD_PERMIC,
us::Script::Old_Persian => script::OLD_PERSIAN,
us::Script::Old_Sogdian => script::OLD_SOGDIAN,
us::Script::Old_South_Arabian => script::OLD_SOUTH_ARABIAN,
us::Script::Old_Turkic => script::OLD_TURKIC,
us::Script::Oriya => script::ORIYA,
us::Script::Osage => script::OSAGE,
us::Script::Osmanya => script::OSMANYA,
us::Script::Pahawh_Hmong => script::PAHAWH_HMONG,
us::Script::Palmyrene => script::PALMYRENE,
us::Script::Pau_Cin_Hau => script::PAU_CIN_HAU,
us::Script::Phags_Pa => script::PHAGS_PA,
us::Script::Phoenician => script::PHOENICIAN,
us::Script::Psalter_Pahlavi => script::PSALTER_PAHLAVI,
us::Script::Rejang => script::REJANG,
us::Script::Runic => script::RUNIC,
us::Script::Samaritan => script::SAMARITAN,
us::Script::Saurashtra => script::SAURASHTRA,
us::Script::Sharada => script::SHARADA,
us::Script::Shavian => script::SHAVIAN,
us::Script::Siddham => script::SIDDHAM,
us::Script::SignWriting => script::SIGNWRITING,
us::Script::Sinhala => script::SINHALA,
us::Script::Sogdian => script::SOGDIAN,
us::Script::Sora_Sompeng => script::SORA_SOMPENG,
us::Script::Soyombo => script::SOYOMBO,
us::Script::Sundanese => script::SUNDANESE,
us::Script::Syloti_Nagri => script::SYLOTI_NAGRI,
us::Script::Syriac => script::SYRIAC,
us::Script::Tagalog => script::TAGALOG,
us::Script::Tagbanwa => script::TAGBANWA,
us::Script::Tai_Le => script::TAI_LE,
us::Script::Tai_Tham => script::TAI_THAM,
us::Script::Tai_Viet => script::TAI_VIET,
us::Script::Takri => script::TAKRI,
us::Script::Tamil => script::TAMIL,
us::Script::Tangut => script::TANGUT,
us::Script::Telugu => script::TELUGU,
us::Script::Thaana => script::THAANA,
us::Script::Thai => script::THAI,
us::Script::Tibetan => script::TIBETAN,
us::Script::Tifinagh => script::TIFINAGH,
us::Script::Tirhuta => script::TIRHUTA,
us::Script::Ugaritic => script::UGARITIC,
us::Script::Vai => script::VAI,
us::Script::Wancho => script::WANCHO,
us::Script::Warang_Citi => script::WARANG_CITI,
us::Script::Yezidi => script::YEZIDI,
us::Script::Yi => script::YI,
us::Script::Zanabazar_Square => script::ZANABAZAR_SQUARE,
_ => script::UNKNOWN,
}
}
fn general_category(self) -> hb_unicode_general_category_t {
unicode_properties::general_category::UnicodeGeneralCategory::general_category(self)
}
fn combining_class(self) -> CanonicalCombiningClass {
unicode_ccc::get_canonical_combining_class(self)
}
fn space_fallback(self) -> hb_unicode_funcs_t::space_t {
use hb_unicode_funcs_t::*;
// All GC=Zs chars that can use a fallback.
match self {
'\u{0020}' => SPACE, // SPACE
'\u{00A0}' => SPACE, // NO-BREAK SPACE
'\u{2000}' => SPACE_EM_2, // EN QUAD
'\u{2001}' => SPACE_EM, // EM QUAD
'\u{2002}' => SPACE_EM_2, // EN SPACE
'\u{2003}' => SPACE_EM, // EM SPACE
'\u{2004}' => SPACE_EM_3, // THREE-PER-EM SPACE
'\u{2005}' => SPACE_EM_4, // FOUR-PER-EM SPACE
'\u{2006}' => SPACE_EM_6, // SIX-PER-EM SPACE
'\u{2007}' => SPACE_FIGURE, // FIGURE SPACE
'\u{2008}' => SPACE_PUNCTUATION, // PUNCTUATION SPACE
'\u{2009}' => SPACE_EM_5, // THIN SPACE
'\u{200A}' => SPACE_EM_16, // HAIR SPACE
'\u{202F}' => SPACE_NARROW, // NARROW NO-BREAK SPACE
'\u{205F}' => SPACE_4_EM_18, // MEDIUM MATHEMATICAL SPACE
'\u{3000}' => SPACE_EM, // IDEOGRAPHIC SPACE
_ => NOT_SPACE, // OGHAM SPACE MARK
}
}
fn modified_combining_class(self) -> u8 {
let mut u = self;
// XXX This hack belongs to the Myanmar shaper.
if u == '\u{1037}' {
u = '\u{103A}';
}
// XXX This hack belongs to the USE shaper (for Tai Tham):
// Reorder SAKOT to ensure it comes after any tone marks.
if u == '\u{1A60}' {
return 254;
}
// XXX This hack belongs to the Tibetan shaper:
// Reorder PADMA to ensure it comes after any vowel marks.
if u == '\u{0FC6}' {
return 254;
}
// Reorder TSA -PHRU to reorder before U+0F74
if u == '\u{0F39}' {
return 127;
}
let k = unicode_ccc::get_canonical_combining_class(u);
MODIFIED_COMBINING_CLASS[k as usize]
}
fn mirrored(self) -> Option<char> {
unicode_bidi_mirroring::get_mirrored(self)
}
fn is_emoji_extended_pictographic(self) -> bool {
// Generated by scripts/gen-unicode-is-emoji-ext-pict.py
match self as u32 {
0x00A9 => true,
0x00AE => true,
0x203C => true,
0x2049 => true,
0x2122 => true,
0x2139 => true,
0x2194..=0x2199 => true,
0x21A9..=0x21AA => true,
0x231A..=0x231B => true,
0x2328 => true,
0x2388 => true,
0x23CF => true,
0x23E9..=0x23F3 => true,
0x23F8..=0x23FA => true,
0x24C2 => true,
0x25AA..=0x25AB => true,
0x25B6 => true,
0x25C0 => true,
0x25FB..=0x25FE => true,
0x2600..=0x2605 => true,
0x2607..=0x2612 => true,
0x2614..=0x2685 => true,
0x2690..=0x2705 => true,
0x2708..=0x2712 => true,
0x2714 => true,
0x2716 => true,
0x271D => true,
0x2721 => true,
0x2728 => true,
0x2733..=0x2734 => true,
0x2744 => true,
0x2747 => true,
0x274C => true,
0x274E => true,
0x2753..=0x2755 => true,
0x2757 => true,
0x2763..=0x2767 => true,
0x2795..=0x2797 => true,
0x27A1 => true,
0x27B0 => true,
0x27BF => true,
0x2934..=0x2935 => true,
0x2B05..=0x2B07 => true,
0x2B1B..=0x2B1C => true,
0x2B50 => true,
0x2B55 => true,
0x3030 => true,
0x303D => true,
0x3297 => true,
0x3299 => true,
0x1F000..=0x1F0FF => true,
0x1F10D..=0x1F10F => true,
0x1F12F => true,
0x1F16C..=0x1F171 => true,
0x1F17E..=0x1F17F => true,
0x1F18E => true,
0x1F191..=0x1F19A => true,
0x1F1AD..=0x1F1E5 => true,
0x1F201..=0x1F20F => true,
0x1F21A => true,
0x1F22F => true,
0x1F232..=0x1F23A => true,
0x1F23C..=0x1F23F => true,
0x1F249..=0x1F3FA => true,
0x1F400..=0x1F53D => true,
0x1F546..=0x1F64F => true,
0x1F680..=0x1F6FF => true,
0x1F774..=0x1F77F => true,
0x1F7D5..=0x1F7FF => true,
0x1F80C..=0x1F80F => true,
0x1F848..=0x1F84F => true,
0x1F85A..=0x1F85F => true,
0x1F888..=0x1F88F => true,
0x1F8AE..=0x1F8FF => true,
0x1F90C..=0x1F93A => true,
0x1F93C..=0x1F945 => true,
0x1F947..=0x1FFFD => true,
_ => false,
}
}
/// Default_Ignorable codepoints:
///
/// Note: While U+115F, U+1160, U+3164 and U+FFA0 are Default_Ignorable,
/// we do NOT want to hide them, as the way Uniscribe has implemented them
/// is with regular spacing glyphs, and that's the way fonts are made to work.
/// As such, we make exceptions for those four.
/// Also ignoring U+1BCA0..1BCA3. https://github.com/harfbuzz/harfbuzz/issues/503
///
/// Unicode 14.0:
/// $ grep '; Default_Ignorable_Code_Point ' DerivedCoreProperties.txt | sed 's/;.*#/#/'
/// 00AD # Cf SOFT HYPHEN
/// 034F # Mn COMBINING GRAPHEME JOINER
/// 061C # Cf ARABIC LETTER MARK
/// 115F..1160 # Lo [2] HANGUL CHOSEONG FILLER..HANGUL JUNGSEONG FILLER
/// 17B4..17B5 # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
/// 180B..180D # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
/// 180E # Cf MONGOLIAN VOWEL SEPARATOR
/// 180F # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR
/// 200B..200F # Cf [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK
/// 202A..202E # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
/// 2060..2064 # Cf [5] WORD JOINER..INVISIBLE PLUS
/// 2065 # Cn <reserved-2065>
/// 2066..206F # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES
/// 3164 # Lo HANGUL FILLER
/// FE00..FE0F # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
/// FEFF # Cf ZERO WIDTH NO-BREAK SPACE
/// FFA0 # Lo HALFWIDTH HANGUL FILLER
/// FFF0..FFF8 # Cn [9] <reserved-FFF0>..<reserved-FFF8>
/// 1BCA0..1BCA3 # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
/// 1D173..1D17A # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
/// E0000 # Cn <reserved-E0000>
/// E0001 # Cf LANGUAGE TAG
/// E0002..E001F # Cn [30] <reserved-E0002>..<reserved-E001F>
/// E0020..E007F # Cf [96] TAG SPACE..CANCEL TAG
/// E0080..E00FF # Cn [128] <reserved-E0080>..<reserved-E00FF>
/// E0100..E01EF # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
/// E01F0..E0FFF # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
fn is_default_ignorable(self) -> bool {
let ch = u32::from(self);
let plane = ch >> 16;
if plane == 0 {
// BMP
let page = ch >> 8;
match page {
0x00 => ch == 0x00AD,
0x03 => ch == 0x034F,
0x06 => ch == 0x061C,
0x17 => (0x17B4..=0x17B5).contains(&ch),
0x18 => (0x180B..=0x180E).contains(&ch),
0x20 => {
(0x200B..=0x200F).contains(&ch)
|| (0x202A..=0x202E).contains(&ch)
|| (0x2060..=0x206F).contains(&ch)
}
0xFE => (0xFE00..=0xFE0F).contains(&ch) || ch == 0xFEFF,
0xFF => (0xFFF0..=0xFFF8).contains(&ch),
_ => false,
}
} else {
// Other planes
match plane {
0x01 => (0x1D173..=0x1D17A).contains(&ch),
0x0E => (0xE0000..=0xE0FFF).contains(&ch),
_ => false,
}
}
}
fn is_variation_selector(self) -> bool {
// U+180B..180D, U+180F MONGOLIAN FREE VARIATION SELECTORs are handled in the
//Arabic shaper. No need to match them here.
let ch = u32::from(self);
(0x0FE00..=0x0FE0F).contains(&ch) || // VARIATION SELECTOR - 1..16
(0xE0100..=0xE01EF).contains(&ch) // VARIATION SELECTOR - 17..256
}
fn vertical(self) -> Option<char> {
Some(match u32::from(self) >> 8 {
0x20 => match self {
'\u{2013}' => '\u{fe32}', // EN DASH
'\u{2014}' => '\u{fe31}', // EM DASH
'\u{2025}' => '\u{fe30}', // TWO DOT LEADER
'\u{2026}' => '\u{fe19}', // HORIZONTAL ELLIPSIS
_ => return None,
},
0x30 => match self {
'\u{3001}' => '\u{fe11}', // IDEOGRAPHIC COMMA
'\u{3002}' => '\u{fe12}', // IDEOGRAPHIC FULL STOP
'\u{3008}' => '\u{fe3f}', // LEFT ANGLE BRACKET
'\u{3009}' => '\u{fe40}', // RIGHT ANGLE BRACKET
'\u{300a}' => '\u{fe3d}', // LEFT DOUBLE ANGLE BRACKET
'\u{300b}' => '\u{fe3e}', // RIGHT DOUBLE ANGLE BRACKET
'\u{300c}' => '\u{fe41}', // LEFT CORNER BRACKET
'\u{300d}' => '\u{fe42}', // RIGHT CORNER BRACKET
'\u{300e}' => '\u{fe43}', // LEFT WHITE CORNER BRACKET
'\u{300f}' => '\u{fe44}', // RIGHT WHITE CORNER BRACKET
'\u{3010}' => '\u{fe3b}', // LEFT BLACK LENTICULAR BRACKET
'\u{3011}' => '\u{fe3c}', // RIGHT BLACK LENTICULAR BRACKET
'\u{3014}' => '\u{fe39}', // LEFT TORTOISE SHELL BRACKET
'\u{3015}' => '\u{fe3a}', // RIGHT TORTOISE SHELL BRACKET
'\u{3016}' => '\u{fe17}', // LEFT WHITE LENTICULAR BRACKET
'\u{3017}' => '\u{fe18}', // RIGHT WHITE LENTICULAR BRACKET
_ => return None,
},
0xfe => match self {
'\u{fe4f}' => '\u{fe34}', // WAVY LOW LINE
_ => return None,
},
0xff => match self {
'\u{ff01}' => '\u{fe15}', // FULLWIDTH EXCLAMATION MARK
'\u{ff08}' => '\u{fe35}', // FULLWIDTH LEFT PARENTHESIS
'\u{ff09}' => '\u{fe36}', // FULLWIDTH RIGHT PARENTHESIS
'\u{ff0c}' => '\u{fe10}', // FULLWIDTH COMMA
'\u{ff1a}' => '\u{fe13}', // FULLWIDTH COLON
'\u{ff1b}' => '\u{fe14}', // FULLWIDTH SEMICOLON
'\u{ff1f}' => '\u{fe16}', // FULLWIDTH QUESTION MARK
'\u{ff3b}' => '\u{fe47}', // FULLWIDTH LEFT SQUARE BRACKET
'\u{ff3d}' => '\u{fe48}', // FULLWIDTH RIGHT SQUARE BRACKET
'\u{ff3f}' => '\u{fe33}', // FULLWIDTH LOW LINE
'\u{ff5b}' => '\u{fe37}', // FULLWIDTH LEFT CURLY BRACKET
'\u{ff5d}' => '\u{fe38}', // FULLWIDTH RIGHT CURLY BRACKET
_ => return None,
},
_ => return None,
})
}
}
const S_BASE: u32 = 0xAC00;
const L_BASE: u32 = 0x1100;
const V_BASE: u32 = 0x1161;
const T_BASE: u32 = 0x11A7;
const L_COUNT: u32 = 19;
const V_COUNT: u32 = 21;
const T_COUNT: u32 = 28;
const N_COUNT: u32 = V_COUNT * T_COUNT;
const S_COUNT: u32 = L_COUNT * N_COUNT;
pub fn compose(a: char, b: char) -> Option<char> {
if let Some(ab) = compose_hangul(a, b) {
return Some(ab);
}
let needle = (a as u64) << 32 | (b as u64);
super::unicode_norm::COMPOSITION_TABLE
.binary_search_by(|item| item.0.cmp(&needle))
.map(|idx| super::unicode_norm::COMPOSITION_TABLE[idx].1)
.ok()
}
fn compose_hangul(a: char, b: char) -> Option<char> {
let l = u32::from(a);
let v = u32::from(b);
if L_BASE <= l && l < (L_BASE + L_COUNT) && V_BASE <= v && v < (V_BASE + V_COUNT) {
let r = S_BASE + (l - L_BASE) * N_COUNT + (v - V_BASE) * T_COUNT;
Some(char::try_from(r).unwrap())
} else if S_BASE <= l
&& l <= (S_BASE + S_COUNT - T_COUNT)
&& T_BASE <= v
&& v < (T_BASE + T_COUNT)
&& (l - S_BASE) % T_COUNT == 0
{
let r = l + (v - T_BASE);
Some(char::try_from(r).unwrap())
} else {
None
}
}
pub fn decompose(ab: char) -> Option<(char, char)> {
if let Some(ab) = decompose_hangul(ab) {
return Some(ab);
}
super::unicode_norm::DECOMPOSITION_TABLE
.binary_search_by(|item| item.0.cmp(&ab))
.map(|idx| {
let chars = &super::unicode_norm::DECOMPOSITION_TABLE[idx];
(chars.1, chars.2.unwrap_or('\0'))
})
.ok()
}
pub fn decompose_hangul(ab: char) -> Option<(char, char)> {
let si = u32::from(ab).wrapping_sub(S_BASE);
if si >= S_COUNT {
return None;
}
let (a, b) = if si % T_COUNT != 0 {
// LV,T
(S_BASE + (si / T_COUNT) * T_COUNT, T_BASE + (si % T_COUNT))
} else {
// L,V
(L_BASE + (si / N_COUNT), V_BASE + (si % N_COUNT) / T_COUNT)
};
Some((char::try_from(a).unwrap(), char::try_from(b).unwrap()))
}
#[cfg(test)]
mod tests {
#[test]
fn check_unicode_version() {
assert_eq!(unicode_bidi_mirroring::UNICODE_VERSION, (14, 0, 0));
assert_eq!(unicode_ccc::UNICODE_VERSION, (14, 0, 0));
assert_eq!(unicode_properties::UNICODE_VERSION, (15, 0, 0));
assert_eq!(unicode_script::UNICODE_VERSION, (15, 1, 0));
assert_eq!(crate::hb::unicode_norm::UNICODE_VERSION, (14, 0, 0));
}
}
// TODO: remove
pub mod hb_gc {
pub const RB_UNICODE_GENERAL_CATEGORY_CONTROL: u32 = 0;
pub const RB_UNICODE_GENERAL_CATEGORY_FORMAT: u32 = 1;
pub const RB_UNICODE_GENERAL_CATEGORY_UNASSIGNED: u32 = 2;
pub const RB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE: u32 = 3;
pub const RB_UNICODE_GENERAL_CATEGORY_SURROGATE: u32 = 4;
pub const RB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER: u32 = 5;
pub const RB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER: u32 = 6;
pub const RB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER: u32 = 7;
pub const RB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER: u32 = 8;
pub const RB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER: u32 = 9;
pub const RB_UNICODE_GENERAL_CATEGORY_SPACING_MARK: u32 = 10;
pub const RB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK: u32 = 11;
pub const RB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK: u32 = 12;
pub const RB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER: u32 = 13;
pub const RB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER: u32 = 14;
pub const RB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER: u32 = 15;
pub const RB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: u32 = 16;
pub const RB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION: u32 = 17;
pub const RB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: u32 = 18;
pub const RB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: u32 = 19;
pub const RB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: u32 = 20;
pub const RB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: u32 = 21;
pub const RB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION: u32 = 22;
pub const RB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL: u32 = 23;
pub const RB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL: u32 = 24;
pub const RB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL: u32 = 25;
pub const RB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL: u32 = 26;
pub const RB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR: u32 = 27;
pub const RB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR: u32 = 28;
pub const RB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR: u32 = 29;
}

3104
vendor/rustybuzz/src/hb/unicode_norm.rs vendored Normal file

File diff suppressed because it is too large Load Diff