Skip to main content

harfrust/hb/
unicode.rs

1use super::ucd_table::ucd::*;
2use crate::hb::algs::*;
3use crate::Script;
4
5pub type Codepoint = u32;
6
7// Space estimates based on:
8// https://unicode.org/charts/PDF/U2000.pdf
9// https://docs.microsoft.com/en-us/typography/develop/character-design-standards/whitespace
10pub mod hb_unicode_funcs_t {
11    pub type space_t = u8;
12    pub const NOT_SPACE: u8 = 0;
13    pub const SPACE_EM: u8 = 1;
14    pub const SPACE_EM_2: u8 = 2;
15    pub const SPACE_EM_3: u8 = 3;
16    pub const SPACE_EM_4: u8 = 4;
17    pub const SPACE_EM_5: u8 = 5;
18    pub const SPACE_EM_6: u8 = 6;
19    pub const SPACE_EM_16: u8 = 16;
20    pub const SPACE_4_EM_18: u8 = 17; // 4/18th of an EM!
21    pub const SPACE: u8 = 18;
22    pub const SPACE_FIGURE: u8 = 19;
23    pub const SPACE_PUNCTUATION: u8 = 20;
24    pub const SPACE_NARROW: u8 = 21;
25}
26
27#[derive(Copy, Clone, PartialEq, Eq, Debug)]
28pub struct GeneralCategory(pub u8);
29
30#[allow(unused)]
31impl GeneralCategory {
32    pub const CONTROL: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_CONTROL as _);
33    pub const FORMAT: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_FORMAT as _);
34    pub const UNASSIGNED: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED as _);
35    pub const PRIVATE_USE: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE as _);
36    pub const SURROGATE: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_SURROGATE as _);
37    pub const LOWERCASE_LETTER: Self =
38        Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER as _);
39    pub const MODIFIER_LETTER: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER as _);
40    pub const OTHER_LETTER: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER as _);
41    pub const TITLECASE_LETTER: Self =
42        Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER as _);
43    pub const UPPERCASE_LETTER: Self =
44        Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER as _);
45    pub const SPACING_MARK: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK as _);
46    pub const ENCLOSING_MARK: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK as _);
47    pub const NON_SPACING_MARK: Self =
48        Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK as _);
49    pub const DECIMAL_NUMBER: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER as _);
50    pub const LETTER_NUMBER: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER as _);
51    pub const OTHER_NUMBER: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER as _);
52    pub const CONNECT_PUNCTUATION: Self =
53        Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION as _);
54    pub const DASH_PUNCTUATION: Self =
55        Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION as _);
56    pub const CLOSE_PUNCTUATION: Self =
57        Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION as _);
58    pub const FINAL_PUNCTUATION: Self =
59        Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION as _);
60    pub const INITIAL_PUNCTUATION: Self =
61        Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION as _);
62    pub const OTHER_PUNCTUATION: Self =
63        Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION as _);
64    pub const OPEN_PUNCTUATION: Self =
65        Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION as _);
66    pub const CURRENCY_SYMBOL: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL as _);
67    pub const MODIFIER_SYMBOL: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL as _);
68    pub const MATH_SYMBOL: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL as _);
69    pub const OTHER_SYMBOL: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL as _);
70    pub const LINE_SEPARATOR: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR as _);
71    pub const PARAGRAPH_SEPARATOR: Self =
72        Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR as _);
73    pub const SPACE_SEPARATOR: Self = Self(hb_gc::HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR as _);
74}
75
76impl GeneralCategory {
77    pub fn to_u8(self) -> u8 {
78        self.0
79    }
80
81    pub fn is_mark(&self) -> bool {
82        matches!(
83            *self,
84            Self::SPACING_MARK | Self::ENCLOSING_MARK | Self::NON_SPACING_MARK
85        )
86    }
87
88    pub fn is_letter(&self) -> bool {
89        matches!(
90            *self,
91            Self::LOWERCASE_LETTER
92                | Self::MODIFIER_LETTER
93                | Self::OTHER_LETTER
94                | Self::TITLECASE_LETTER
95                | Self::UPPERCASE_LETTER
96        )
97    }
98}
99
100#[allow(dead_code)]
101pub mod combining_class {
102    pub const NotReordered: u8 = 0;
103    pub const Overlay: u8 = 1;
104    pub const Nukta: u8 = 7;
105    pub const KanaVoicing: u8 = 8;
106    pub const Virama: u8 = 9;
107
108    /* Hebrew */
109    pub const CCC10: u8 = 10;
110    pub const CCC11: u8 = 11;
111    pub const CCC12: u8 = 12;
112    pub const CCC13: u8 = 13;
113    pub const CCC14: u8 = 14;
114    pub const CCC15: u8 = 15;
115    pub const CCC16: u8 = 16;
116    pub const CCC17: u8 = 17;
117    pub const CCC18: u8 = 18;
118    pub const CCC19: u8 = 19;
119    pub const CCC20: u8 = 20;
120    pub const CCC21: u8 = 21;
121    pub const CCC22: u8 = 22;
122    pub const CCC23: u8 = 23;
123    pub const CCC24: u8 = 24;
124    pub const CCC25: u8 = 25;
125    pub const CCC26: u8 = 26;
126
127    /* Arabic */
128    pub const CCC27: u8 = 27;
129    pub const CCC28: u8 = 28;
130    pub const CCC29: u8 = 29;
131    pub const CCC30: u8 = 30;
132    pub const CCC31: u8 = 31;
133    pub const CCC32: u8 = 32;
134    pub const CCC33: u8 = 33;
135    pub const CCC34: u8 = 34;
136    pub const CCC35: u8 = 35;
137
138    /* Syriac */
139    pub const CCC36: u8 = 36;
140
141    /* Telugu */
142    pub const CCC84: u8 = 84;
143    pub const CCC91: u8 = 91;
144
145    /* Thai */
146    pub const CCC103: u8 = 103;
147    pub const CCC107: u8 = 107;
148
149    /* Lao */
150    pub const CCC118: u8 = 118;
151    pub const CCC122: u8 = 122;
152
153    /* Tibetan */
154    pub const CCC129: u8 = 129;
155    pub const CCC130: u8 = 130;
156    pub const CCC132: u8 = 132;
157
158    pub const AttachedBelowLeft: u8 = 200;
159    pub const AttachedBelow: u8 = 202;
160    pub const AttachedAbove: u8 = 214;
161    pub const AttachedAboveRight: u8 = 216;
162    pub const BelowLeft: u8 = 218;
163    pub const Below: u8 = 220;
164    pub const BelowRight: u8 = 222;
165    pub const Left: u8 = 224;
166    pub const Right: u8 = 226;
167    pub const AboveLeft: u8 = 228;
168    pub const Above: u8 = 230;
169    pub const AboveRight: u8 = 232;
170    pub const DoubleBelow: u8 = 233;
171    pub const DoubleAbove: u8 = 234;
172
173    pub const IotaSubscript: u8 = 240;
174
175    pub const Invalid: u8 = 255;
176}
177
178#[allow(dead_code)]
179pub mod modified_combining_class {
180    // Hebrew
181    //
182    // We permute the "fixed-position" classes 10-26 into the order
183    // described in the SBL Hebrew manual:
184    //
185    // https://www.sbl-site.org/Fonts/SBLHebrewUserManual1.5x.pdf
186    //
187    // (as recommended by:
188    //  https://forum.fontlab.com/archive-old-microsoft-volt-group/vista-and-diacritic-ordering/msg22823/)
189    //
190    // More details here:
191    // https://bugzilla.mozilla.org/show_bug.cgi?id=662055
192    pub const CCC10: u8 = 22; // sheva
193    pub const CCC11: u8 = 15; // hataf segol
194    pub const CCC12: u8 = 16; // hataf patah
195    pub const CCC13: u8 = 17; // hataf qamats
196    pub const CCC14: u8 = 23; // hiriq
197    pub const CCC15: u8 = 18; // tsere
198    pub const CCC16: u8 = 19; // segol
199    pub const CCC17: u8 = 20; // patah
200    pub const CCC18: u8 = 21; // qamats & qamats qatan
201    pub const CCC19: u8 = 14; // holam & holam haser for vav
202    pub const CCC20: u8 = 24; // qubuts
203    pub const CCC21: u8 = 12; // dagesh
204    pub const CCC22: u8 = 25; // meteg
205    pub const CCC23: u8 = 13; // rafe
206    pub const CCC24: u8 = 10; // shin dot
207    pub const CCC25: u8 = 11; // sin dot
208    pub const CCC26: u8 = 26; // point varika
209
210    // Arabic
211    //
212    // Modify to move Shadda (ccc=33) before other marks.  See:
213    // https://unicode.org/faq/normalization.html#8
214    // https://unicode.org/faq/normalization.html#9
215    pub const CCC27: u8 = 28; // fathatan
216    pub const CCC28: u8 = 29; // dammatan
217    pub const CCC29: u8 = 30; // kasratan
218    pub const CCC30: u8 = 31; // fatha
219    pub const CCC31: u8 = 32; // damma
220    pub const CCC32: u8 = 33; // kasra
221    pub const CCC33: u8 = 27; // shadda
222    pub const CCC34: u8 = 34; // sukun
223    pub const CCC35: u8 = 35; // superscript alef
224
225    // Syriac
226    pub const CCC36: u8 = 36; // superscript alaph
227
228    // Telugu
229    //
230    // Modify Telugu length marks (ccc=84, ccc=91).
231    // These are the only matras in the main Indic scripts range that have
232    // a non-zero ccc.  That makes them reorder with the Halant that is
233    // ccc=9.  Just zero them, we don't need them in our Indic shaper.
234    pub const CCC84: u8 = 0; // length mark
235    pub const CCC91: u8 = 0; // ai length mark
236
237    // Thai
238    //
239    // Modify U+0E38 and U+0E39 (ccc=103) to be reordered before U+0E3A (ccc=9).
240    // Assign 3, which is unassigned otherwise.
241    // Uniscribe does this reordering too.
242    pub const CCC103: u8 = 3; // sara u / sara uu
243    pub const CCC107: u8 = 107; // mai *
244
245    // Lao
246    pub const CCC118: u8 = 118; // sign u / sign uu
247    pub const CCC122: u8 = 122; // mai *
248
249    // Tibetan
250    //
251    // In case of multiple vowel-signs, use u first (but after achung)
252    // this allows Dzongkha multi-vowel shortcuts to render correctly
253    pub const CCC129: u8 = 129; // sign aa
254    pub const CCC130: u8 = 132; // sign i
255    pub const CCC132: u8 = 131; // sign u
256}
257
258#[rustfmt::skip]
259static MODIFIED_COMBINING_CLASS: &[u8; 256] = &[
260    combining_class::NotReordered,
261    combining_class::Overlay,
262    2, 3, 4, 5, 6,
263    combining_class::Nukta,
264    combining_class::KanaVoicing,
265    combining_class::Virama,
266
267    // Hebrew
268    modified_combining_class::CCC10,
269    modified_combining_class::CCC11,
270    modified_combining_class::CCC12,
271    modified_combining_class::CCC13,
272    modified_combining_class::CCC14,
273    modified_combining_class::CCC15,
274    modified_combining_class::CCC16,
275    modified_combining_class::CCC17,
276    modified_combining_class::CCC18,
277    modified_combining_class::CCC19,
278    modified_combining_class::CCC20,
279    modified_combining_class::CCC21,
280    modified_combining_class::CCC22,
281    modified_combining_class::CCC23,
282    modified_combining_class::CCC24,
283    modified_combining_class::CCC25,
284    modified_combining_class::CCC26,
285
286    // Arabic
287    modified_combining_class::CCC27,
288    modified_combining_class::CCC28,
289    modified_combining_class::CCC29,
290    modified_combining_class::CCC30,
291    modified_combining_class::CCC31,
292    modified_combining_class::CCC32,
293    modified_combining_class::CCC33,
294    modified_combining_class::CCC34,
295    modified_combining_class::CCC35,
296
297    // Syriac
298    modified_combining_class::CCC36,
299
300    37, 38, 39,
301    40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
302    60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
303    80, 81, 82, 83,
304
305    // Telugu
306    modified_combining_class::CCC84,
307    85, 86, 87, 88, 89, 90,
308    modified_combining_class::CCC91,
309    92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102,
310
311    // Thai
312    modified_combining_class::CCC103,
313    104, 105, 106,
314    modified_combining_class::CCC107,
315    108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
316
317    // Lao
318    modified_combining_class::CCC118,
319    119, 120, 121,
320    modified_combining_class::CCC122,
321    123, 124, 125, 126, 127, 128,
322
323    // Tibetan
324    modified_combining_class::CCC129,
325    modified_combining_class::CCC130,
326    131,
327    modified_combining_class::CCC132,
328    133, 134, 135, 136, 137, 138, 139,
329
330
331    140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
332    150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
333    160, 161, 162, 163, 164, 165, 166, 167, 168, 169,
334    170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
335    180, 181, 182, 183, 184, 185, 186, 187, 188, 189,
336    190, 191, 192, 193, 194, 195, 196, 197, 198, 199,
337
338    combining_class::AttachedBelowLeft,
339    201,
340    combining_class::AttachedBelow,
341    203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213,
342    combining_class::AttachedAbove,
343    215,
344    combining_class::AttachedAboveRight,
345    217,
346    combining_class::BelowLeft,
347    219,
348    combining_class::Below,
349    221,
350    combining_class::BelowRight,
351    223,
352    combining_class::Left,
353    225,
354    combining_class::Right,
355    227,
356    combining_class::AboveLeft,
357    229,
358    combining_class::Above,
359    231,
360    combining_class::AboveRight,
361    combining_class::DoubleBelow,
362    combining_class::DoubleAbove,
363    235, 236, 237, 238, 239,
364    combining_class::IotaSubscript,
365    241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
366    combining_class::Invalid,
367];
368
369pub trait CharExt {
370    fn script(self) -> Script;
371    fn general_category(self) -> GeneralCategory;
372    fn space_fallback(self) -> hb_unicode_funcs_t::space_t;
373    fn combining_class(self) -> u8;
374    fn modified_combining_class(self) -> u8;
375    fn mirrored(self) -> Option<Codepoint>;
376    fn is_emoji_extended_pictographic(self) -> bool;
377    fn is_default_ignorable(self) -> bool;
378    fn is_variation_selector(self) -> bool;
379    fn vertical(self) -> Option<Codepoint>;
380}
381
382impl CharExt for Codepoint {
383    fn script(self) -> Script {
384        _hb_ucd_sc_map[_hb_ucd_sc(self as usize) as usize]
385    }
386
387    fn general_category(self) -> GeneralCategory {
388        GeneralCategory(_hb_ucd_gc(self as usize))
389    }
390
391    fn space_fallback(self) -> hb_unicode_funcs_t::space_t {
392        use hb_unicode_funcs_t::*;
393
394        // All GC=Zs chars that can use a fallback.
395        match self {
396            0x0020 => SPACE,             // SPACE
397            0x00A0 => SPACE,             // NO-BREAK SPACE
398            0x2000 => SPACE_EM_2,        // EN QUAD
399            0x2001 => SPACE_EM,          // EM QUAD
400            0x2002 => SPACE_EM_2,        // EN SPACE
401            0x2003 => SPACE_EM,          // EM SPACE
402            0x2004 => SPACE_EM_3,        // THREE-PER-EM SPACE
403            0x2005 => SPACE_EM_4,        // FOUR-PER-EM SPACE
404            0x2006 => SPACE_EM_6,        // SIX-PER-EM SPACE
405            0x2007 => SPACE_FIGURE,      // FIGURE SPACE
406            0x2008 => SPACE_PUNCTUATION, // PUNCTUATION SPACE
407            0x2009 => SPACE_EM_5,        // THIN SPACE
408            0x200A => SPACE_EM_16,       // HAIR SPACE
409            0x202F => SPACE_NARROW,      // NARROW NO-BREAK SPACE
410            0x205F => SPACE_4_EM_18,     // MEDIUM MATHEMATICAL SPACE
411            0x3000 => SPACE_EM,          // IDEOGRAPHIC SPACE
412            _ => NOT_SPACE,              // OGHAM SPACE MARK
413        }
414    }
415
416    fn combining_class(self) -> u8 {
417        _hb_ucd_ccc(self as usize)
418    }
419
420    fn modified_combining_class(self) -> u8 {
421        let u = self;
422
423        // Reorder SAKOT to ensure it comes after any tone marks.
424        if u == 0x1A60 {
425            return 254;
426        }
427
428        // Reorder PADMA to ensure it comes after any vowel marks.
429        if u == 0x0FC6 {
430            return 254;
431        }
432
433        // Reorder TSA -PHRU to reorder before U+0F74
434        if u == 0x0F39 {
435            return 127;
436        }
437
438        let k = u.combining_class();
439
440        MODIFIED_COMBINING_CLASS[k as usize]
441    }
442
443    fn mirrored(self) -> Option<Codepoint> {
444        let delta = _hb_ucd_bmg(self as usize);
445        if delta == 0 {
446            None
447        } else {
448            Some(((self as i32).wrapping_add(delta as i32)) as u32)
449        }
450    }
451
452    fn is_emoji_extended_pictographic(self) -> bool {
453        super::unicode_emoji_table::is_Extended_Pictographic(self)
454    }
455
456    /// Default_Ignorable codepoints:
457    ///
458    /// Note: While U+115F, U+1160, U+3164 and U+FFA0 are Default_Ignorable,
459    /// we do NOT want to hide them, as the way Uniscribe has implemented them
460    /// is with regular spacing glyphs, and that's the way fonts are made to work.
461    /// As such, we make exceptions for those four.
462    /// Also ignoring U+1BCA0..1BCA3. https://github.com/harfbuzz/harfbuzz/issues/503
463    ///
464    /// Unicode 14.0:
465    /// $ grep '; Default_Ignorable_Code_Point ' DerivedCoreProperties.txt | sed 's/;.*#/#/'
466    /// 00AD          # Cf       SOFT HYPHEN
467    /// 034F          # Mn       COMBINING GRAPHEME JOINER
468    /// 061C          # Cf       ARABIC LETTER MARK
469    /// 115F..1160    # Lo   [2] HANGUL CHOSEONG FILLER..HANGUL JUNGSEONG FILLER
470    /// 17B4..17B5    # Mn   [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
471    /// 180B..180D    # Mn   [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
472    /// 180E          # Cf       MONGOLIAN VOWEL SEPARATOR
473    /// 180F          # Mn       MONGOLIAN FREE VARIATION SELECTOR FOUR
474    /// 200B..200F    # Cf   [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK
475    /// 202A..202E    # Cf   [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
476    /// 2060..2064    # Cf   [5] WORD JOINER..INVISIBLE PLUS
477    /// 2065          # Cn       <reserved-2065>
478    /// 2066..206F    # Cf  [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES
479    /// 3164          # Lo       HANGUL FILLER
480    /// FE00..FE0F    # Mn  [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
481    /// FEFF          # Cf       ZERO WIDTH NO-BREAK SPACE
482    /// FFA0          # Lo       HALFWIDTH HANGUL FILLER
483    /// FFF0..FFF8    # Cn   [9] <reserved-FFF0>..<reserved-FFF8>
484    /// 1BCA0..1BCA3  # Cf   [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
485    /// 1D173..1D17A  # Cf   [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
486    /// E0000         # Cn       <reserved-E0000>
487    /// E0001         # Cf       LANGUAGE TAG
488    /// E0002..E001F  # Cn  [30] <reserved-E0002>..<reserved-E001F>
489    /// E0020..E007F  # Cf  [96] TAG SPACE..CANCEL TAG
490    /// E0080..E00FF  # Cn [128] <reserved-E0080>..<reserved-E00FF>
491    /// E0100..E01EF  # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
492    /// E01F0..E0FFF  # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
493    fn is_default_ignorable(self) -> bool {
494        let ch = self;
495        let plane = ch >> 16;
496        if plane == 0 {
497            // BMP
498            let page = ch >> 8;
499            match page {
500                0x00 => ch == 0x00AD,
501                0x03 => ch == 0x034F,
502                0x06 => ch == 0x061C,
503                0x17 => (0x17B4..=0x17B5).contains(&ch),
504                0x18 => (0x180B..=0x180E).contains(&ch),
505                0x20 => {
506                    (0x200B..=0x200F).contains(&ch)
507                        || (0x202A..=0x202E).contains(&ch)
508                        || (0x2060..=0x206F).contains(&ch)
509                }
510                0xFE => (0xFE00..=0xFE0F).contains(&ch) || ch == 0xFEFF,
511                0xFF => (0xFFF0..=0xFFF8).contains(&ch),
512                _ => false,
513            }
514        } else {
515            // Other planes
516            match plane {
517                0x01 => (0x1D173..=0x1D17A).contains(&ch),
518                0x0E => (0xE0000..=0xE0FFF).contains(&ch),
519                _ => false,
520            }
521        }
522    }
523
524    fn is_variation_selector(self) -> bool {
525        // U+180B..180D, U+180F MONGOLIAN FREE VARIATION SELECTORs are handled in the
526        //Arabic shaper. No need to match them here.
527        (0x0FE00..=0x0FE0F).contains(&self) || // VARIATION SELECTOR - 1..16
528        (0xE0100..=0xE01EF).contains(&self) // VARIATION SELECTOR - 17..256
529    }
530
531    fn vertical(self) -> Option<Codepoint> {
532        Some(match self >> 8 {
533            0x20 => match self {
534                0x2013 => 0xfe32, // EN DASH
535                0x2014 => 0xfe31, // EM DASH
536                0x2025 => 0xfe30, // TWO DOT LEADER
537                0x2026 => 0xfe19, // HORIZONTAL ELLIPSIS
538                _ => return None,
539            },
540            0x30 => match self {
541                0x3001 => 0xfe11, // IDEOGRAPHIC COMMA
542                0x3002 => 0xfe12, // IDEOGRAPHIC FULL STOP
543                0x3008 => 0xfe3f, // LEFT ANGLE BRACKET
544                0x3009 => 0xfe40, // RIGHT ANGLE BRACKET
545                0x300a => 0xfe3d, // LEFT DOUBLE ANGLE BRACKET
546                0x300b => 0xfe3e, // RIGHT DOUBLE ANGLE BRACKET
547                0x300c => 0xfe41, // LEFT CORNER BRACKET
548                0x300d => 0xfe42, // RIGHT CORNER BRACKET
549                0x300e => 0xfe43, // LEFT WHITE CORNER BRACKET
550                0x300f => 0xfe44, // RIGHT WHITE CORNER BRACKET
551                0x3010 => 0xfe3b, // LEFT BLACK LENTICULAR BRACKET
552                0x3011 => 0xfe3c, // RIGHT BLACK LENTICULAR BRACKET
553                0x3014 => 0xfe39, // LEFT TORTOISE SHELL BRACKET
554                0x3015 => 0xfe3a, // RIGHT TORTOISE SHELL BRACKET
555                0x3016 => 0xfe17, // LEFT WHITE LENTICULAR BRACKET
556                0x3017 => 0xfe18, // RIGHT WHITE LENTICULAR BRACKET
557                _ => return None,
558            },
559            0xfe => match self {
560                0xfe4f => 0xfe34, // WAVY LOW LINE
561                _ => return None,
562            },
563            0xff => match self {
564                0xff01 => 0xfe15, // FULLWIDTH EXCLAMATION MARK
565                0xff08 => 0xfe35, // FULLWIDTH LEFT PARENTHESIS
566                0xff09 => 0xfe36, // FULLWIDTH RIGHT PARENTHESIS
567                0xff0c => 0xfe10, // FULLWIDTH COMMA
568                0xff1a => 0xfe13, // FULLWIDTH COLON
569                0xff1b => 0xfe14, // FULLWIDTH SEMICOLON
570                0xff1f => 0xfe16, // FULLWIDTH QUESTION MARK
571                0xff3b => 0xfe47, // FULLWIDTH LEFT SQUARE BRACKET
572                0xff3d => 0xfe48, // FULLWIDTH RIGHT SQUARE BRACKET
573                0xff3f => 0xfe33, // FULLWIDTH LOW LINE
574                0xff5b => 0xfe37, // FULLWIDTH LEFT CURLY BRACKET
575                0xff5d => 0xfe38, // FULLWIDTH RIGHT CURLY BRACKET
576                _ => return None,
577            },
578            _ => return None,
579        })
580    }
581}
582
583const S_BASE: u32 = 0xAC00;
584const L_BASE: u32 = 0x1100;
585const V_BASE: u32 = 0x1161;
586const T_BASE: u32 = 0x11A7;
587const L_COUNT: u32 = 19;
588const V_COUNT: u32 = 21;
589const T_COUNT: u32 = 28;
590const N_COUNT: u32 = V_COUNT * T_COUNT;
591const S_COUNT: u32 = L_COUNT * N_COUNT;
592
593pub fn compose(a: Codepoint, b: Codepoint) -> Option<Codepoint> {
594    // Hangul is handled algorithmically.
595    if let Some(ab) = compose_hangul(a, b) {
596        return Some(ab);
597    }
598
599    let u: u32;
600
601    if (a & 0xFFFF_F800) == 0x0000 && (b & 0xFFFF_FF80) == 0x0300 {
602        /* If "a" is small enough and "b" is in the U+0300 range,
603         * the composition data is encoded in a 32bit array sorted
604         * by "a,b" pair. */
605        let k = HB_CODEPOINT_ENCODE3_11_7_14(a, b, 0);
606        let v = _hb_ucd_dm2_u32_map
607            .binary_search_by(|probe| {
608                let key = probe & HB_CODEPOINT_ENCODE3_11_7_14(0x001F_FFFF, 0x001F_FFFF, 0);
609                key.cmp(&k)
610            })
611            .ok()
612            .map(|index| _hb_ucd_dm2_u32_map[index]);
613
614        if let Some(value) = v {
615            u = HB_CODEPOINT_DECODE3_11_7_14_3(value);
616        } else {
617            return None;
618        }
619    } else {
620        /* Otherwise it is stored in a 64bit array sorted by
621         * "a,b" pair. */
622        let k = HB_CODEPOINT_ENCODE3(a, b, 0);
623        let v = _hb_ucd_dm2_u64_map
624            .binary_search_by(|probe| {
625                let key = probe & HB_CODEPOINT_ENCODE3(0x001F_FFFF, 0x001F_FFFF, 0);
626                key.cmp(&k)
627            })
628            .ok()
629            .map(|index| _hb_ucd_dm2_u64_map[index]);
630
631        if let Some(value) = v {
632            u = HB_CODEPOINT_DECODE3_3(value);
633        } else {
634            return None;
635        }
636    }
637
638    if u == 0 {
639        None
640    } else {
641        Some(u)
642    }
643}
644
645fn compose_hangul(a: Codepoint, b: Codepoint) -> Option<Codepoint> {
646    let l = a;
647    let v = b;
648    if L_BASE <= l && l < (L_BASE + L_COUNT) && V_BASE <= v && v < (V_BASE + V_COUNT) {
649        let r = S_BASE + (l - L_BASE) * N_COUNT + (v - V_BASE) * T_COUNT;
650        Some(r)
651    } else if S_BASE <= l
652        && l <= (S_BASE + S_COUNT - T_COUNT)
653        && T_BASE <= v
654        && v < (T_BASE + T_COUNT)
655        && (l - S_BASE) % T_COUNT == 0
656    {
657        let r = l + (v - T_BASE);
658        Some(r)
659    } else {
660        None
661    }
662}
663
664pub fn decompose(ab: Codepoint) -> Option<(Codepoint, Codepoint)> {
665    if let Some((a, b)) = decompose_hangul(ab) {
666        return Some((a, b));
667    }
668
669    let mut i = _hb_ucd_dm(ab as usize) as usize;
670
671    // If no data, there's no decomposition.
672    if i == 0 {
673        return None;
674    }
675    i -= 1;
676
677    if i < _hb_ucd_dm1_p0_map.len() + _hb_ucd_dm1_p2_map.len() {
678        let a = if i < _hb_ucd_dm1_p0_map.len() {
679            _hb_ucd_dm1_p0_map[i] as u32
680        } else {
681            let j = i - _hb_ucd_dm1_p0_map.len();
682            0x20000 | _hb_ucd_dm1_p2_map[j] as u32
683        };
684        return Some((a, 0));
685    }
686
687    i -= _hb_ucd_dm1_p0_map.len() + _hb_ucd_dm1_p2_map.len();
688
689    if i < _hb_ucd_dm2_u32_map.len() {
690        let v = _hb_ucd_dm2_u32_map[i];
691        let a = HB_CODEPOINT_DECODE3_11_7_14_1(v);
692        let b = HB_CODEPOINT_DECODE3_11_7_14_2(v);
693        return Some((a, b));
694    }
695
696    i -= _hb_ucd_dm2_u32_map.len();
697
698    let v = _hb_ucd_dm2_u64_map[i];
699    let a = HB_CODEPOINT_DECODE3_1(v);
700    let b = HB_CODEPOINT_DECODE3_2(v);
701    Some((a, b))
702}
703
704pub fn decompose_hangul(ab: Codepoint) -> Option<(Codepoint, Codepoint)> {
705    let si = ab.wrapping_sub(S_BASE);
706    if si >= S_COUNT {
707        return None;
708    }
709
710    let (a, b) = if si % T_COUNT != 0 {
711        // LV,T
712        (S_BASE + (si / T_COUNT) * T_COUNT, T_BASE + (si % T_COUNT))
713    } else {
714        // L,V
715        (L_BASE + (si / N_COUNT), V_BASE + (si % N_COUNT) / T_COUNT)
716    };
717    Some((a, b))
718}
719
720pub mod hb_gc {
721    pub const HB_UNICODE_GENERAL_CATEGORY_CONTROL: u32 = 0;
722    pub const HB_UNICODE_GENERAL_CATEGORY_FORMAT: u32 = 1;
723    pub const HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED: u32 = 2;
724    pub const HB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE: u32 = 3;
725    pub const HB_UNICODE_GENERAL_CATEGORY_SURROGATE: u32 = 4;
726    pub const HB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER: u32 = 5;
727    pub const HB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER: u32 = 6;
728    pub const HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER: u32 = 7;
729    pub const HB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER: u32 = 8;
730    pub const HB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER: u32 = 9;
731    pub const HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK: u32 = 10;
732    pub const HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK: u32 = 11;
733    pub const HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK: u32 = 12;
734    pub const HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER: u32 = 13;
735    pub const HB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER: u32 = 14;
736    pub const HB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER: u32 = 15;
737    pub const HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: u32 = 16;
738    pub const HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION: u32 = 17;
739    pub const HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: u32 = 18;
740    pub const HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: u32 = 19;
741    pub const HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: u32 = 20;
742    pub const HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: u32 = 21;
743    pub const HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION: u32 = 22;
744    pub const HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL: u32 = 23;
745    pub const HB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL: u32 = 24;
746    pub const HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL: u32 = 25;
747    pub const HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL: u32 = 26;
748    pub const HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR: u32 = 27;
749    pub const HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR: u32 = 28;
750    pub const HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR: u32 = 29;
751}