| 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 | |