blitz-pdf 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +23 -6
  2. package/THIRD_PARTY_NOTICES.md +18 -0
  3. package/dist/types.d.ts +2 -1
  4. package/dist/types.d.ts.map +1 -1
  5. package/licenses/Noto-OFL.txt +94 -0
  6. package/licenses/NotoCJK-OFL.txt +92 -0
  7. package/licenses/NotoEmoji-OFL.txt +93 -0
  8. package/licenses/Unifont-OFL.txt +90 -0
  9. package/licenses/font-sources.json +106 -0
  10. package/package.json +1 -1
  11. package/src/types.ts +2 -1
  12. package/vendor/README.md +19 -0
  13. package/vendor/fontique/Cargo.toml +194 -0
  14. package/vendor/fontique/LICENSE-APACHE +201 -0
  15. package/vendor/fontique/LICENSE-MIT +25 -0
  16. package/vendor/fontique/README.md +54 -0
  17. package/vendor/fontique/examples/generic_families.rs +37 -0
  18. package/vendor/fontique/src/attributes.rs +42 -0
  19. package/vendor/fontique/src/backend/android.rs +218 -0
  20. package/vendor/fontique/src/backend/coretext.rs +206 -0
  21. package/vendor/fontique/src/backend/dwrite.rs +395 -0
  22. package/vendor/fontique/src/backend/fontconfig.rs +844 -0
  23. package/vendor/fontique/src/backend/mod.rs +75 -0
  24. package/vendor/fontique/src/charmap.rs +127 -0
  25. package/vendor/fontique/src/collection/mod.rs +1016 -0
  26. package/vendor/fontique/src/collection/query.rs +392 -0
  27. package/vendor/fontique/src/fallback.rs +293 -0
  28. package/vendor/fontique/src/family.rs +111 -0
  29. package/vendor/fontique/src/family_name.rs +153 -0
  30. package/vendor/fontique/src/font.rs +562 -0
  31. package/vendor/fontique/src/generic.rs +36 -0
  32. package/vendor/fontique/src/impl_fontconfig.rs +159 -0
  33. package/vendor/fontique/src/lib.rs +87 -0
  34. package/vendor/fontique/src/matching.rs +483 -0
  35. package/vendor/fontique/src/scan.rs +312 -0
  36. package/vendor/fontique/src/script.rs +271 -0
  37. package/vendor/fontique/src/source.rs +108 -0
  38. package/vendor/fontique/src/source_cache.rs +254 -0
  39. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,392 @@
