blitz-pdf 0.1.6 → 0.1.8

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 (88) hide show
  1. package/README.md +66 -11
  2. package/THIRD_PARTY_NOTICES.md +19 -1
  3. package/dist/api.d.ts +2 -1
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +31 -6
  6. package/dist/api.js.map +1 -1
  7. package/dist/browser.d.ts +2 -1
  8. package/dist/browser.d.ts.map +1 -1
  9. package/dist/browser.js +1 -1
  10. package/dist/browser.js.map +1 -1
  11. package/dist/node.d.ts +2 -1
  12. package/dist/node.d.ts.map +1 -1
  13. package/dist/node.js +6 -2
  14. package/dist/node.js.map +1 -1
  15. package/dist/types.d.ts +16 -1
  16. package/dist/types.d.ts.map +1 -1
  17. package/dist/worker-host.d.ts +10 -5
  18. package/dist/worker-host.d.ts.map +1 -1
  19. package/dist/worker-host.js +149 -28
  20. package/dist/worker-host.js.map +1 -1
  21. package/dist/worker-protocol.d.ts +5 -0
  22. package/dist/worker-protocol.d.ts.map +1 -1
  23. package/dist/worker-runtime.d.ts +1 -1
  24. package/dist/worker-runtime.d.ts.map +1 -1
  25. package/dist/worker-runtime.js +22 -6
  26. package/dist/worker-runtime.js.map +1 -1
  27. package/licenses/Noto-OFL.txt +94 -0
  28. package/licenses/NotoCJK-OFL.txt +92 -0
  29. package/licenses/NotoEmoji-OFL.txt +93 -0
  30. package/licenses/RUST_DEPENDENCIES.txt +34 -40
  31. package/licenses/Unifont-OFL.txt +90 -0
  32. package/licenses/font-sources.json +106 -0
  33. package/package.json +1 -1
  34. package/src/api.ts +32 -7
  35. package/src/browser.ts +2 -1
  36. package/src/node.ts +7 -2
  37. package/src/types.ts +17 -1
  38. package/src/worker-host.ts +148 -29
  39. package/src/worker-protocol.ts +5 -5
  40. package/src/worker-runtime.ts +20 -6
  41. package/vendor/README.md +38 -5
  42. package/vendor/blitz-dom/Cargo.toml +6 -6
  43. package/vendor/blitz-dom/src/accessibility.rs +175 -2
  44. package/vendor/blitz-dom/src/config.rs +8 -10
  45. package/vendor/blitz-dom/src/document.rs +66 -3
  46. package/vendor/blitz-dom/src/events/keyboard.rs +2 -6
  47. package/vendor/blitz-dom/src/layout/construct.rs +5 -300
  48. package/vendor/blitz-dom/src/layout/inline/build.rs +309 -0
  49. package/vendor/blitz-dom/src/layout/{inline.rs → inline/layout.rs} +1 -1
  50. package/vendor/blitz-dom/src/layout/inline/mod.rs +4 -0
  51. package/vendor/blitz-dom/src/layout/mod.rs +3 -1
  52. package/vendor/blitz-dom/src/layout/replaced.rs +13 -4
  53. package/vendor/blitz-dom/src/mutator.rs +47 -0
  54. package/vendor/blitz-dom/src/node/element.rs +17 -0
  55. package/vendor/blitz-dom/src/node/mod.rs +1 -0
  56. package/vendor/blitz-dom/src/node/node.rs +46 -5
  57. package/vendor/blitz-dom/src/node/text.rs +11 -11
  58. package/vendor/blitz-dom/src/resolve.rs +2 -2
  59. package/vendor/blitz-dom/src/stylo.rs +30 -22
  60. package/vendor/blitz-dom/src/util.rs +1 -1
  61. package/vendor/blitz-paint/Cargo.toml +4 -4
  62. package/vendor/fontique/Cargo.toml +194 -0
  63. package/vendor/fontique/LICENSE-APACHE +201 -0
  64. package/vendor/fontique/LICENSE-MIT +25 -0
  65. package/vendor/fontique/README.md +54 -0
  66. package/vendor/fontique/examples/generic_families.rs +37 -0
  67. package/vendor/fontique/src/attributes.rs +42 -0
  68. package/vendor/fontique/src/backend/android.rs +218 -0
  69. package/vendor/fontique/src/backend/coretext.rs +206 -0
  70. package/vendor/fontique/src/backend/dwrite.rs +395 -0
  71. package/vendor/fontique/src/backend/fontconfig.rs +844 -0
  72. package/vendor/fontique/src/backend/mod.rs +75 -0
  73. package/vendor/fontique/src/charmap.rs +127 -0
  74. package/vendor/fontique/src/collection/mod.rs +1016 -0
  75. package/vendor/fontique/src/collection/query.rs +392 -0
  76. package/vendor/fontique/src/fallback.rs +293 -0
  77. package/vendor/fontique/src/family.rs +111 -0
  78. package/vendor/fontique/src/family_name.rs +153 -0
  79. package/vendor/fontique/src/font.rs +562 -0
  80. package/vendor/fontique/src/generic.rs +36 -0
  81. package/vendor/fontique/src/impl_fontconfig.rs +159 -0
  82. package/vendor/fontique/src/lib.rs +87 -0
  83. package/vendor/fontique/src/matching.rs +483 -0
  84. package/vendor/fontique/src/scan.rs +312 -0
  85. package/vendor/fontique/src/script.rs +271 -0
  86. package/vendor/fontique/src/source.rs +108 -0
  87. package/vendor/fontique/src/source_cache.rs +254 -0
  88. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,218 @@
