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,844 @@
1
+ // Copyright 2025 the Parley Authors
2
+ // SPDX-License-Identifier: Apache-2.0 OR MIT
3
+
4
+ use core::{
5
+ ffi::{CStr, c_char},
6
+ marker::PhantomData,
7
+ ptr::NonNull,
8
+ };
9
+ use std::{
10
+ borrow::Cow,
11
+ ffi::{CString, OsStr},
12
+ os::unix::ffi::OsStrExt,
13
+ path::Path,
14
+ sync::Arc,
15
+ };
16
+
17
+ #[cfg(feature = "fontconfig-dlopen")]
18
+ use fontconfig_sys::statics::{LIB, LIB_RESULT};
19
+ use fontconfig_sys::{
20
+ FcChar8, FcCharSet, FcConfig, FcFontSet, FcLangSet, FcMatchKind, FcMatchPattern, FcPattern,
21
+ FcResult, FcResultMatch, FcResultNoId, FcResultNoMatch, FcResultOutOfMemory,
22
+ FcResultTypeMismatch, FcSetSystem,
23
+ constants::{FC_CHARSET, FC_FAMILY, FC_FILE, FC_INDEX, FC_LANG, FC_SLANT, FC_WEIGHT, FC_WIDTH},
24
+ ffi_dispatch,
25
+ };
26
+ #[cfg(not(feature = "fontconfig-dlopen"))]
27
+ use fontconfig_sys::{
28
+ FcCharSetAddChar, FcCharSetCopy, FcCharSetCreate, FcCharSetDestroy, FcConfigBuildFonts,
29
+ FcConfigDestroy, FcConfigGetFonts, FcConfigReference, FcConfigSubstitute, FcFontMatch,
30
+ FcFontRenderPrepare, FcFontSetDestroy, FcFontSort, FcInitLoadConfig, FcLangSetAdd,
31
+ FcLangSetCopy, FcLangSetCreate, FcLangSetDestroy, FcNameUnparse, FcPatternAddCharSet,
32
+ FcPatternAddLangSet, FcPatternAddString, FcPatternCreate, FcPatternDestroy,
33
+ FcPatternGetInteger, FcPatternGetString, FcPatternReference, FcStrFree,
34
+ };
35
+
36
+ use hashbrown::{HashMap, HashSet, hash_map::Entry};
37
+ use smallvec::SmallVec;
38
+
39
+ use crate::{
40
+ FallbackKey, FamilyId, FamilyInfo, FontInfo, FontStyle, FontWeight, FontWidth,
41
+ FromFontconfig as _, GenericFamily, Script, ScriptExt,
42
+ family_name::{FamilyName, FamilyNameMap},
43
+ generic::GenericFamilyMap,
44
+ source::SourcePathMap,
45
+ };
46
+
47
+ #[derive(Clone, Copy, PartialEq, Eq, Debug)]
48
+ enum MatchErr {
49
+ NoMatch,
50
+ TypeMismatch,
51
+ NoId,
52
+ OutOfMemory,
53
+ Other,
54
+ }
55
+
56
+ impl MatchErr {
57
+ fn from_raw(raw: FcResult) -> Self {
58
+ #[allow(non_upper_case_globals)]
59
+ match raw {
60
+ FcResultNoMatch => Self::NoMatch,
61
+ FcResultTypeMismatch => Self::TypeMismatch,
62
+ FcResultNoId => Self::NoId,
63
+ FcResultOutOfMemory => Self::OutOfMemory,
64
+ _ => Self::Other,
65
+ }
66
+ }
67
+ }
68
+
69
+ type MatchResult<T> = Result<T, MatchErr>;
70
+
71
+ /// Ownership for refcounted Fontconfig objects. Used to track if a given
72
+ /// fontconfig function returns an object that it owns or is passing its
73
+ /// ownership to us.
74
+ #[derive(Clone, Copy, PartialEq, Eq, Debug)]
75
+ enum Ownership {
76
+ /// This object is owned by Fontconfig and needs to be freed by it.
77
+ Fontconfig,
78
+ /// This object is owned by the application.
79
+ Application,
80
+ }
81
+
82
+ /// Wrapper for an `FcPattern`.
83
+ struct Pattern {
84
+ inner: NonNull<FcPattern>,
85
+ }
86
+
87
+ impl Pattern {
88
+ fn new() -> Option<Self> {
89
+ Some(unsafe {
90
+ Self::from_raw(
91
+ ffi_dispatch!(feature = "fontconfig-dlopen", LIB, FcPatternCreate,),
92
+ Ownership::Application,
93
+ )?
94
+ })
95
+ }
96
+
97
+ unsafe fn from_raw(raw: *mut FcPattern, ownership: Ownership) -> Option<Self> {
98
+ let inner = NonNull::new(raw)?;
99
+ // Don't free this object when we are dropped.
100
+ if ownership == Ownership::Fontconfig {
101
+ unsafe {
102
+ ffi_dispatch!(
103
+ feature = "fontconfig-dlopen",
104
+ LIB,
105
+ FcPatternReference,
106
+ inner.as_ptr()
107
+ );
108
+ }
109
+ }
110
+ Some(Self { inner })
111
+ }
112
+
113
+ fn add_string(&mut self, object: &CStr, s: &CStr) -> bool {
114
+ // All objects passed to FcPatternAddWhatever are cloned.
115
+ unsafe {
116
+ ffi_dispatch!(
117
+ feature = "fontconfig-dlopen",
118
+ LIB,
119
+ FcPatternAddString,
120
+ self.inner.as_ptr(),
121
+ object.as_ptr(),
122
+ s.as_ptr() as *const _
123
+ ) != 0
124
+ }
125
+ }
126
+
127
+ fn add_charset(&mut self, object: &CStr, s: &CharSet) -> bool {
128
+ unsafe {
129
+ ffi_dispatch!(
130
+ feature = "fontconfig-dlopen",
131
+ LIB,
132
+ FcPatternAddCharSet,
133
+ self.inner.as_ptr(),
134
+ object.as_ptr(),
135
+ s.inner.as_ptr()
136
+ ) != 0
137
+ }
138
+ }
139
+
140
+ fn add_langset(&mut self, object: &CStr, s: &LangSet) -> bool {
141
+ unsafe {
142
+ ffi_dispatch!(
143
+ feature = "fontconfig-dlopen",
144
+ LIB,
145
+ FcPatternAddLangSet,
146
+ self.inner.as_ptr(),
147
+ object.as_ptr(),
148
+ s.inner.as_ptr()
149
+ ) != 0
150
+ }
151
+ }
152
+
153
+ fn get_string<'a>(&'a self, object: &CStr, n: u32) -> MatchResult<Cow<'a, str>> {
154
+ Ok(self.get_c_string(object, n)?.to_string_lossy())
155
+ }
156
+
157
+ fn get_c_string<'a>(&'a self, object: &CStr, n: u32) -> MatchResult<&'a CStr> {
158
+ let mut dest: *mut FcChar8 = std::ptr::null_mut();
159
+ let result = unsafe {
160
+ ffi_dispatch!(
161
+ feature = "fontconfig-dlopen",
162
+ LIB,
163
+ FcPatternGetString,
164
+ self.inner.as_ptr(),
165
+ object.as_ptr(),
166
+ n.try_into().map_err(|_| MatchErr::Other)?,
167
+ &raw mut dest
168
+ )
169
+ };
170
+ if result != FcResultMatch {
171
+ return Err(MatchErr::from_raw(result));
172
+ }
173
+ let dest = NonNull::new(dest).ok_or(MatchErr::Other)?;
174
+ Ok(unsafe { CStr::from_ptr(dest.as_ptr() as *const _) })
175
+ }
176
+
177
+ fn get_int(&self, object: &CStr, n: u32) -> MatchResult<i32> {
178
+ let mut dest = 0;
179
+ let result = unsafe {
180
+ ffi_dispatch!(
181
+ feature = "fontconfig-dlopen",
182
+ LIB,
183
+ FcPatternGetInteger,
184
+ self.inner.as_ptr(),
185
+ object.as_ptr(),
186
+ n.try_into().map_err(|_| MatchErr::Other)?,
187
+ &raw mut dest
188
+ )
189
+ };
190
+ if result != FcResultMatch {
191
+ return Err(MatchErr::from_raw(result));
192
+ }
193
+ Ok(dest)
194
+ }
195
+ }
196
+
197
+ impl Clone for Pattern {
198
+ fn clone(&self) -> Self {
199
+ unsafe {
200
+ ffi_dispatch!(
201
+ feature = "fontconfig-dlopen",
202
+ LIB,
203
+ FcPatternReference,
204
+ self.inner.as_ptr()
205
+ );
206
+ };
207
+ Self { inner: self.inner }
208
+ }
209
+ }
210
+
211
+ impl Drop for Pattern {
212
+ fn drop(&mut self) {
213
+ unsafe {
214
+ ffi_dispatch!(
215
+ feature = "fontconfig-dlopen",
216
+ LIB,
217
+ FcPatternDestroy,
218
+ self.inner.as_ptr()
219
+ );
220
+ };
221
+ }
222
+ }
223
+
224
+ impl std::fmt::Debug for Pattern {
225
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
226
+ match NonNull::new(unsafe {
227
+ ffi_dispatch!(
228
+ feature = "fontconfig-dlopen",
229
+ LIB,
230
+ FcNameUnparse,
231
+ self.inner.as_ptr()
232
+ )
233
+ }) {
234
+ Some(unparsed) => {
235
+ let printable = unsafe {
236
+ &CStr::from_ptr(unparsed.as_ptr() as *const c_char).to_string_lossy()
237
+ };
238
+ let res = f.write_str(printable);
239
+ unsafe {
240
+ ffi_dispatch!(
241
+ feature = "fontconfig-dlopen",
242
+ LIB,
243
+ FcStrFree,
244
+ unparsed.as_ptr()
245
+ );
246
+ };
247
+ res
248
+ }
249
+ None => f.debug_struct("Pattern").finish(),
250
+ }
251
+ }
252
+ }
253
+
254
+ struct FontSet<'a> {
255
+ inner: NonNull<FcFontSet>,
256
+ ownership: Ownership,
257
+ // If an `FcFontSet` is created from an `FcPattern`, it will reference that
258
+ // pattern's data. Well, maybe. The docs say "The returned FcFontSet
259
+ // references FcPattern structures which may be shared by the return value
260
+ // from multiple FcFontSort calls, applications cannot modify these
261
+ // patterns." It's unclear whether this refers to actual lifetime/ownership
262
+ // semantics or if everything's properly refcounted and you're just not
263
+ // allowed to mutate them.
264
+ _parent: PhantomData<&'a ()>,
265
+ }
266
+
267
+ impl FontSet<'_> {
268
+ unsafe fn from_raw(raw: *mut FcFontSet, ownership: Ownership) -> Option<Self> {
269
+ let inner = NonNull::new(raw)?;
270
+ Some(Self {
271
+ inner,
272
+ ownership,
273
+ _parent: PhantomData,
274
+ })
275
+ }
276
+
277
+ fn iter(&self) -> FontSetIter<'_> {
278
+ FontSetIter {
279
+ i: 0,
280
+ font_set: self,
281
+ }
282
+ }
283
+ }
284
+
285
+ impl Drop for FontSet<'_> {
286
+ fn drop(&mut self) {
287
+ if self.ownership == Ownership::Application {
288
+ unsafe {
289
+ ffi_dispatch!(
290
+ feature = "fontconfig-dlopen",
291
+ LIB,
292
+ FcFontSetDestroy,
293
+ self.inner.as_ptr()
294
+ );
295
+ };
296
+ }
297
+ }
298
+ }
299
+
300
+ struct FontSetIter<'a> {
301
+ i: usize,
302
+ font_set: &'a FontSet<'a>,
303
+ }
304
+
305
+ impl Iterator for FontSetIter<'_> {
306
+ type Item = Pattern;
307
+
308
+ fn next(&mut self) -> Option<Self::Item> {
309
+ let font_set = self.font_set.inner.as_ptr();
310
+ if self.i >= unsafe { (*font_set).nfont }.try_into().ok()? {
311
+ None
312
+ } else {
313
+ let pattern: *mut FcPattern = unsafe { *(*font_set).fonts.add(self.i) };
314
+ self.i += 1;
315
+ Some(unsafe { Pattern::from_raw(pattern, Ownership::Fontconfig) }?)
316
+ }
317
+ }
318
+
319
+ fn size_hint(&self) -> (usize, Option<usize>) {
320
+ let nfont: Result<usize, _> = unsafe { (*self.font_set.inner.as_ptr()).nfont }.try_into();
321
+ let Ok(nfont) = nfont else {
322
+ return (0, None);
323
+ };
324
+ (nfont, Some(nfont))
325
+ }
326
+ }
327
+
328
+ struct LangSet {
329
+ inner: NonNull<FcLangSet>,
330
+ }
331
+
332
+ impl LangSet {
333
+ fn new() -> Option<Self> {
334
+ let inner = NonNull::new(unsafe {
335
+ ffi_dispatch!(feature = "fontconfig-dlopen", LIB, FcLangSetCreate,)
336
+ })?;
337
+ Some(Self { inner })
338
+ }
339
+
340
+ fn add(&mut self, lang: &CStr) -> bool {
341
+ unsafe {
342
+ ffi_dispatch!(
343
+ feature = "fontconfig-dlopen",
344
+ LIB,
345
+ FcLangSetAdd,
346
+ self.inner.as_ptr(),
347
+ lang.as_ptr() as *const _
348
+ ) != 0
349
+ }
350
+ }
351
+ }
352
+
353
+ impl Drop for LangSet {
354
+ fn drop(&mut self) {
355
+ unsafe {
356
+ ffi_dispatch!(
357
+ feature = "fontconfig-dlopen",
358
+ LIB,
359
+ FcLangSetDestroy,
360
+ self.inner.as_ptr()
361
+ );
362
+ };
363
+ }
364
+ }
365
+
366
+ impl Clone for LangSet {
367
+ fn clone(&self) -> Self {
368
+ Self {
369
+ inner: unsafe {
370
+ NonNull::new(ffi_dispatch!(
371
+ feature = "fontconfig-dlopen",
372
+ LIB,
373
+ FcLangSetCopy,
374
+ self.inner.as_ptr()
375
+ ))
376
+ .unwrap()
377
+ },
378
+ }
379
+ }
380
+ }
381
+
382
+ struct CharSet {
383
+ inner: NonNull<FcCharSet>,
384
+ }
385
+
386
+ impl CharSet {
387
+ fn new() -> Option<Self> {
388
+ let inner = NonNull::new(unsafe {
389
+ ffi_dispatch!(feature = "fontconfig-dlopen", LIB, FcCharSetCreate,)
390
+ })?;
391
+ Some(Self { inner })
392
+ }
393
+
394
+ fn add(&mut self, c: char) -> bool {
395
+ unsafe {
396
+ ffi_dispatch!(
397
+ feature = "fontconfig-dlopen",
398
+ LIB,
399
+ FcCharSetAddChar,
400
+ self.inner.as_ptr(),
401
+ c as u32
402
+ ) != 0
403
+ }
404
+ }
405
+ }
406
+
407
+ impl Drop for CharSet {
408
+ fn drop(&mut self) {
409
+ unsafe {
410
+ ffi_dispatch!(
411
+ feature = "fontconfig-dlopen",
412
+ LIB,
413
+ FcCharSetDestroy,
414
+ self.inner.as_ptr()
415
+ );
416
+ };
417
+ }
418
+ }
419
+
420
+ impl Clone for CharSet {
421
+ fn clone(&self) -> Self {
422
+ Self {
423
+ inner: unsafe {
424
+ NonNull::new(ffi_dispatch!(
425
+ feature = "fontconfig-dlopen",
426
+ LIB,
427
+ FcCharSetCopy,
428
+ self.inner.as_ptr()
429
+ ))
430
+ .unwrap()
431
+ },
432
+ }
433
+ }
434
+ }
435
+
436
+ struct Config {
437
+ inner: NonNull<FcConfig>,
438
+ }
439
+
440
+ impl Config {
441
+ unsafe fn from_raw(raw: *mut FcConfig, ownership: Ownership) -> Option<Self> {
442
+ let inner = NonNull::new(raw)?;
443
+ // Don't free this object when we are dropped.
444
+ if ownership == Ownership::Fontconfig {
445
+ unsafe {
446
+ ffi_dispatch!(
447
+ feature = "fontconfig-dlopen",
448
+ LIB,
449
+ FcConfigReference,
450
+ inner.as_ptr()
451
+ );
452
+ }
453
+ }
454
+ Some(Self { inner })
455
+ }
456
+
457
+ fn substitute(&self, pattern: &mut Pattern, kind: FcMatchKind) {
458
+ unsafe {
459
+ ffi_dispatch!(
460
+ feature = "fontconfig-dlopen",
461
+ LIB,
462
+ FcConfigSubstitute,
463
+ self.inner.as_ptr(),
464
+ pattern.inner.as_ptr(),
465
+ kind
466
+ )
467
+ };
468
+ }
469
+
470
+ fn font_sort<'me, 'ret, 'pat: 'ret>(
471
+ &'me self,
472
+ pattern: &'pat Pattern,
473
+ trim: bool,
474
+ ) -> MatchResult<FontSet<'ret>> {
475
+ let mut result = 0;
476
+ // The returned FcFontSet is for us to free.
477
+ let font_set = unsafe {
478
+ FontSet::from_raw(
479
+ ffi_dispatch!(
480
+ feature = "fontconfig-dlopen",
481
+ LIB,
482
+ FcFontSort,
483
+ self.inner.as_ptr(),
484
+ pattern.inner.as_ptr(),
485
+ trim as i32,
486
+ std::ptr::null_mut(),
487
+ &raw mut result
488
+ ),
489
+ Ownership::Application,
490
+ )
491
+ }
492
+ .ok_or(MatchErr::Other)?;
493
+ if result != FcResultMatch {
494
+ return Err(MatchErr::from_raw(result));
495
+ }
496
+
497
+ Ok(font_set)
498
+ }
499
+
500
+ fn font_match(&self, pattern: &Pattern) -> MatchResult<Pattern> {
501
+ let mut result = 0;
502
+ let pattern = unsafe {
503
+ Pattern::from_raw(
504
+ ffi_dispatch!(
505
+ feature = "fontconfig-dlopen",
506
+ LIB,
507
+ FcFontMatch,
508
+ self.inner.as_ptr(),
509
+ pattern.inner.as_ptr(),
510
+ &raw mut result
511
+ ),
512
+ Ownership::Application,
513
+ )
514
+ }
515
+ .ok_or(MatchErr::Other)?;
516
+ if result != FcResultMatch {
517
+ return Err(MatchErr::from_raw(result));
518
+ }
519
+
520
+ Ok(pattern)
521
+ }
522
+
523
+ fn font_render_prepare(&self, pat: &Pattern, font: &Pattern) -> Option<Pattern> {
524
+ unsafe {
525
+ Pattern::from_raw(
526
+ ffi_dispatch!(
527
+ feature = "fontconfig-dlopen",
528
+ LIB,
529
+ FcFontRenderPrepare,
530
+ self.inner.as_ptr(),
531
+ pat.inner.as_ptr(),
532
+ font.inner.as_ptr()
533
+ ),
534
+ Ownership::Application,
535
+ )
536
+ }
537
+ }
538
+ }
539
+
540
+ impl Clone for Config {
541
+ fn clone(&self) -> Self {
542
+ unsafe {
543
+ ffi_dispatch!(
544
+ feature = "fontconfig-dlopen",
545
+ LIB,
546
+ FcConfigReference,
547
+ self.inner.as_ptr()
548
+ )
549
+ };
550
+ Self { inner: self.inner }
551
+ }
552
+ }
553
+
554
+ impl Drop for Config {
555
+ fn drop(&mut self) {
556
+ unsafe {
557
+ ffi_dispatch!(
558
+ feature = "fontconfig-dlopen",
559
+ LIB,
560
+ FcConfigDestroy,
561
+ self.inner.as_ptr()
562
+ );
563
+ };
564
+ }
565
+ }
566
+
567
+ /// Cache wrapper that maps Unicode scripts to fontconfig [`CharSet`]s.
568
+ #[derive(Default)]
569
+ struct ScriptCharSetMap(HashMap<Script, Option<CharSet>>);
570
+
571
+ impl ScriptCharSetMap {
572
+ fn charset_for_script(&mut self, script: Script) -> Option<&CharSet> {
573
+ match self.0.entry(script) {
574
+ Entry::Occupied(e) => e.into_mut().as_ref(),
575
+ Entry::Vacant(e) => {
576
+ let Some(sample) = script.sample() else {
577
+ return e.insert(None).as_ref();
578
+ };
579
+
580
+ let mut charset = CharSet::new()?;
581
+ for c in sample.chars() {
582
+ charset.add(c);
583
+ }
584
+ e.insert(Some(charset)).as_ref()
585
+ }
586
+ }
587
+ }
588
+ }
589
+
590
+ /// Raw access to the collection of local system fonts.
591
+ #[derive(Default)]
592
+ pub(crate) struct SystemFonts {
593
+ pub(crate) name_map: Arc<FamilyNameMap>,
594
+ pub(crate) generic_families: Arc<GenericFamilyMap>,
595
+ source_cache: SourcePathMap,
596
+ family_map: HashMap<FamilyId, Option<FamilyInfo>>,
597
+ config: Option<Config>,
598
+ script_charsets: ScriptCharSetMap,
599
+ }
600
+
601
+ unsafe impl Send for SystemFonts {}
602
+
603
+ impl SystemFonts {
604
+ pub(crate) fn new() -> Self {
605
+ // We couldn't find the fontconfig library; maybe it doesn't exist. Just
606
+ // return a `SystemFonts` with no `config`. All our methods will return
607
+ // `None` and shouldn't attempt any FFI calls because the first thing we
608
+ // do is check for `config`.
609
+ #[cfg(feature = "fontconfig-dlopen")]
610
+ if LIB_RESULT.is_err() {
611
+ return Self::default();
612
+ }
613
+
614
+ // Initialize the config
615
+ let config =
616
+ unsafe { ffi_dispatch!(feature = "fontconfig-dlopen", LIB, FcInitLoadConfig,) };
617
+ // fontconfig returns a new config object each time we call FcInitLoadConfig
618
+ let Some(config) = (unsafe { Config::from_raw(config, Ownership::Application) }) else {
619
+ return Self::default();
620
+ };
621
+ unsafe {
622
+ ffi_dispatch!(
623
+ feature = "fontconfig-dlopen",
624
+ LIB,
625
+ FcConfigBuildFonts,
626
+ config.inner.as_ptr()
627
+ );
628
+ }
629
+
630
+ // Get all the fonts.
631
+
632
+ // The fontconfig docs say this "isn't threadsafe", but this seems to be
633
+ // related to refcounting:
634
+ // https://gitlab.freedesktop.org/fontconfig/fontconfig/-/commit/b5bcf61fe789e66df2de609ec246cb7e4d326180
635
+ // The source code for this function just calls `FcConfigGetCurrent` if
636
+ // none is provided (which we do, and it's atomic anyway) and then
637
+ // dereferences a pointer. I *think* the safety issue they're referring
638
+ // to is that if we destroyed this config on another thread and then
639
+ // tried to access what it returns, it would dereference a null pointer.
640
+ // But we're not doing that.
641
+ let Some(font_set) = (unsafe {
642
+ FontSet::from_raw(
643
+ ffi_dispatch!(
644
+ feature = "fontconfig-dlopen",
645
+ LIB,
646
+ FcConfigGetFonts,
647
+ config.inner.as_ptr(),
648
+ FcSetSystem
649
+ ),
650
+ Ownership::Fontconfig,
651
+ )
652
+ }) else {
653
+ return Self::default();
654
+ };
655
+
656
+ // Populate the family name map.
657
+ let mut name_map = FamilyNameMap::default();
658
+ for pattern in font_set.iter() {
659
+ let mut i = 0;
660
+
661
+ let mut first_name_id = None;
662
+ // For fonts with more than one family name, the second one is
663
+ // *often* (but not always) an RBIZ name.
664
+ while let Ok(name) = pattern.get_string(FC_FAMILY, i) {
665
+ if i == 0 {
666
+ // First name
667
+ first_name_id = Some(name_map.get_or_insert(&name).id());
668
+ } else if let Some(first_name_id) = first_name_id {
669
+ name_map.add_alias(first_name_id, &name);
670
+ }
671
+ i += 1;
672
+ }
673
+ }
674
+
675
+ // Populate the generic family map.
676
+ let mut generic_families = GenericFamilyMap::default();
677
+ for (generic_family, name) in GENERIC_FAMILY_NAMES.iter().copied() {
678
+ let mut pattern = Pattern::new().unwrap();
679
+ pattern.add_string(FC_FAMILY, name);
680
+ // TODO: do we need FcConfigSetDefaultSubstitute?
681
+
682
+ config.substitute(&mut pattern, FcMatchPattern);
683
+
684
+ // We enable the "trim" option here which ignores later fonts if
685
+ // they provide no new Unicode coverage. On error this generic
686
+ // family simply has no members; in particular `font_sort` errors
687
+ // with `NoMatch` when there are no fonts to sort at all (e.g. a
688
+ // fontconfig configuration exposing an empty system font set).
689
+ let Ok(font_set) = config.font_sort(&pattern, true) else {
690
+ continue;
691
+ };
692
+
693
+ // There are a lot of duplicate font families in the substituted
694
+ // pattern. Keep track of which ones have already been added to the
695
+ // list.
696
+ let mut added_families = HashSet::new();
697
+
698
+ generic_families.append(
699
+ generic_family,
700
+ font_set.iter().filter_map(|font| {
701
+ // Not sure if FcFontRenderPrepare performs any substitutions
702
+ // relevant to fallback family name matching, but it's a good
703
+ // idea to call it just in case.
704
+ let font = config.font_render_prepare(&pattern, &font)?;
705
+ // Generic families can have more than one name, but the only
706
+ // one we care about is the first one.
707
+ let name = font.get_string(FC_FAMILY, 0).ok()?;
708
+ let family_name = name_map.get(name.as_ref())?;
709
+
710
+ if !added_families.insert(family_name.id()) {
711
+ return None;
712
+ }
713
+
714
+ Some(family_name.id())
715
+ }),
716
+ );
717
+ }
718
+
719
+ Self {
720
+ name_map: Arc::new(name_map),
721
+ generic_families: Arc::new(generic_families),
722
+ source_cache: SourcePathMap::default(),
723
+ family_map: HashMap::default(),
724
+ config: Some(config),
725
+ script_charsets: ScriptCharSetMap::default(),
726
+ }
727
+ }
728
+
729
+ pub(crate) fn family(&mut self, id: FamilyId) -> Option<FamilyInfo> {
730
+ match self.family_map.get(&id) {
731
+ Some(Some(family)) => return Some(family.clone()),
732
+ Some(None) => return None,
733
+ None => {}
734
+ }
735
+
736
+ let family = self.family_uncached(id);
737
+ self.family_map.insert(id, family.clone());
738
+ family
739
+ }
740
+
741
+ pub(crate) fn fallback(&mut self, key: impl Into<FallbackKey>) -> Option<FamilyId> {
742
+ let config = self.config.as_ref()?;
743
+ let key: FallbackKey = key.into();
744
+
745
+ let mut pattern = Pattern::new()?;
746
+
747
+ let locale_lang_set = key.locale_str().and_then(|locale| {
748
+ let mut lang_set = LangSet::new()?;
749
+ lang_set.add(CString::new(locale).ok()?.as_c_str());
750
+ Some(lang_set)
751
+ });
752
+ let script_char_set = self.script_charsets.charset_for_script(key.script());
753
+
754
+ if let Some(set) = locale_lang_set {
755
+ pattern.add_langset(FC_LANG, &set);
756
+ }
757
+ if let Some(set) = script_char_set {
758
+ pattern.add_charset(FC_CHARSET, set);
759
+ }
760
+
761
+ config.substitute(&mut pattern, FcMatchPattern);
762
+
763
+ // This calls FcFontRenderPrepare for us.
764
+ let font = config.font_match(&pattern).ok()?;
765
+
766
+ let family_name = font.get_string(FC_FAMILY, 0).ok()?;
767
+ self.name_map.get(&family_name).map(FamilyName::id)
768
+ }
769
+ }
770
+
771
+ impl SystemFonts {
772
+ fn family_uncached(&mut self, id: FamilyId) -> Option<FamilyInfo> {
773
+ let config = self.config.as_ref()?;
774
+ let name = self.name_map.get_by_id(id).cloned()?;
775
+
776
+ // Match by family name.
777
+ let mut pattern = Pattern::new()?;
778
+ pattern.add_string(FC_FAMILY, CString::new(name.name()).ok()?.as_c_str());
779
+ config.substitute(&mut pattern, FcMatchPattern);
780
+
781
+ let fc_fonts = config.font_sort(&pattern, false).ok()?;
782
+ let mut font_infos = SmallVec::<[FontInfo; 4]>::new();
783
+ for font in fc_fonts.iter() {
784
+ let Some(font) = config.font_render_prepare(&pattern, &font) else {
785
+ continue;
786
+ };
787
+ let Ok(family_name) = font.get_string(FC_FAMILY, 0) else {
788
+ continue;
789
+ };
790
+ // We've performed font substitution and then sorted everything by
791
+ // "closeness", so the good fonts should be at the top. Once we see
792
+ // a fallback font (one that's not part of the family we explicitly
793
+ // asked for), we can stop.
794
+ if family_name != name.name() {
795
+ break;
796
+ }
797
+
798
+ if let Some(font_info) = (|| {
799
+ let path = font.get_c_string(FC_FILE, 0).ok()?;
800
+ // This part is Unix-specific. Sorry, Windows fontconfig user.
801
+ let path = Path::new(OsStr::from_bytes(path.to_bytes()));
802
+ let source_info = self.source_cache.get_or_insert(path);
803
+
804
+ let weight = font
805
+ .get_int(FC_WEIGHT, 0)
806
+ .map(FontWeight::from_fontconfig)
807
+ .unwrap_or_default();
808
+ let width = font
809
+ .get_int(FC_WIDTH, 0)
810
+ .map(FontWidth::from_fontconfig)
811
+ .unwrap_or_default();
812
+ let style = font
813
+ .get_int(FC_SLANT, 0)
814
+ .map(FontStyle::from_fontconfig)
815
+ .unwrap_or_default();
816
+ let index = font.get_int(FC_INDEX, 0).map_or(0, |idx| idx.max(0) as u32);
817
+
818
+ let mut font_info = FontInfo::from_source(source_info, index)?;
819
+ // TODO(valadaptive): does this do anything anymore?
820
+ font_info.maybe_override_attributes(width, style, weight);
821
+ Some(font_info)
822
+ })() {
823
+ font_infos.push(font_info);
824
+ }
825
+ }
826
+
827
+ if font_infos.is_empty() {
828
+ return None;
829
+ }
830
+
831
+ Some(FamilyInfo::new(name, font_infos))
832
+ }
833
+ }
834
+
835
+ const GENERIC_FAMILY_NAMES: &[(GenericFamily, &CStr)] = &[
836
+ (GenericFamily::Serif, c"serif"),
837
+ (GenericFamily::SansSerif, c"sans-serif"),
838
+ (GenericFamily::Monospace, c"monospace"),
839
+ (GenericFamily::Cursive, c"cursive"),
840
+ (GenericFamily::Fantasy, c"fantasy"),
841
+ (GenericFamily::SystemUi, c"system-ui"),
842
+ (GenericFamily::Emoji, c"emoji"),
843
+ (GenericFamily::Math, c"math"),
844
+ ];