1
+ // Copyright 2024 the Parley Authors
2
+ // SPDX-License-Identifier: Apache-2.0 OR MIT
3
+
4
+ //! Query support.
5
+
6
+ use crate::{Charmap, CharmapIndex};
7
+
8
+ use super::super::{Collection, SourceCache, matching::match_fonts};
9
+
10
+ use alloc::vec::Vec;
11
+ use parlance::Script;
12
+ use smallvec::SmallVec;
13
+
14
+ use super::{
15
+ super::{Attributes, Blob, FallbackKey, FamilyId, FamilyInfo, GenericFamily, Synthesis},
16
+ Inner,
17
+ };
18
+
19
+ #[derive(Clone, Default)]
20
+ pub(super) struct QueryState {
21
+ families: Vec<CachedFamily>,
22
+ fallback_families: Vec<CachedFamily>,
23
+ registered_families: Vec<CachedFamily>,
24
+ }
25
+
26
+ impl QueryState {
27
+ fn clear(&mut self) {
28
+ self.families.clear();
29
+ self.fallback_families.clear();
30
+ self.registered_families.clear();
31
+ }
32
+ }
33
+
34
+ /// State for font selection.
35
+ ///
36
+ /// Instances of this can be obtained from [`Collection::query`].
37
+ pub struct Query<'a> {
38
+ collection: &'a mut Inner,
39
+ state: &'a mut QueryState,
40
+ source_cache: &'a mut SourceCache,
41
+ attributes: Attributes,
42
+ fallbacks: Option<FallbackKey>,
43
+ }
44
+
45
+ impl<'a> Query<'a> {
46
+ pub(super) fn new(collection: &'a mut Collection, source_cache: &'a mut SourceCache) -> Self {
47
+ collection.query_state.clear();
48
+ collection.query_state.registered_families.extend(
49
+ collection
50
+ .inner
51
+ .registered_fallbacks()
52
+ .into_iter()
53
+ .map(CachedFamily::new),
54
+ );
55
+ Self {
56
+ collection: &mut collection.inner,
57
+ state: &mut collection.query_state,
58
+ source_cache,
59
+ attributes: Attributes::default(),
60
+ fallbacks: None,
61
+ }
62
+ }
63
+
64
+ /// Sets the ordered sequence of families to match against.
65
+ pub fn set_families<'f, I>(&mut self, families: I)
66
+ where
67
+ I: IntoIterator,
68
+ I::Item: Into<QueryFamily<'f>>,
69
+ {
70
+ self.state.families.clear();
71
+ for family in families {
72
+ let family = family.into();
73
+ match family {
74
+ QueryFamily::Named(name) => {
75
+ if let Some(id) = self.collection.family_id(name) {
76
+ self.state.families.push(CachedFamily::new(id));
77
+ }
78
+ }
79
+ QueryFamily::Id(id) => {
80
+ self.state.families.push(CachedFamily::new(id));
81
+ }
82
+ QueryFamily::Generic(generic) => {
83
+ for id in self.collection.generic_families(generic) {
84
+ self.state.families.push(CachedFamily::new(id));
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ /// Sets the primary attributes to match against.
92
+ pub fn set_attributes(&mut self, attributes: Attributes) {
93
+ if self.attributes != attributes {
94
+ for family in &mut self.state.families {
95
+ family.clear_fonts();
96
+ }
97
+ for family in self
98
+ .state
99
+ .fallback_families
100
+ .iter_mut()
101
+ .chain(&mut self.state.registered_families)
102
+ {
103
+ family.clear_fonts();
104
+ }
105
+ self.attributes = attributes;
106
+ }
107
+ }
108
+
109
+ /// Sets the script and locale for fallback fonts.
110
+ pub fn set_fallbacks(&mut self, key: impl Into<FallbackKey>) {
111
+ let key = key.into();
112
+ if self.fallbacks != Some(key) {
113
+ self.state.fallback_families.clear();
114
+ self.state.fallback_families.extend(
115
+ self.collection
116
+ .fallback_families(key)
117
+ .map(CachedFamily::new),
118
+ );
119
+ // HACK: always add a Han font to the fallback list to capture
120
+ // punctuation in the common script
121
+ // See <https://github.com/linebender/parley/issues/597>
122
+ self.state.fallback_families.extend(
123
+ self.collection
124
+ .fallback_families(FallbackKey::new(Script::from_bytes(*b"Hani"), None))
125
+ .map(CachedFamily::new),
126
+ );
127
+ self.fallbacks = Some(key);
128
+ }
129
+ }
130
+
131
+ /// Invokes the given callback with all fonts that match the current
132
+ /// settings.
133
+ ///
134
+ /// Return [`QueryStatus::Stop`] to end iterating over the matching
135
+ /// fonts or [`QueryStatus::Continue`] to continue iterating.
136
+ pub fn matches_with<'q>(&'q mut self, mut f: impl FnMut(&'q QueryFont) -> QueryStatus) {
137
+ for family in self
138
+ .state
139
+ .families
140
+ .iter_mut()
141
+ .chain(self.state.fallback_families.iter_mut())
142
+ .chain(self.state.registered_families.iter_mut())
143
+ {
144
+ match &mut family.family {
145
+ Entry::Error => continue,
146
+ Entry::Ok(..) => {}
147
+ status @ Entry::Vacant => {
148
+ if let Some(info) = self.collection.family(family.id) {
149
+ *status = Entry::Ok(info);
150
+ } else {
151
+ *status = Entry::Error;
152
+ continue;
153
+ }
154
+ }
155
+ }
156
+ let Entry::Ok(family_info) = &family.family else {
157
+ continue;
158
+ };
159
+ let bucket = load_bucket(
160
+ family_info,
161
+ self.attributes,
162
+ &mut family.best,
163
+ self.source_cache,
164
+ );
165
+ let mut yielded_default = false;
166
+ for font in bucket {
167
+ if font.family.1 == family_info.default_font_index() {
168
+ yielded_default = true;
169
+ }
170
+ if f(font) == QueryStatus::Stop {
171
+ return;
172
+ }
173
+ }
174
+ if yielded_default {
175
+ continue;
176
+ }
177
+ if let Some(font) = load_font(
178
+ family_info,
179
+ self.attributes,
180
+ &mut family.default,
181
+ self.source_cache,
182
+ ) && f(font) == QueryStatus::Stop
183
+ {
184
+ return;
185
+ }
186
+ }
187
+ }
188
+
189
+ /// Returns the [first available font] for the current settings: the first
190
+ /// font that [`matches_with`](Self::matches_with) would yield, if any.
191
+ ///
192
+ /// CSS additionally requires the font's `unicode-range` to include U+0020
193
+ /// SPACE; fontique has no `unicode-range`, so every available font qualifies.
194
+ ///
195
+ /// [first available font]: https://drafts.csswg.org/css-fonts-4/#first-available-font
196
+ pub fn first_available_font(&mut self) -> Option<QueryFont> {
197
+ let mut found = None;
198
+ self.matches_with(|font| {
199
+ found = Some(font.clone());
200
+ QueryStatus::Stop
201
+ });
202
+ found
203
+ }
204
+ }
205
+
206
+ impl Drop for Query<'_> {
207
+ fn drop(&mut self) {
208
+ self.state.clear();
209
+ }
210
+ }
211
+
212
+ /// Determines whether a font query operation will continue.
213
+ ///
214
+ /// See [`Query::matches_with`].
215
+ #[derive(Copy, Clone, PartialEq, Eq, Debug)]
216
+ pub enum QueryStatus {
217
+ /// Query should continue with the next font.
218
+ Continue,
219
+ /// Query should stop.
220
+ Stop,
221
+ }
222
+
223
+ /// Family descriptor for a font query.
224
+ ///
225
+ /// This allows [`Query::set_families`] to
226
+ /// take a variety of family types.
227
+ #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
228
+ pub enum QueryFamily<'a> {
229
+ /// A named font family.
230
+ Named(&'a str),
231
+ /// An identifier for a font family.
232
+ Id(FamilyId),
233
+ /// A generic font family.
234
+ Generic(GenericFamily),
235
+ }
236
+
237
+ impl<'a> From<&'a str> for QueryFamily<'a> {
238
+ fn from(value: &'a str) -> Self {
239
+ Self::Named(value)
240
+ }
241
+ }
242
+
243
+ impl From<FamilyId> for QueryFamily<'static> {
244
+ fn from(value: FamilyId) -> Self {
245
+ Self::Id(value)
246
+ }
247
+ }
248
+
249
+ impl From<GenericFamily> for QueryFamily<'static> {
250
+ fn from(value: GenericFamily) -> Self {
251
+ Self::Generic(value)
252
+ }
253
+ }
254
+
255
+ /// Candidate font generated by a [`Query`].
256
+ #[derive(Clone, Debug)]
257
+ pub struct QueryFont {
258
+ /// Family identifier and index of the font in the family font list.
259
+ pub family: (FamilyId, usize),
260
+ /// Blob containing the font data.
261
+ pub blob: Blob<u8>,
262
+ /// Index of a font in a font collection (`ttc`) file.
263
+ pub index: u32,
264
+ /// Synthesis suggestions for this font based on the requested attributes.
265
+ pub synthesis: Synthesis,
266
+ /// Data used for constructing a character map for this font.
267
+ pub charmap_index: CharmapIndex,
268
+ }
269
+
270
+ impl QueryFont {
271
+ /// Attempts to construct a [Charmap] for this font.
272
+ pub fn charmap(&self) -> Option<Charmap<'_>> {
273
+ self.charmap_index.charmap(self.blob.as_ref())
274
+ }
275
+ }
276
+
277
+ /// Loads every font in `family` matching `attributes` (all coverage variants of the best match).
278
+ fn load_bucket<'a>(
279
+ family: &FamilyInfo,
280
+ attributes: Attributes,
281
+ bucket: &'a mut Entry<SmallVec<[QueryFont; 1]>>,
282
+ source_cache: &mut SourceCache,
283
+ ) -> &'a [QueryFont] {
284
+ match bucket {
285
+ Entry::Error => &[],
286
+ Entry::Ok(fonts) => fonts,
287
+ status @ Entry::Vacant => {
288
+ *status = Entry::Error;
289
+ let indices = match_fonts(
290
+ family.fonts(),
291
+ attributes.width,
292
+ attributes.style,
293
+ attributes.weight,
294
+ true,
295
+ );
296
+ let mut fonts: SmallVec<[QueryFont; 1]> = SmallVec::new();
297
+ for index in indices {
298
+ let Some(font_info) = family.fonts().get(index) else {
299
+ continue;
300
+ };
301
+ let Some(blob) = font_info.load(Some(source_cache)) else {
302
+ continue;
303
+ };
304
+ fonts.push(QueryFont {
305
+ family: (family.id(), index),
306
+ blob: blob.clone(),
307
+ index: font_info.index(),
308
+ synthesis: font_info.synthesis(
309
+ attributes.width,
310
+ attributes.style,
311
+ attributes.weight,
312
+ ),
313
+ charmap_index: font_info.charmap_index(),
314
+ });
315
+ }
316
+ if fonts.is_empty() {
317
+ return &[];
318
+ }
319
+ *status = Entry::Ok(fonts);
320
+ let Entry::Ok(fonts) = status else {
321
+ unreachable!()
322
+ };
323
+ fonts
324
+ }
325
+ }
326
+ }
327
+
328
+ fn load_font<'a>(
329
+ family: &FamilyInfo,
330
+ attributes: Attributes,
331
+ font: &'a mut Entry<QueryFont>,
332
+ source_cache: &mut SourceCache,
333
+ ) -> Option<&'a QueryFont> {
334
+ match font {
335
+ Entry::Error => None,
336
+ Entry::Ok(font) => Some(font),
337
+ status @ Entry::Vacant => {
338
+ // Set to error in case we fail. This simplifies
339
+ // the following code.
340
+ *status = Entry::Error;
341
+ let family_index = family.default_font_index();
342
+ let font_info = family.fonts().get(family_index)?;
343
+ let blob = font_info.load(Some(source_cache))?;
344
+ let blob_index = font_info.index();
345
+ let synthesis =
346
+ font_info.synthesis(attributes.width, attributes.style, attributes.weight);
347
+ *status = Entry::Ok(QueryFont {
348
+ family: (family.id(), family_index),
349
+ blob,
350
+ index: blob_index,
351
+ synthesis,
352
+ charmap_index: font_info.charmap_index(),
353
+ });
354
+ if let Entry::Ok(font) = status {
355
+ Some(font)
356
+ } else {
357
+ None
358
+ }
359
+ }
360
+ }
361
+ }
362
+
363
+ #[derive(Clone)]
364
+ struct CachedFamily {
365
+ id: FamilyId,
366
+ family: Entry<FamilyInfo>,
367
+ best: Entry<SmallVec<[QueryFont; 1]>>,
368
+ default: Entry<QueryFont>,
369
+ }
370
+
371
+ impl CachedFamily {
372
+ fn new(id: FamilyId) -> Self {
373
+ Self {
374
+ id,
375
+ family: Entry::Vacant,
376
+ best: Entry::Vacant,
377
+ default: Entry::Vacant,
378
+ }
379
+ }
380
+
381
+ fn clear_fonts(&mut self) {
382
+ self.best = Entry::Vacant;
383
+ self.default = Entry::Vacant;
384
+ }
385
+ }
386
+
387
+ #[derive(Clone)]
388
+ enum Entry<T> {
389
+ Ok(T),
390
+ Vacant,
391
+ Error,
392
+ }
@@ -0,0 +1,293 @@
1
+ // Copyright 2024 the Parley Authors
2
+ // SPDX-License-Identifier: Apache-2.0 OR MIT
3
+
4
+ //! Support for script/language based font fallback.
5
+
6
+ use super::{Language, Script, family::FamilyId};
7
+ use alloc::vec::Vec;
8
+ use hashbrown::HashMap;
9
+ use smallvec::SmallVec;
10
+
11
+ type FamilyList = SmallVec<[FamilyId; 1]>;
12
+
13
+ /// Maps script and language pairs to font families.
14
+ #[derive(Clone, Default, Debug)]
15
+ pub struct FallbackMap {
16
+ fallbacks: HashMap<Script, PerScript>,
17
+ }
18
+
19
+ impl FallbackMap {
20
+ /// Returns the font fallback families for the given key.
21
+ pub fn get(&self, key: impl Into<FallbackKey>) -> Option<&[FamilyId]> {
22
+ let key = key.into();
23
+ let entry = self.fallbacks.get(&key.script)?;
24
+ if key.is_default() {
25
+ Some(entry.default.as_ref()?.as_slice())
26
+ } else {
27
+ for other in &entry.others {
28
+ if key.locale() == Some(other.0) {
29
+ return Some(other.1.as_slice());
30
+ }
31
+ }
32
+ None
33
+ }
34
+ }
35
+
36
+ /// Inserts or replaces the fallback families for the given script and
37
+ /// language.
38
+ ///
39
+ /// Returns `false` if we don't track that particular pair of script and
40
+ /// language.
41
+ pub fn set(
42
+ &mut self,
43
+ key: impl Into<FallbackKey>,
44
+ families: impl Iterator<Item = FamilyId>,
45
+ ) -> bool {
46
+ self.set_or_append(key, families, true)
47
+ }
48
+
49
+ /// Inserts or appends the fallback families for the given script and
50
+ /// language.
51
+ ///
52
+ /// Returns `false` if we don't track that particular pair of script and
53
+ /// language.
54
+ pub fn append(
55
+ &mut self,
56
+ key: impl Into<FallbackKey>,
57
+ families: impl Iterator<Item = FamilyId>,
58
+ ) -> bool {
59
+ self.set_or_append(key, families, false)
60
+ }
61
+
62
+ fn set_or_append(
63
+ &mut self,
64
+ key: impl Into<FallbackKey>,
65
+ families: impl Iterator<Item = FamilyId>,
66
+ do_set: bool,
67
+ ) -> bool {
68
+ let key = key.into();
69
+ let script = key.script;
70
+ if key.is_tracked() {
71
+ if key.is_default() {
72
+ let existing_families = self
73
+ .fallbacks
74
+ .entry(script)
75
+ .or_default()
76
+ .default
77
+ .get_or_insert(SmallVec::default());
78
+ if do_set {
79
+ existing_families.clear();
80
+ }
81
+ existing_families.extend(families);
82
+ true
83
+ } else {
84
+ let locale = key
85
+ .locale
86
+ .expect("non-default fallback keys must have a locale");
87
+ let script_fallbacks = self.fallbacks.entry(script).or_default();
88
+ if let Some(existing_families) = script_fallbacks
89
+ .others
90
+ .iter_mut()
91
+ .find(|x| x.0 == locale)
92
+ .map(|x| &mut x.1)
93
+ {
94
+ if do_set {
95
+ existing_families.clear();
96
+ }
97
+ existing_families.extend(families);
98
+ } else {
99
+ script_fallbacks.others.push((locale, families.collect()));
100
+ }
101
+ true
102
+ }
103
+ } else {
104
+ false
105
+ }
106
+ }
107
+ }
108
+
109
+ /// Describes a selector for fallback families.
110
+ ///
111
+ /// This is a [`Script`] and optionally, a `locale`, represented as a [`Language`].
112
+ ///
113
+ /// It can be constructed directly via [`FallbackKey::new`] or any of
114
+ /// a variety of `From` implementations to improve the ease of use.
115
+ #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
116
+ pub struct FallbackKey {
117
+ script: Script,
118
+ locale: Option<Language>,
119
+ is_default: bool,
120
+ is_tracked: bool,
121
+ }
122
+
123
+ impl FallbackKey {
124
+ /// Creates a new fallback key from the given script and locale.
125
+ pub fn new(script: impl Into<Script>, locale: Option<&Language>) -> Self {
126
+ let script = script.into();
127
+ let (locale, is_default, is_tracked) = match canonical_locale(script, locale) {
128
+ Some((is_default, locale)) => (locale, is_default, true),
129
+ None => (None, true, false),
130
+ };
131
+ Self {
132
+ script,
133
+ locale,
134
+ is_default,
135
+ is_tracked,
136
+ }
137
+ }
138
+
139
+ /// Returns the requested script.
140
+ pub fn script(&self) -> Script {
141
+ self.script
142
+ }
143
+
144
+ /// Returns a normalized version of the requested locale.
145
+ pub fn locale(&self) -> Option<Language> {
146
+ self.locale
147
+ }
148
+
149
+ /// Returns the requested locale as a string, if present.
150
+ pub fn locale_str(&self) -> Option<&str> {
151
+ self.locale.as_ref().map(Language::as_str)
152
+ }
153
+
154
+ /// Returns `true` if the requested locale is considered the "default"
155
+ /// language/region for the requested script.
156
+ ///
157
+ /// Always returns `true` when [`locale`](Self::locale) returns `None`.
158
+ pub fn is_default(&self) -> bool {
159
+ self.is_default
160
+ }
161
+
162
+ /// Returns `true` if the requested script and locale pair are actually
163
+ /// tracked for fallback.
164
+ pub fn is_tracked(&self) -> bool {
165
+ self.is_tracked
166
+ }
167
+ }
168
+
169
+ impl<S> From<(S, &str)> for FallbackKey
170
+ where
171
+ S: Into<Script>,
172
+ {
173
+ fn from(value: (S, &str)) -> Self {
174
+ let locale = Language::parse(value.1).ok();
175
+ Self::new(value.0, locale.as_ref())
176
+ }
177
+ }
178
+
179
+ impl<S> From<(S, &Language)> for FallbackKey
180
+ where
181
+ S: Into<Script>,
182
+ {
183
+ fn from(value: (S, &Language)) -> Self {
184
+ Self::new(value.0, Some(value.1))
185
+ }
186
+ }
187
+
188
+ impl From<Script> for FallbackKey {
189
+ fn from(value: Script) -> Self {
190
+ Self::new(value, None)
191
+ }
192
+ }
193
+
194
+ #[derive(Clone, Default, Debug)]
195
+ struct PerScript {
196
+ default: Option<FamilyList>,
197
+ others: Vec<(Language, FamilyList)>,
198
+ }
199
+
200
+ fn canonical_locale(script: Script, locale: Option<&Language>) -> Option<(bool, Option<Language>)> {
201
+ let Some(locale) = locale else {
202
+ return Some((true, None));
203
+ };
204
+ let lang = locale.language();
205
+ let region = locale.region().unwrap_or_default();
206
+ let (is_default, token) = match &script.to_bytes() {
207
+ b"Arab" => match (lang, region) {
208
+ ("ar", "") => (true, "ar"),
209
+ ("ar", "IR") => (false, "ar-IR"),
210
+ ("fa", "") => (false, "fa"),
211
+ ("ks", "") => (false, "ks"),
212
+ ("ku", "IQ") => (false, "ku-IQ"),
213
+ ("ku", "IR") => (false, "ku-IR"),
214
+ ("la", "") => (false, "la"),
215
+ ("ota", "") => (false, "ota"),
216
+ ("pa", "PK") => (false, "pa-PK"),
217
+ ("ps", "AF") => (false, "ps-AF"),
218
+ ("ps", "PK") => (false, "ps-PK"),
219
+ ("sd", "") => (false, "sd"),
220
+ ("ug", "") => (false, "ug"),
221
+ ("ur", "") => (false, "ur"),
222
+ _ => return None,
223
+ },
224
+ b"Beng" => match (lang, region) {
225
+ ("bn", "") => (true, "bn"),
226
+ ("as", "") => (false, "as"),
227
+ ("mni", "") => (false, "mni"),
228
+ _ => return None,
229
+ },
230
+ b"Deva" => match (lang, region) {
231
+ ("hi", "") => (true, "hi"),
232
+ ("bh", "") => (false, "bh"),
233
+ ("bho", "") => (false, "bho"),
234
+ ("brx", "") => (false, "brx"),
235
+ ("doi", "") => (false, "doi"),
236
+ ("hne", "") => (false, "hne"),
237
+ ("kok", "") => (false, "kok"),
238
+ ("mai", "") => (false, "mai"),
239
+ ("mr", "") => (false, "mr"),
240
+ ("bne", "") => (false, "bne"),
241
+ ("sa", "") => (false, "sa"),
242
+ ("sat", "") => (false, "sat"),
243
+ _ => return None,
244
+ },
245
+ b"Ethi" => match (lang, region) {
246
+ ("gez", "") => (true, "gez"),
247
+ ("am", "") => (false, "am"),
248
+ ("byn", "") => (false, "byn"),
249
+ ("sid", "") => (false, "sid"),
250
+ ("ti", "ER") => (false, "ti-ER"),
251
+ ("ti", "ET") => (false, "ti-ET"),
252
+ ("tig", "") => (false, "tig"),
253
+ ("wal", "") => (false, "wal"),
254
+ _ => return None,
255
+ },
256
+ b"Hani" => match lang {
257
+ "ja" => (false, "ja"),
258
+ "ko" => (false, "ko"),
259
+ "zh" => {
260
+ match region {
261
+ "HK" => (false, "zh-HK"),
262
+ "TW" => (false, "zh-TW"),
263
+ "MO" => (false, "zh-MO"),
264
+ "SG" => (false, "zh-SG"),
265
+ _ => {
266
+ if locale.script() == Some("Hant") {
267
+ (false, "zh-TW")
268
+ } else {
269
+ // Default to simplified Chinese
270
+ (true, "zh-CN")
271
+ }
272
+ }
273
+ }
274
+ }
275
+ _ => return None,
276
+ },
277
+ b"Hebr" => match (lang, region) {
278
+ ("he", "") => (true, "he"),
279
+ ("yi", "") => (false, "yi"),
280
+ _ => return None,
281
+ },
282
+ b"Tibt" => match (lang, region) {
283
+ ("bo", "") => (true, "bo"),
284
+ ("dz", "") => (false, "dz"),
285
+ _ => return None,
286
+ },
287
+ _ => return None,
288
+ };
289
+ Some((
290
+ is_default,
291
+ Some(Language::parse(token).expect("valid canonical locale")),
292
+ ))
293
+ }