1
+ // Copyright 2024 the Parley Authors
2
+ // SPDX-License-Identifier: Apache-2.0 OR MIT
3
+
4
+ use alloc::{
5
+ boxed::Box,
6
+ str::FromStr,
7
+ string::{String, ToString},
8
+ sync::Arc,
9
+ vec,
10
+ vec::Vec,
11
+ };
12
+ use std::path::Path;
13
+
14
+ use hashbrown::HashMap;
15
+ use roxmltree::{Document, Node};
16
+
17
+ use super::{
18
+ FallbackKey, FamilyId, FamilyInfo, FamilyNameMap, GenericFamily, GenericFamilyMap, Language,
19
+ Script, scan,
20
+ };
21
+
22
+ // TODO: Use actual generic families here, where available, when fonts.xml is properly parsed.
23
+ // system-ui should map to `variant="compact"` in some scripts during fallback resolution.
24
+ const DEFAULT_GENERIC_FAMILIES: &[(GenericFamily, &[&str])] = &[
25
+ (
26
+ GenericFamily::SansSerif,
27
+ &["Roboto Flex", "Roboto", "Noto Sans"],
28
+ ),
29
+ (GenericFamily::Serif, &["Noto Serif"]),
30
+ (GenericFamily::Monospace, &["monospace"]),
31
+ (GenericFamily::Cursive, &["Dancing Script"]),
32
+ (GenericFamily::Fantasy, &["Noto Serif"]),
33
+ (
34
+ GenericFamily::SystemUi,
35
+ &["Roboto Flex", "Roboto", "Noto Sans"],
36
+ ),
37
+ (GenericFamily::Emoji, &["Noto Color Emoji"]),
38
+ (GenericFamily::Math, &["Noto Sans Math", "Noto Sans"]),
39
+ ];
40
+
41
+ pub(crate) struct SystemFonts {
42
+ pub(crate) name_map: Arc<FamilyNameMap>,
43
+ pub(crate) generic_families: Arc<GenericFamilyMap>,
44
+ family_map: HashMap<FamilyId, FamilyInfo>,
45
+ locale_fallback: Box<[(Language, FamilyId)]>,
46
+ script_fallback: Box<[(Script, FamilyId)]>,
47
+ }
48
+
49
+ impl SystemFonts {
50
+ pub(crate) fn new() -> Self {
51
+ let android_root: String = std::env::var("ANDROID_ROOT").unwrap_or("/system".to_string());
52
+
53
+ let scan::ScannedCollection {
54
+ family_names: mut name_map,
55
+ families: family_map,
56
+ postscript_names,
57
+ ..
58
+ } = scan::ScannedCollection::from_paths(Path::new(&android_root).join("fonts").to_str(), 8);
59
+ let mut generic_families = GenericFamilyMap::default();
60
+ for (family, names) in DEFAULT_GENERIC_FAMILIES {
61
+ generic_families.set(
62
+ *family,
63
+ names
64
+ .iter()
65
+ .filter_map(|name| name_map.get(name))
66
+ .map(|name| name.id()),
67
+ );
68
+ }
69
+
70
+ let mut locale_fallback = vec![];
71
+ let mut script_fallback = vec![];
72
+
73
+ // Try to get generic info from fonts.xml
74
+ if let Ok(s) = std::fs::read_to_string(Path::new(&android_root).join("etc/fonts.xml"))
75
+ && let Ok(doc) = Document::parse(s.clone().as_str())
76
+ {
77
+ let root = doc.root_element();
78
+ if root.tag_name().name() == "familyset"
79
+ || root
80
+ .attribute("version")
81
+ .is_some_and(|v| usize::from_str(v).is_ok_and(|x| x >= 21))
82
+ {
83
+ for child in root.children() {
84
+ match child.tag_name().name() {
85
+ "alias" => {
86
+ if let Some((name, to)) =
87
+ child.attribute("name").zip(child.attribute("to"))
88
+ {
89
+ if child.attribute("weight").is_some() {
90
+ // weight aliases are an Android quirk and are not in­
91
+ // teresting for use cases other than Android legacy.
92
+ continue;
93
+ }
94
+ let to_n = name_map.get_or_insert(to);
95
+ name_map.add_alias(to_n.id(), name);
96
+ }
97
+ }
98
+ "family" => {
99
+ if let Some(name) = child.attribute("name") {
100
+ let f = name_map.get_or_insert(name);
101
+ let _id = f.id();
102
+ for _child in child.children() {
103
+ // TODO: map using postScriptName when available other­
104
+ // wise use the file name, and perhaps if necess­
105
+ // ary (e.g. if it's a collection), do something
106
+ // smarter, or something dumb that meets expecta­
107
+ // tions on Android.
108
+ }
109
+ } else if let Some(langs) = child
110
+ .attribute("lang")
111
+ .map(|s| s.split(',').collect::<Vec<&str>>())
112
+ {
113
+ let (_has_for, hasnt_for): (Vec<Node<'_, '_>>, Vec<Node<'_, '_>>) =
114
+ child
115
+ .children()
116
+ .partition(|c| c.attribute("fallbackFor").is_some());
117
+ {
118
+ // general fallback families
119
+ let (ps_named, _ps_unnamed): (
120
+ Vec<Node<'_, '_>>,
121
+ Vec<Node<'_, '_>>,
122
+ ) = hasnt_for
123
+ .iter()
124
+ .partition(|c| c.attribute("postScriptName").is_some());
125
+
126
+ if let Some(family) = ps_named.iter().find_map(|x| {
127
+ postscript_names.get(x.attribute("postScriptName").unwrap())
128
+ }) {
129
+ for lang in langs {
130
+ if let Some(scr) = lang.strip_prefix("und-") {
131
+ // Undefined lang for script-only fallbacks
132
+ script_fallback.push((
133
+ scr.parse().unwrap_or(Script::UNKNOWN),
134
+ *family,
135
+ ));
136
+ } else if let Ok(locale) = Language::parse(lang) {
137
+ if let Some(scr) = locale
138
+ .script()
139
+ .and_then(|s| s.parse::<Script>().ok())
140
+ {
141
+ // Also fallback for the script on its own
142
+ script_fallback.push((scr, *family));
143
+ if Script::from_bytes(*b"Hant") == scr {
144
+ // This works around ambiguous han char­
145
+ // acters going unmapped with current
146
+ // fallback code. This should be done in
147
+ // a locale-dependent manner, since that
148
+ // is the norm.
149
+ script_fallback.push((
150
+ Script::from_bytes(*b"Hani"),
151
+ *family,
152
+ ));
153
+ }
154
+ }
155
+ locale_fallback.push((locale, *family));
156
+ }
157
+ }
158
+ }
159
+
160
+ // TODO: handle mapping to family names from file names
161
+ // when postScriptName is unavailable.
162
+ }
163
+
164
+ // family-specific fallback families, currently unimplemented
165
+ // because it requires a GenericFamily to be plumbed through
166
+ // the `RangedStyle` `font_stack` from `resolve` where it is
167
+ // currently thrown away.
168
+ {}
169
+ }
170
+ // TODO: interpret variant="compact" without fallbackFor as a
171
+ // fallback for system-ui, as falling back to a
172
+ // variant="elegant" for system-ui can mess up a layout
173
+ // in some scripts.
174
+ }
175
+ _ => {}
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ Self {
182
+ name_map: Arc::new(name_map),
183
+ generic_families: Arc::new(generic_families),
184
+ family_map,
185
+ locale_fallback: locale_fallback.into(),
186
+ script_fallback: script_fallback.into(),
187
+ }
188
+ }
189
+
190
+ pub(crate) fn family(&self, id: FamilyId) -> Option<FamilyInfo> {
191
+ self.family_map.get(&id).cloned()
192
+ }
193
+
194
+ pub(crate) fn fallback(&self, key: impl Into<FallbackKey>) -> Option<FamilyId> {
195
+ let key: FallbackKey = key.into();
196
+ let script = key.script();
197
+
198
+ key.locale()
199
+ .and_then(|locale| {
200
+ self.locale_fallback
201
+ .iter()
202
+ .find(|(other, _)| locale == *other)
203
+ .map(|(_, fid)| *fid)
204
+ })
205
+ .or_else(|| {
206
+ self.script_fallback
207
+ .iter()
208
+ .find(|(s, _)| script == *s)
209
+ .map(|(_, fid)| *fid)
210
+ })
211
+ .or_else(|| {
212
+ self.generic_families
213
+ .get(GenericFamily::SansSerif)
214
+ .first()
215
+ .copied()
216
+ })
217
+ }
218
+ }
@@ -0,0 +1,206 @@
1
+ // Copyright 2024 the Parley Authors
2
+ // SPDX-License-Identifier: Apache-2.0 OR MIT
3
+
4
+ use super::{
5
+ FallbackKey, FamilyId, FamilyInfo, FamilyNameMap, GenericFamily, GenericFamilyMap, ScriptExt,
6
+ scan,
7
+ };
8
+ use alloc::format;
9
+ use alloc::string::ToString;
10
+ use alloc::sync::Arc;
11
+ use alloc::vec::Vec;
12
+ use core::ptr::{null, null_mut};
13
+ use hashbrown::{HashMap, HashSet};
14
+ use objc2_core_foundation::{
15
+ CFArray, CFDictionary, CFRange, CFRetained, CFString, CFType, CFURL, CFURLPathStyle,
16
+ };
17
+ use objc2_core_text::{
18
+ CTFont, CTFontCollection, CTFontDescriptor, CTFontUIFontType, kCTFontURLAttribute,
19
+ };
20
+ use objc2_foundation::{
21
+ NSSearchPathDirectory, NSSearchPathDomainMask, NSSearchPathForDirectoriesInDomains,
22
+ };
23
+ use parlance::Script;
24
+ use std::path::{Path, PathBuf};
25
+
26
+ const DEFAULT_GENERIC_FAMILIES: &[(GenericFamily, &[&str])] = &[
27
+ (GenericFamily::Serif, &["Times", "Times New Roman"]),
28
+ (GenericFamily::SansSerif, &["Helvetica"]),
29
+ (GenericFamily::Monospace, &["Courier", "Courier New"]),
30
+ (GenericFamily::Cursive, &["Apple Chancery"]),
31
+ (GenericFamily::Fantasy, &["Papyrus"]),
32
+ (GenericFamily::SystemUi, &["System Font", ".SF NS"]),
33
+ (GenericFamily::Emoji, &["Apple Color Emoji"]),
34
+ (GenericFamily::Math, &["STIX Two Math"]),
35
+ ];
36
+
37
+ pub(crate) struct SystemFonts {
38
+ pub(crate) name_map: Arc<FamilyNameMap>,
39
+ pub(crate) generic_families: Arc<GenericFamilyMap>,
40
+ family_map: HashMap<FamilyId, FamilyInfo>,
41
+ }
42
+
43
+ impl SystemFonts {
44
+ pub(crate) fn new() -> Self {
45
+ let scanned = scan_system_fonts().unwrap_or_default();
46
+ let name_map = scanned.family_names;
47
+ let mut generic_families = GenericFamilyMap::default();
48
+ for (family, names) in DEFAULT_GENERIC_FAMILIES {
49
+ generic_families.set(
50
+ *family,
51
+ names
52
+ .iter()
53
+ .filter_map(|name| name_map.get(name))
54
+ .map(|name| name.id()),
55
+ );
56
+ }
57
+ Self {
58
+ name_map: Arc::new(name_map),
59
+ generic_families: Arc::new(generic_families),
60
+ family_map: scanned.families,
61
+ }
62
+ }
63
+
64
+ pub(crate) fn family(&mut self, id: FamilyId) -> Option<FamilyInfo> {
65
+ self.family_map.get(&id).cloned()
66
+ }
67
+
68
+ pub(crate) fn fallback(&mut self, key: impl Into<FallbackKey>) -> Option<FamilyId> {
69
+ const HANI: Script = Script::from_bytes(*b"Hani");
70
+ const HANT: Script = Script::from_bytes(*b"Hant");
71
+ const HANS: Script = Script::from_bytes(*b"Hans");
72
+ let key = key.into();
73
+ let script = key.script();
74
+ let font = create_fallback_font_for_text(script.sample()?, key.locale_str(), false)?;
75
+ let family_name = unsafe { font.family_name() };
76
+ if let Some(family) = self.name_map.get(&family_name.to_string()) {
77
+ return Some(family.id());
78
+ }
79
+ // HACK: if we don't have a usable PingFangUI due to our inability
80
+ // to render hvgl outlines then try another font
81
+ let name = match script {
82
+ HANI | HANS => "Heiti SC",
83
+ HANT => "Heiti TC",
84
+ _ => return None,
85
+ };
86
+ self.name_map.get(name).map(|family| family.id())
87
+ }
88
+ }
89
+
90
+ /// Discover system fonts by combining CoreText enumeration with a directory scan of all
91
+ /// Library/Fonts paths, then index them through the shared scan pipeline.
92
+ fn scan_system_fonts() -> Option<scan::ScannedCollection> {
93
+ // SAFETY: Calls into CoreText. If anything fails we return None and use the fallback scan.
94
+ let collection = unsafe { CTFontCollection::from_available_fonts(None) };
95
+ let descriptors = unsafe { collection.matching_font_descriptors()? };
96
+ let descriptors: CFRetained<CFArray<CTFontDescriptor>> =
97
+ unsafe { CFRetained::cast_unchecked(descriptors) };
98
+
99
+ // Collect unique font file paths to avoid redundant scanning.
100
+ let mut paths: HashSet<PathBuf> = HashSet::new();
101
+ for index in 0..descriptors.len() {
102
+ let Some(descriptor) = descriptors.get(index) else {
103
+ continue;
104
+ };
105
+
106
+ let Some(url_cf): Option<CFRetained<CFType>> =
107
+ (unsafe { descriptor.attribute(kCTFontURLAttribute) })
108
+ else {
109
+ continue;
110
+ };
111
+
112
+ // The attribute is typed as CFType; attempt to downcast to CFURL.
113
+ let Ok(url_cf): Result<CFRetained<CFURL>, _> = url_cf.downcast::<CFURL>() else {
114
+ continue;
115
+ };
116
+
117
+ // Convert the file URL into a POSIX path.
118
+ let Some(path_cf): Option<CFRetained<CFString>> =
119
+ url_cf.file_system_path(CFURLPathStyle::CFURLPOSIXPathStyle)
120
+ else {
121
+ continue;
122
+ };
123
+
124
+ let path = PathBuf::from(path_cf.to_string());
125
+ if path.exists() {
126
+ paths.insert(path);
127
+ }
128
+ }
129
+
130
+ // Apple hides certain fonts from CTFontCollection (notably SFNS.ttf, the San Francisco
131
+ // system UI font). Scanning Library/Fonts directories catches what CoreText omits.
132
+ paths.extend(library_font_files());
133
+
134
+ if paths.is_empty() {
135
+ return None;
136
+ }
137
+
138
+ Some(scan::ScannedCollection::from_paths(paths.iter(), 0))
139
+ }
140
+
141
+ fn library_font_files() -> Vec<PathBuf> {
142
+ let mut files = Vec::new();
143
+ for dir in NSSearchPathForDirectoriesInDomains(
144
+ NSSearchPathDirectory::LibraryDirectory,
145
+ NSSearchPathDomainMask::AllDomainsMask,
146
+ true,
147
+ ) {
148
+ let font_dir = PathBuf::from(format!("{dir}/Fonts"));
149
+ if font_dir.is_dir() {
150
+ collect_files(&font_dir, 8, 0, &mut files);
151
+ }
152
+ }
153
+ files
154
+ }
155
+
156
+ fn collect_files(dir: &Path, max_depth: u32, depth: u32, out: &mut Vec<PathBuf>) {
157
+ let Ok(entries) = std::fs::read_dir(dir) else {
158
+ return;
159
+ };
160
+ for entry in entries.filter_map(|e| e.ok()) {
161
+ let path = entry.path();
162
+ if path.is_dir() {
163
+ if depth < max_depth {
164
+ collect_files(&path, max_depth, depth + 1, out);
165
+ }
166
+ } else {
167
+ out.push(path);
168
+ }
169
+ }
170
+ }
171
+
172
+ fn create_base_font(prefer_ui_font: bool) -> CFRetained<CTFont> {
173
+ if prefer_ui_font
174
+ && let Some(font) =
175
+ unsafe { CTFont::new_ui_font_for_language(CTFontUIFontType::System, 0.0, None) }
176
+ {
177
+ return font;
178
+ }
179
+ unsafe {
180
+ let attrs = CFDictionary::new(None, null_mut(), null_mut(), 0, null(), null());
181
+ let desc = CTFontDescriptor::with_attributes(&attrs.unwrap());
182
+ CTFont::with_font_descriptor(&desc, 0.0, null())
183
+ }
184
+ }
185
+
186
+ fn create_fallback_font_for_text(
187
+ text: &str,
188
+ locale: Option<&str>,
189
+ prefer_ui_font: bool,
190
+ ) -> Option<CFRetained<CTFont>> {
191
+ let text = CFString::from_str(text);
192
+ let text_range = CFRange {
193
+ location: 0,
194
+ length: text.length(),
195
+ };
196
+ let locale = locale.map(CFString::from_str);
197
+ let base_font = create_base_font(prefer_ui_font);
198
+ let font = unsafe {
199
+ if let Some(locale) = locale {
200
+ CTFont::for_string_with_language(&base_font, &text, text_range, Some(&locale))
201
+ } else {
202
+ CTFont::for_string(&base_font, &text, text_range)
203
+ }
204
+ };
205
+ Some(font)
206
+ }