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

82
vendor/rustybuzz/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,82 @@
/*!
A complete [harfbuzz](https://github.com/harfbuzz/harfbuzz) shaping algorithm port to Rust.
*/
#![no_std]
#![warn(missing_docs)]
#[cfg(not(any(feature = "std", feature = "libm")))]
compile_error!("You have to activate either the `std` or the `libm` feature.");
#[cfg(feature = "std")]
extern crate std;
extern crate alloc;
mod hb;
pub use ttf_parser;
pub use hb::buffer::hb_glyph_info_t as GlyphInfo;
pub use hb::buffer::{GlyphBuffer, GlyphPosition, UnicodeBuffer};
pub use hb::common::{script, Direction, Feature, Language, Script, Variation};
pub use hb::face::hb_font_t as Face;
pub use hb::ot_shape_plan::hb_ot_shape_plan_t as ShapePlan;
pub use hb::shape::{shape, shape_with_plan};
bitflags::bitflags! {
/// Flags for buffers.
#[derive(Default, Debug, Clone, Copy)]
pub struct BufferFlags: u32 {
/// Indicates that special handling of the beginning of text paragraph can be applied to this buffer. Should usually be set, unless you are passing to the buffer only part of the text without the full context.
const BEGINNING_OF_TEXT = 1 << 1;
/// Indicates that special handling of the end of text paragraph can be applied to this buffer, similar to [`BufferFlags::BEGINNING_OF_TEXT`].
const END_OF_TEXT = 1 << 2;
/// Indicates that characters with `Default_Ignorable` Unicode property should use the corresponding glyph from the font, instead of hiding them (done by replacing them with the space glyph and zeroing the advance width.) This flag takes precedence over [`BufferFlags::REMOVE_DEFAULT_IGNORABLES`].
const PRESERVE_DEFAULT_IGNORABLES = 1 << 3;
/// Indicates that characters with `Default_Ignorable` Unicode property should be removed from glyph string instead of hiding them (done by replacing them with the space glyph and zeroing the advance width.) [`BufferFlags::PRESERVE_DEFAULT_IGNORABLES`] takes precedence over this flag.
const REMOVE_DEFAULT_IGNORABLES = 1 << 4;
/// Indicates that a dotted circle should not be inserted in the rendering of incorrect character sequences (such as `<0905 093E>`).
const DO_NOT_INSERT_DOTTED_CIRCLE = 1 << 5;
/// Indicates that the shape() call and its variants should perform various verification processes on the results of the shaping operation on the buffer. If the verification fails, then either a buffer message is sent, if a message handler is installed on the buffer, or a message is written to standard error. In either case, the shaping result might be modified to show the failed output.
const VERIFY = 1 << 6;
/// Indicates that the `UNSAFE_TO_CONCAT` glyph-flag should be produced by the shaper. By default it will not be produced since it incurs a cost.
const PRODUCE_UNSAFE_TO_CONCAT = 1 << 7;
}
}
/// A cluster level.
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum BufferClusterLevel {
MonotoneGraphemes,
MonotoneCharacters,
Characters,
}
impl Default for BufferClusterLevel {
#[inline]
fn default() -> Self {
BufferClusterLevel::MonotoneGraphemes
}
}
bitflags::bitflags! {
/// Flags used for serialization with a `BufferSerializer`.
#[derive(Default)]
pub struct SerializeFlags: u8 {
/// Do not serialize glyph cluster.
const NO_CLUSTERS = 0b00000001;
/// Do not serialize glyph position information.
const NO_POSITIONS = 0b00000010;
/// Do no serialize glyph name.
const NO_GLYPH_NAMES = 0b00000100;
/// Serialize glyph extents.
const GLYPH_EXTENTS = 0b00001000;
/// Serialize glyph flags.
const GLYPH_FLAGS = 0b00010000;
/// Do not serialize glyph advances, glyph offsets will reflect absolute
/// glyph positions.
const NO_ADVANCES = 0b00100000;
}
}