1 | use alloc::string::String; |
2 | use core::ops::{Bound, RangeBounds}; |
3 | |
4 | use ttf_parser::Tag; |
5 | |
6 | use super::text_parser::TextParser; |
7 | |
8 | pub type hb_codepoint_t = char; // uint32_t in C++ |
9 | |
10 | pub const HB_FEATURE_GLOBAL_START: u32 = 0; |
11 | pub const HB_FEATURE_GLOBAL_END: u32 = u32::MAX; |
12 | |
13 | /// Defines the direction in which text is to be read. |
14 | #[derive (Clone, Copy, PartialEq, Eq, Hash, Debug)] |
15 | pub enum Direction { |
16 | /// Initial, unset direction. |
17 | Invalid, |
18 | /// Text is set horizontally from left to right. |
19 | LeftToRight, |
20 | /// Text is set horizontally from right to left. |
21 | RightToLeft, |
22 | /// Text is set vertically from top to bottom. |
23 | TopToBottom, |
24 | /// Text is set vertically from bottom to top. |
25 | BottomToTop, |
26 | } |
27 | |
28 | impl Direction { |
29 | #[inline ] |
30 | pub(crate) fn is_horizontal(self) -> bool { |
31 | match self { |
32 | Direction::Invalid => false, |
33 | Direction::LeftToRight => true, |
34 | Direction::RightToLeft => true, |
35 | Direction::TopToBottom => false, |
36 | Direction::BottomToTop => false, |
37 | } |
38 | } |
39 | |
40 | #[inline ] |
41 | pub(crate) fn is_vertical(self) -> bool { |
42 | !self.is_horizontal() |
43 | } |
44 | |
45 | #[inline ] |
46 | pub(crate) fn is_forward(self) -> bool { |
47 | match self { |
48 | Direction::Invalid => false, |
49 | Direction::LeftToRight => true, |
50 | Direction::RightToLeft => false, |
51 | Direction::TopToBottom => true, |
52 | Direction::BottomToTop => false, |
53 | } |
54 | } |
55 | |
56 | #[inline ] |
57 | pub(crate) fn is_backward(self) -> bool { |
58 | !self.is_forward() |
59 | } |
60 | |
61 | #[inline ] |
62 | pub(crate) fn reverse(self) -> Self { |
63 | match self { |
64 | Direction::Invalid => Direction::Invalid, |
65 | Direction::LeftToRight => Direction::RightToLeft, |
66 | Direction::RightToLeft => Direction::LeftToRight, |
67 | Direction::TopToBottom => Direction::BottomToTop, |
68 | Direction::BottomToTop => Direction::TopToBottom, |
69 | } |
70 | } |
71 | |
72 | pub(crate) fn from_script(script: Script) -> Option<Self> { |
73 | // https://docs.google.com/spreadsheets/d/1Y90M0Ie3MUJ6UVCRDOypOtijlMDLNNyyLk36T6iMu0o |
74 | |
75 | match script { |
76 | // Unicode-1.1 additions |
77 | script::ARABIC | |
78 | script::HEBREW | |
79 | |
80 | // Unicode-3.0 additions |
81 | script::SYRIAC | |
82 | script::THAANA | |
83 | |
84 | // Unicode-4.0 additions |
85 | script::CYPRIOT | |
86 | |
87 | // Unicode-4.1 additions |
88 | script::KHAROSHTHI | |
89 | |
90 | // Unicode-5.0 additions |
91 | script::PHOENICIAN | |
92 | script::NKO | |
93 | |
94 | // Unicode-5.1 additions |
95 | script::LYDIAN | |
96 | |
97 | // Unicode-5.2 additions |
98 | script::AVESTAN | |
99 | script::IMPERIAL_ARAMAIC | |
100 | script::INSCRIPTIONAL_PAHLAVI | |
101 | script::INSCRIPTIONAL_PARTHIAN | |
102 | script::OLD_SOUTH_ARABIAN | |
103 | script::OLD_TURKIC | |
104 | script::SAMARITAN | |
105 | |
106 | // Unicode-6.0 additions |
107 | script::MANDAIC | |
108 | |
109 | // Unicode-6.1 additions |
110 | script::MEROITIC_CURSIVE | |
111 | script::MEROITIC_HIEROGLYPHS | |
112 | |
113 | // Unicode-7.0 additions |
114 | script::MANICHAEAN | |
115 | script::MENDE_KIKAKUI | |
116 | script::NABATAEAN | |
117 | script::OLD_NORTH_ARABIAN | |
118 | script::PALMYRENE | |
119 | script::PSALTER_PAHLAVI | |
120 | |
121 | // Unicode-8.0 additions |
122 | script::HATRAN | |
123 | |
124 | // Unicode-9.0 additions |
125 | script::ADLAM | |
126 | |
127 | // Unicode-11.0 additions |
128 | script::HANIFI_ROHINGYA | |
129 | script::OLD_SOGDIAN | |
130 | script::SOGDIAN | |
131 | |
132 | // Unicode-12.0 additions |
133 | script::ELYMAIC | |
134 | |
135 | // Unicode-13.0 additions |
136 | script::CHORASMIAN | |
137 | script::YEZIDI | |
138 | |
139 | // Unicode-14.0 additions |
140 | script::OLD_UYGHUR => { |
141 | Some(Direction::RightToLeft) |
142 | } |
143 | |
144 | // https://github.com/harfbuzz/harfbuzz/issues/1000 |
145 | script::OLD_HUNGARIAN | |
146 | script::OLD_ITALIC | |
147 | script::RUNIC | |
148 | script::TIFINAGH => { |
149 | None |
150 | } |
151 | |
152 | _ => Some(Direction::LeftToRight), |
153 | } |
154 | } |
155 | } |
156 | |
157 | impl Default for Direction { |
158 | #[inline ] |
159 | fn default() -> Self { |
160 | Direction::Invalid |
161 | } |
162 | } |
163 | |
164 | impl core::str::FromStr for Direction { |
165 | type Err = &'static str; |
166 | |
167 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
168 | if s.is_empty() { |
169 | return Err("invalid direction" ); |
170 | } |
171 | |
172 | // harfbuzz also matches only the first letter. |
173 | match s.as_bytes()[0].to_ascii_lowercase() { |
174 | b'l' => Ok(Direction::LeftToRight), |
175 | b'r' => Ok(Direction::RightToLeft), |
176 | b't' => Ok(Direction::TopToBottom), |
177 | b'b' => Ok(Direction::BottomToTop), |
178 | _ => Err("invalid direction" ), |
179 | } |
180 | } |
181 | } |
182 | |
183 | /// A script language. |
184 | #[derive (Clone, PartialEq, Eq, Hash, Debug)] |
185 | pub struct Language(String); |
186 | |
187 | impl Language { |
188 | /// Returns the language as a string. |
189 | #[inline ] |
190 | pub fn as_str(&self) -> &str { |
191 | self.0.as_str() |
192 | } |
193 | } |
194 | |
195 | impl core::str::FromStr for Language { |
196 | type Err = &'static str; |
197 | |
198 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
199 | if !s.is_empty() { |
200 | Ok(Language(s.to_ascii_lowercase())) |
201 | } else { |
202 | Err("invalid language" ) |
203 | } |
204 | } |
205 | } |
206 | |
207 | // In harfbuzz, despite having `hb_script_t`, script can actually have any tag. |
208 | // So we're doing the same. |
209 | // The only difference is that `Script` cannot be set to `HB_SCRIPT_INVALID`. |
210 | /// A text script. |
211 | #[allow (missing_docs)] |
212 | #[derive (Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] |
213 | pub struct Script(pub(crate) Tag); |
214 | |
215 | impl Script { |
216 | #[inline ] |
217 | pub(crate) const fn from_bytes(bytes: &[u8; 4]) -> Self { |
218 | Script(Tag::from_bytes(bytes)) |
219 | } |
220 | |
221 | /// Converts an ISO 15924 script tag to a corresponding `Script`. |
222 | pub fn from_iso15924_tag(tag: Tag) -> Option<Script> { |
223 | if tag.is_null() { |
224 | return None; |
225 | } |
226 | |
227 | // Be lenient, adjust case (one capital letter followed by three small letters). |
228 | let tag = Tag((tag.as_u32() & 0xDFDFDFDF) | 0x00202020); |
229 | |
230 | match &tag.to_bytes() { |
231 | // These graduated from the 'Q' private-area codes, but |
232 | // the old code is still aliased by Unicode, and the Qaai |
233 | // one in use by ICU. |
234 | b"Qaai" => return Some(script::INHERITED), |
235 | b"Qaac" => return Some(script::COPTIC), |
236 | |
237 | // Script variants from https://unicode.org/iso15924/ |
238 | b"Aran" => return Some(script::ARABIC), |
239 | b"Cyrs" => return Some(script::CYRILLIC), |
240 | b"Geok" => return Some(script::GEORGIAN), |
241 | b"Hans" | b"Hant" => return Some(script::HAN), |
242 | b"Jamo" => return Some(script::HANGUL), |
243 | b"Latf" | b"Latg" => return Some(script::LATIN), |
244 | b"Syre" | b"Syrj" | b"Syrn" => return Some(script::SYRIAC), |
245 | |
246 | _ => {} |
247 | } |
248 | |
249 | if tag.as_u32() & 0xE0E0E0E0 == 0x40606060 { |
250 | Some(Script(tag)) |
251 | } else { |
252 | Some(script::UNKNOWN) |
253 | } |
254 | } |
255 | |
256 | /// Returns script's tag. |
257 | #[inline ] |
258 | pub fn tag(&self) -> Tag { |
259 | self.0 |
260 | } |
261 | } |
262 | |
263 | impl core::str::FromStr for Script { |
264 | type Err = &'static str; |
265 | |
266 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
267 | let tag: Tag = Tag::from_bytes_lossy(s.as_bytes()); |
268 | Script::from_iso15924_tag(tag).ok_or(err:"invalid script" ) |
269 | } |
270 | } |
271 | |
272 | /// Predefined scripts. |
273 | pub mod script { |
274 | #![allow (missing_docs)] |
275 | |
276 | use crate::Script; |
277 | |
278 | // Since 1.1 |
279 | pub const COMMON: Script = Script::from_bytes(b"Zyyy" ); |
280 | pub const INHERITED: Script = Script::from_bytes(b"Zinh" ); |
281 | pub const ARABIC: Script = Script::from_bytes(b"Arab" ); |
282 | pub const ARMENIAN: Script = Script::from_bytes(b"Armn" ); |
283 | pub const BENGALI: Script = Script::from_bytes(b"Beng" ); |
284 | pub const CYRILLIC: Script = Script::from_bytes(b"Cyrl" ); |
285 | pub const DEVANAGARI: Script = Script::from_bytes(b"Deva" ); |
286 | pub const GEORGIAN: Script = Script::from_bytes(b"Geor" ); |
287 | pub const GREEK: Script = Script::from_bytes(b"Grek" ); |
288 | pub const GUJARATI: Script = Script::from_bytes(b"Gujr" ); |
289 | pub const GURMUKHI: Script = Script::from_bytes(b"Guru" ); |
290 | pub const HANGUL: Script = Script::from_bytes(b"Hang" ); |
291 | pub const HAN: Script = Script::from_bytes(b"Hani" ); |
292 | pub const HEBREW: Script = Script::from_bytes(b"Hebr" ); |
293 | pub const HIRAGANA: Script = Script::from_bytes(b"Hira" ); |
294 | pub const KANNADA: Script = Script::from_bytes(b"Knda" ); |
295 | pub const KATAKANA: Script = Script::from_bytes(b"Kana" ); |
296 | pub const LAO: Script = Script::from_bytes(b"Laoo" ); |
297 | pub const LATIN: Script = Script::from_bytes(b"Latn" ); |
298 | pub const MALAYALAM: Script = Script::from_bytes(b"Mlym" ); |
299 | pub const ORIYA: Script = Script::from_bytes(b"Orya" ); |
300 | pub const TAMIL: Script = Script::from_bytes(b"Taml" ); |
301 | pub const TELUGU: Script = Script::from_bytes(b"Telu" ); |
302 | pub const THAI: Script = Script::from_bytes(b"Thai" ); |
303 | // Since 2.0 |
304 | pub const TIBETAN: Script = Script::from_bytes(b"Tibt" ); |
305 | // Since 3.0 |
306 | pub const BOPOMOFO: Script = Script::from_bytes(b"Bopo" ); |
307 | pub const BRAILLE: Script = Script::from_bytes(b"Brai" ); |
308 | pub const CANADIAN_SYLLABICS: Script = Script::from_bytes(b"Cans" ); |
309 | pub const CHEROKEE: Script = Script::from_bytes(b"Cher" ); |
310 | pub const ETHIOPIC: Script = Script::from_bytes(b"Ethi" ); |
311 | pub const KHMER: Script = Script::from_bytes(b"Khmr" ); |
312 | pub const MONGOLIAN: Script = Script::from_bytes(b"Mong" ); |
313 | pub const MYANMAR: Script = Script::from_bytes(b"Mymr" ); |
314 | pub const OGHAM: Script = Script::from_bytes(b"Ogam" ); |
315 | pub const RUNIC: Script = Script::from_bytes(b"Runr" ); |
316 | pub const SINHALA: Script = Script::from_bytes(b"Sinh" ); |
317 | pub const SYRIAC: Script = Script::from_bytes(b"Syrc" ); |
318 | pub const THAANA: Script = Script::from_bytes(b"Thaa" ); |
319 | pub const YI: Script = Script::from_bytes(b"Yiii" ); |
320 | // Since 3.1 |
321 | pub const DESERET: Script = Script::from_bytes(b"Dsrt" ); |
322 | pub const GOTHIC: Script = Script::from_bytes(b"Goth" ); |
323 | pub const OLD_ITALIC: Script = Script::from_bytes(b"Ital" ); |
324 | // Since 3.2 |
325 | pub const BUHID: Script = Script::from_bytes(b"Buhd" ); |
326 | pub const HANUNOO: Script = Script::from_bytes(b"Hano" ); |
327 | pub const TAGALOG: Script = Script::from_bytes(b"Tglg" ); |
328 | pub const TAGBANWA: Script = Script::from_bytes(b"Tagb" ); |
329 | // Since 4.0 |
330 | pub const CYPRIOT: Script = Script::from_bytes(b"Cprt" ); |
331 | pub const LIMBU: Script = Script::from_bytes(b"Limb" ); |
332 | pub const LINEAR_B: Script = Script::from_bytes(b"Linb" ); |
333 | pub const OSMANYA: Script = Script::from_bytes(b"Osma" ); |
334 | pub const SHAVIAN: Script = Script::from_bytes(b"Shaw" ); |
335 | pub const TAI_LE: Script = Script::from_bytes(b"Tale" ); |
336 | pub const UGARITIC: Script = Script::from_bytes(b"Ugar" ); |
337 | // Since 4.1 |
338 | pub const BUGINESE: Script = Script::from_bytes(b"Bugi" ); |
339 | pub const COPTIC: Script = Script::from_bytes(b"Copt" ); |
340 | pub const GLAGOLITIC: Script = Script::from_bytes(b"Glag" ); |
341 | pub const KHAROSHTHI: Script = Script::from_bytes(b"Khar" ); |
342 | pub const NEW_TAI_LUE: Script = Script::from_bytes(b"Talu" ); |
343 | pub const OLD_PERSIAN: Script = Script::from_bytes(b"Xpeo" ); |
344 | pub const SYLOTI_NAGRI: Script = Script::from_bytes(b"Sylo" ); |
345 | pub const TIFINAGH: Script = Script::from_bytes(b"Tfng" ); |
346 | // Since 5.0 |
347 | pub const UNKNOWN: Script = Script::from_bytes(b"Zzzz" ); // Script can be Unknown, but not Invalid. |
348 | pub const BALINESE: Script = Script::from_bytes(b"Bali" ); |
349 | pub const CUNEIFORM: Script = Script::from_bytes(b"Xsux" ); |
350 | pub const NKO: Script = Script::from_bytes(b"Nkoo" ); |
351 | pub const PHAGS_PA: Script = Script::from_bytes(b"Phag" ); |
352 | pub const PHOENICIAN: Script = Script::from_bytes(b"Phnx" ); |
353 | // Since 5.1 |
354 | pub const CARIAN: Script = Script::from_bytes(b"Cari" ); |
355 | pub const CHAM: Script = Script::from_bytes(b"Cham" ); |
356 | pub const KAYAH_LI: Script = Script::from_bytes(b"Kali" ); |
357 | pub const LEPCHA: Script = Script::from_bytes(b"Lepc" ); |
358 | pub const LYCIAN: Script = Script::from_bytes(b"Lyci" ); |
359 | pub const LYDIAN: Script = Script::from_bytes(b"Lydi" ); |
360 | pub const OL_CHIKI: Script = Script::from_bytes(b"Olck" ); |
361 | pub const REJANG: Script = Script::from_bytes(b"Rjng" ); |
362 | pub const SAURASHTRA: Script = Script::from_bytes(b"Saur" ); |
363 | pub const SUNDANESE: Script = Script::from_bytes(b"Sund" ); |
364 | pub const VAI: Script = Script::from_bytes(b"Vaii" ); |
365 | // Since 5.2 |
366 | pub const AVESTAN: Script = Script::from_bytes(b"Avst" ); |
367 | pub const BAMUM: Script = Script::from_bytes(b"Bamu" ); |
368 | pub const EGYPTIAN_HIEROGLYPHS: Script = Script::from_bytes(b"Egyp" ); |
369 | pub const IMPERIAL_ARAMAIC: Script = Script::from_bytes(b"Armi" ); |
370 | pub const INSCRIPTIONAL_PAHLAVI: Script = Script::from_bytes(b"Phli" ); |
371 | pub const INSCRIPTIONAL_PARTHIAN: Script = Script::from_bytes(b"Prti" ); |
372 | pub const JAVANESE: Script = Script::from_bytes(b"Java" ); |
373 | pub const KAITHI: Script = Script::from_bytes(b"Kthi" ); |
374 | pub const LISU: Script = Script::from_bytes(b"Lisu" ); |
375 | pub const MEETEI_MAYEK: Script = Script::from_bytes(b"Mtei" ); |
376 | pub const OLD_SOUTH_ARABIAN: Script = Script::from_bytes(b"Sarb" ); |
377 | pub const OLD_TURKIC: Script = Script::from_bytes(b"Orkh" ); |
378 | pub const SAMARITAN: Script = Script::from_bytes(b"Samr" ); |
379 | pub const TAI_THAM: Script = Script::from_bytes(b"Lana" ); |
380 | pub const TAI_VIET: Script = Script::from_bytes(b"Tavt" ); |
381 | // Since 6.0 |
382 | pub const BATAK: Script = Script::from_bytes(b"Batk" ); |
383 | pub const BRAHMI: Script = Script::from_bytes(b"Brah" ); |
384 | pub const MANDAIC: Script = Script::from_bytes(b"Mand" ); |
385 | // Since 6.1 |
386 | pub const CHAKMA: Script = Script::from_bytes(b"Cakm" ); |
387 | pub const MEROITIC_CURSIVE: Script = Script::from_bytes(b"Merc" ); |
388 | pub const MEROITIC_HIEROGLYPHS: Script = Script::from_bytes(b"Mero" ); |
389 | pub const MIAO: Script = Script::from_bytes(b"Plrd" ); |
390 | pub const SHARADA: Script = Script::from_bytes(b"Shrd" ); |
391 | pub const SORA_SOMPENG: Script = Script::from_bytes(b"Sora" ); |
392 | pub const TAKRI: Script = Script::from_bytes(b"Takr" ); |
393 | // Since 7.0 |
394 | pub const BASSA_VAH: Script = Script::from_bytes(b"Bass" ); |
395 | pub const CAUCASIAN_ALBANIAN: Script = Script::from_bytes(b"Aghb" ); |
396 | pub const DUPLOYAN: Script = Script::from_bytes(b"Dupl" ); |
397 | pub const ELBASAN: Script = Script::from_bytes(b"Elba" ); |
398 | pub const GRANTHA: Script = Script::from_bytes(b"Gran" ); |
399 | pub const KHOJKI: Script = Script::from_bytes(b"Khoj" ); |
400 | pub const KHUDAWADI: Script = Script::from_bytes(b"Sind" ); |
401 | pub const LINEAR_A: Script = Script::from_bytes(b"Lina" ); |
402 | pub const MAHAJANI: Script = Script::from_bytes(b"Mahj" ); |
403 | pub const MANICHAEAN: Script = Script::from_bytes(b"Mani" ); |
404 | pub const MENDE_KIKAKUI: Script = Script::from_bytes(b"Mend" ); |
405 | pub const MODI: Script = Script::from_bytes(b"Modi" ); |
406 | pub const MRO: Script = Script::from_bytes(b"Mroo" ); |
407 | pub const NABATAEAN: Script = Script::from_bytes(b"Nbat" ); |
408 | pub const OLD_NORTH_ARABIAN: Script = Script::from_bytes(b"Narb" ); |
409 | pub const OLD_PERMIC: Script = Script::from_bytes(b"Perm" ); |
410 | pub const PAHAWH_HMONG: Script = Script::from_bytes(b"Hmng" ); |
411 | pub const PALMYRENE: Script = Script::from_bytes(b"Palm" ); |
412 | pub const PAU_CIN_HAU: Script = Script::from_bytes(b"Pauc" ); |
413 | pub const PSALTER_PAHLAVI: Script = Script::from_bytes(b"Phlp" ); |
414 | pub const SIDDHAM: Script = Script::from_bytes(b"Sidd" ); |
415 | pub const TIRHUTA: Script = Script::from_bytes(b"Tirh" ); |
416 | pub const WARANG_CITI: Script = Script::from_bytes(b"Wara" ); |
417 | // Since 8.0 |
418 | pub const AHOM: Script = Script::from_bytes(b"Ahom" ); |
419 | pub const ANATOLIAN_HIEROGLYPHS: Script = Script::from_bytes(b"Hluw" ); |
420 | pub const HATRAN: Script = Script::from_bytes(b"Hatr" ); |
421 | pub const MULTANI: Script = Script::from_bytes(b"Mult" ); |
422 | pub const OLD_HUNGARIAN: Script = Script::from_bytes(b"Hung" ); |
423 | pub const SIGNWRITING: Script = Script::from_bytes(b"Sgnw" ); |
424 | // Since 9.0 |
425 | pub const ADLAM: Script = Script::from_bytes(b"Adlm" ); |
426 | pub const BHAIKSUKI: Script = Script::from_bytes(b"Bhks" ); |
427 | pub const MARCHEN: Script = Script::from_bytes(b"Marc" ); |
428 | pub const OSAGE: Script = Script::from_bytes(b"Osge" ); |
429 | pub const TANGUT: Script = Script::from_bytes(b"Tang" ); |
430 | pub const NEWA: Script = Script::from_bytes(b"Newa" ); |
431 | // Since 10.0 |
432 | pub const MASARAM_GONDI: Script = Script::from_bytes(b"Gonm" ); |
433 | pub const NUSHU: Script = Script::from_bytes(b"Nshu" ); |
434 | pub const SOYOMBO: Script = Script::from_bytes(b"Soyo" ); |
435 | pub const ZANABAZAR_SQUARE: Script = Script::from_bytes(b"Zanb" ); |
436 | // Since 11.0 |
437 | pub const DOGRA: Script = Script::from_bytes(b"Dogr" ); |
438 | pub const GUNJALA_GONDI: Script = Script::from_bytes(b"Gong" ); |
439 | pub const HANIFI_ROHINGYA: Script = Script::from_bytes(b"Rohg" ); |
440 | pub const MAKASAR: Script = Script::from_bytes(b"Maka" ); |
441 | pub const MEDEFAIDRIN: Script = Script::from_bytes(b"Medf" ); |
442 | pub const OLD_SOGDIAN: Script = Script::from_bytes(b"Sogo" ); |
443 | pub const SOGDIAN: Script = Script::from_bytes(b"Sogd" ); |
444 | // Since 12.0 |
445 | pub const ELYMAIC: Script = Script::from_bytes(b"Elym" ); |
446 | pub const NANDINAGARI: Script = Script::from_bytes(b"Nand" ); |
447 | pub const NYIAKENG_PUACHUE_HMONG: Script = Script::from_bytes(b"Hmnp" ); |
448 | pub const WANCHO: Script = Script::from_bytes(b"Wcho" ); |
449 | // Since 13.0 |
450 | pub const CHORASMIAN: Script = Script::from_bytes(b"Chrs" ); |
451 | pub const DIVES_AKURU: Script = Script::from_bytes(b"Diak" ); |
452 | pub const KHITAN_SMALL_SCRIPT: Script = Script::from_bytes(b"Kits" ); |
453 | pub const YEZIDI: Script = Script::from_bytes(b"Yezi" ); |
454 | // Since 14.0 |
455 | pub const CYPRO_MINOAN: Script = Script::from_bytes(b"Cpmn" ); |
456 | pub const OLD_UYGHUR: Script = Script::from_bytes(b"Ougr" ); |
457 | pub const TANGSA: Script = Script::from_bytes(b"Tnsa" ); |
458 | pub const TOTO: Script = Script::from_bytes(b"Toto" ); |
459 | pub const VITHKUQI: Script = Script::from_bytes(b"Vith" ); |
460 | // Since 15.0 |
461 | pub const KAWI: Script = Script::from_bytes(b"Kawi" ); |
462 | pub const NAG_MUNDARI: Script = Script::from_bytes(b"Nagm" ); |
463 | // Since 16.0 |
464 | pub const GARAY: Script = Script::from_bytes(b"Gara" ); |
465 | pub const GURUNG_KHEMA: Script = Script::from_bytes(b"Gukh" ); |
466 | pub const KIRAT_RAI: Script = Script::from_bytes(b"Krai" ); |
467 | pub const OL_ONAL: Script = Script::from_bytes(b"Onao" ); |
468 | pub const SUNUWAR: Script = Script::from_bytes(b"Sunu" ); |
469 | pub const TODHRI: Script = Script::from_bytes(b"Todr" ); |
470 | pub const TULU_TIGALARI: Script = Script::from_bytes(b"Tutg" ); |
471 | |
472 | pub const SCRIPT_MATH: Script = Script::from_bytes(b"Zmth" ); |
473 | |
474 | // https://github.com/harfbuzz/harfbuzz/issues/1162 |
475 | pub const MYANMAR_ZAWGYI: Script = Script::from_bytes(b"Qaag" ); |
476 | } |
477 | |
478 | /// A feature tag with an accompanying range specifying on which subslice of |
479 | /// `shape`s input it should be applied. |
480 | #[repr (C)] |
481 | #[allow (missing_docs)] |
482 | #[derive (Clone, Copy, PartialEq, Hash, Debug)] |
483 | pub struct Feature { |
484 | pub tag: Tag, |
485 | pub value: u32, |
486 | pub start: u32, |
487 | pub end: u32, |
488 | } |
489 | |
490 | impl Feature { |
491 | /// Create a new `Feature` struct. |
492 | pub fn new(tag: Tag, value: u32, range: impl RangeBounds<usize>) -> Feature { |
493 | let max = u32::MAX as usize; |
494 | let start = match range.start_bound() { |
495 | Bound::Included(&included) => included.min(max) as u32, |
496 | Bound::Excluded(&excluded) => excluded.min(max - 1) as u32 + 1, |
497 | Bound::Unbounded => 0, |
498 | }; |
499 | let end = match range.end_bound() { |
500 | Bound::Included(&included) => included.min(max) as u32, |
501 | Bound::Excluded(&excluded) => excluded.saturating_sub(1).min(max) as u32, |
502 | Bound::Unbounded => max as u32, |
503 | }; |
504 | |
505 | Feature { |
506 | tag, |
507 | value, |
508 | start, |
509 | end, |
510 | } |
511 | } |
512 | |
513 | pub(crate) fn is_global(&self) -> bool { |
514 | self.start == 0 && self.end == u32::MAX |
515 | } |
516 | } |
517 | |
518 | impl core::str::FromStr for Feature { |
519 | type Err = &'static str; |
520 | |
521 | /// Parses a `Feature` form a string. |
522 | /// |
523 | /// Possible values: |
524 | /// |
525 | /// - `kern` -> kern .. 1 |
526 | /// - `+kern` -> kern .. 1 |
527 | /// - `-kern` -> kern .. 0 |
528 | /// - `kern=0` -> kern .. 0 |
529 | /// - `kern=1` -> kern .. 1 |
530 | /// - `aalt=2` -> altr .. 2 |
531 | /// - `kern[]` -> kern .. 1 |
532 | /// - `kern[:]` -> kern .. 1 |
533 | /// - `kern[5:]` -> kern 5.. 1 |
534 | /// - `kern[:5]` -> kern ..=5 1 |
535 | /// - `kern[3:5]` -> kern 3..=5 1 |
536 | /// - `kern[3]` -> kern 3..=4 1 |
537 | /// - `aalt[3:5]=2` -> kern 3..=5 1 |
538 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
539 | fn parse(s: &str) -> Option<Feature> { |
540 | if s.is_empty() { |
541 | return None; |
542 | } |
543 | |
544 | let mut p = TextParser::new(s); |
545 | |
546 | // Parse prefix. |
547 | let mut value = 1; |
548 | match p.curr_byte()? { |
549 | b'-' => { |
550 | value = 0; |
551 | p.advance(1); |
552 | } |
553 | b'+' => { |
554 | value = 1; |
555 | p.advance(1); |
556 | } |
557 | _ => {} |
558 | } |
559 | |
560 | // Parse tag. |
561 | p.skip_spaces(); |
562 | let quote = p.consume_quote(); |
563 | |
564 | let tag = p.consume_tag()?; |
565 | |
566 | // Force closing quote. |
567 | if let Some(quote) = quote { |
568 | p.consume_byte(quote)?; |
569 | } |
570 | |
571 | // Parse indices. |
572 | p.skip_spaces(); |
573 | |
574 | let (start, end) = if p.consume_byte(b'[' ).is_some() { |
575 | let start_opt = p.consume_i32(); |
576 | let start = start_opt.unwrap_or(0) as u32; // negative value overflow is ok |
577 | |
578 | let end = if matches!(p.curr_byte(), Some(b':' ) | Some(b';' )) { |
579 | p.advance(1); |
580 | p.consume_i32().unwrap_or(-1) as u32 // negative value overflow is ok |
581 | } else { |
582 | if start_opt.is_some() && start != u32::MAX { |
583 | start + 1 |
584 | } else { |
585 | u32::MAX |
586 | } |
587 | }; |
588 | |
589 | p.consume_byte(b']' )?; |
590 | |
591 | (start, end) |
592 | } else { |
593 | (0, u32::MAX) |
594 | }; |
595 | |
596 | // Parse postfix. |
597 | let had_equal = p.consume_byte(b'=' ).is_some(); |
598 | let value1 = p |
599 | .consume_i32() |
600 | .or_else(|| p.consume_bool().map(|b| b as i32)); |
601 | |
602 | if had_equal && value1.is_none() { |
603 | return None; |
604 | }; |
605 | |
606 | if let Some(value1) = value1 { |
607 | value = value1 as u32; // negative value overflow is ok |
608 | } |
609 | |
610 | p.skip_spaces(); |
611 | |
612 | if !p.at_end() { |
613 | return None; |
614 | } |
615 | |
616 | Some(Feature { |
617 | tag, |
618 | value, |
619 | start, |
620 | end, |
621 | }) |
622 | } |
623 | |
624 | parse(s).ok_or("invalid feature" ) |
625 | } |
626 | } |
627 | |
628 | #[cfg (test)] |
629 | mod tests_features { |
630 | use super::*; |
631 | use core::str::FromStr; |
632 | |
633 | macro_rules! test { |
634 | ($name:ident, $text:expr, $tag:expr, $value:expr, $range:expr) => { |
635 | #[test] |
636 | fn $name() { |
637 | assert_eq!( |
638 | Feature::from_str($text).unwrap(), |
639 | Feature::new(Tag::from_bytes($tag), $value, $range) |
640 | ); |
641 | } |
642 | }; |
643 | } |
644 | |
645 | test !(parse_01, "kern" , b"kern" , 1, ..); |
646 | test !(parse_02, "+kern" , b"kern" , 1, ..); |
647 | test !(parse_03, "-kern" , b"kern" , 0, ..); |
648 | test !(parse_04, "kern=0" , b"kern" , 0, ..); |
649 | test !(parse_05, "kern=1" , b"kern" , 1, ..); |
650 | test !(parse_06, "kern=2" , b"kern" , 2, ..); |
651 | test !(parse_07, "kern[]" , b"kern" , 1, ..); |
652 | test !(parse_08, "kern[:]" , b"kern" , 1, ..); |
653 | test !(parse_09, "kern[5:]" , b"kern" , 1, 5..); |
654 | test !(parse_10, "kern[:5]" , b"kern" , 1, ..=5); |
655 | test !(parse_11, "kern[3:5]" , b"kern" , 1, 3..=5); |
656 | test !(parse_12, "kern[3]" , b"kern" , 1, 3..=4); |
657 | test !(parse_13, "kern[3:5]=2" , b"kern" , 2, 3..=5); |
658 | test !(parse_14, "kern[3;5]=2" , b"kern" , 2, 3..=5); |
659 | test !(parse_15, "kern[:-1]" , b"kern" , 1, ..); |
660 | test !(parse_16, "kern[-1]" , b"kern" , 1, u32::MAX as usize..); |
661 | test !(parse_17, "kern=on" , b"kern" , 1, ..); |
662 | test !(parse_18, "kern=off" , b"kern" , 0, ..); |
663 | test !(parse_19, "kern=oN" , b"kern" , 1, ..); |
664 | test !(parse_20, "kern=oFf" , b"kern" , 0, ..); |
665 | } |
666 | |
667 | /// A font variation. |
668 | #[repr (C)] |
669 | #[allow (missing_docs)] |
670 | #[derive (Clone, Copy, PartialEq, Debug)] |
671 | pub struct Variation { |
672 | pub tag: Tag, |
673 | pub value: f32, |
674 | } |
675 | |
676 | impl core::str::FromStr for Variation { |
677 | type Err = &'static str; |
678 | |
679 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
680 | fn parse(s: &str) -> Option<Variation> { |
681 | if s.is_empty() { |
682 | return None; |
683 | } |
684 | |
685 | let mut p = TextParser::new(s); |
686 | |
687 | // Parse tag. |
688 | p.skip_spaces(); |
689 | let quote = p.consume_quote(); |
690 | |
691 | let tag = p.consume_tag()?; |
692 | |
693 | // Force closing quote. |
694 | if let Some(quote) = quote { |
695 | p.consume_byte(quote)?; |
696 | } |
697 | |
698 | let _ = p.consume_byte(b'=' ); |
699 | let value = p.consume_f32()?; |
700 | p.skip_spaces(); |
701 | |
702 | if !p.at_end() { |
703 | return None; |
704 | } |
705 | |
706 | Some(Variation { tag, value }) |
707 | } |
708 | |
709 | parse(s).ok_or("invalid variation" ) |
710 | } |
711 | } |
712 | |
713 | pub trait TagExt { |
714 | fn default_script() -> Self; |
715 | fn default_language() -> Self; |
716 | #[cfg (test)] |
717 | fn to_lowercase(&self) -> Self; |
718 | fn to_uppercase(&self) -> Self; |
719 | } |
720 | |
721 | impl TagExt for Tag { |
722 | #[inline ] |
723 | fn default_script() -> Self { |
724 | Tag::from_bytes(b"DFLT" ) |
725 | } |
726 | |
727 | #[inline ] |
728 | fn default_language() -> Self { |
729 | Tag::from_bytes(b"dflt" ) |
730 | } |
731 | |
732 | /// Converts tag to lowercase. |
733 | #[cfg (test)] |
734 | #[inline ] |
735 | fn to_lowercase(&self) -> Self { |
736 | let b = self.to_bytes(); |
737 | Tag::from_bytes(&[ |
738 | b[0].to_ascii_lowercase(), |
739 | b[1].to_ascii_lowercase(), |
740 | b[2].to_ascii_lowercase(), |
741 | b[3].to_ascii_lowercase(), |
742 | ]) |
743 | } |
744 | |
745 | /// Converts tag to uppercase. |
746 | #[inline ] |
747 | fn to_uppercase(&self) -> Self { |
748 | let b = self.to_bytes(); |
749 | Tag::from_bytes(&[ |
750 | b[0].to_ascii_uppercase(), |
751 | b[1].to_ascii_uppercase(), |
752 | b[2].to_ascii_uppercase(), |
753 | b[3].to_ascii_uppercase(), |
754 | ]) |
755 | } |
756 | } |
757 | |