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