blitz-pdf 0.1.2 → 0.1.4

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 (64) hide show
  1. package/README.md +26 -0
  2. package/THIRD_PARTY_NOTICES.md +5 -2
  3. package/docs/layout-regressions.md +65 -0
  4. package/docs/pagination-regressions.md +89 -0
  5. package/package.json +2 -1
  6. package/vendor/README.md +35 -0
  7. package/vendor/blitz-dom/Cargo.toml +113 -0
  8. package/vendor/blitz-dom/LICENSE-APACHE +176 -0
  9. package/vendor/blitz-dom/LICENSE-MIT +23 -0
  10. package/vendor/blitz-dom/assets/default.css +1142 -0
  11. package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
  12. package/vendor/blitz-dom/src/accessibility.rs +255 -0
  13. package/vendor/blitz-dom/src/config.rs +97 -0
  14. package/vendor/blitz-dom/src/cssom.rs +971 -0
  15. package/vendor/blitz-dom/src/debug.rs +206 -0
  16. package/vendor/blitz-dom/src/document.rs +3395 -0
  17. package/vendor/blitz-dom/src/events/driver.rs +427 -0
  18. package/vendor/blitz-dom/src/events/focus.rs +40 -0
  19. package/vendor/blitz-dom/src/events/ime.rs +31 -0
  20. package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
  21. package/vendor/blitz-dom/src/events/mod.rs +315 -0
  22. package/vendor/blitz-dom/src/events/pointer.rs +849 -0
  23. package/vendor/blitz-dom/src/font_metrics.rs +177 -0
  24. package/vendor/blitz-dom/src/form.rs +463 -0
  25. package/vendor/blitz-dom/src/html.rs +38 -0
  26. package/vendor/blitz-dom/src/iframe.rs +186 -0
  27. package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
  28. package/vendor/blitz-dom/src/layout/damage.rs +642 -0
  29. package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
  30. package/vendor/blitz-dom/src/layout/list.rs +237 -0
  31. package/vendor/blitz-dom/src/layout/mod.rs +969 -0
  32. package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
  33. package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
  34. package/vendor/blitz-dom/src/layout/table.rs +967 -0
  35. package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
  36. package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
  37. package/vendor/blitz-dom/src/lib.rs +160 -0
  38. package/vendor/blitz-dom/src/mutator.rs +1818 -0
  39. package/vendor/blitz-dom/src/net.rs +649 -0
  40. package/vendor/blitz-dom/src/node/attributes.rs +60 -0
  41. package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
  42. package/vendor/blitz-dom/src/node/element.rs +1106 -0
  43. package/vendor/blitz-dom/src/node/mod.rs +30 -0
  44. package/vendor/blitz-dom/src/node/node.rs +2036 -0
  45. package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
  46. package/vendor/blitz-dom/src/node/serialize.rs +225 -0
  47. package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
  48. package/vendor/blitz-dom/src/node/svg.rs +265 -0
  49. package/vendor/blitz-dom/src/node/text.rs +804 -0
  50. package/vendor/blitz-dom/src/query_selector.rs +275 -0
  51. package/vendor/blitz-dom/src/resolve.rs +474 -0
  52. package/vendor/blitz-dom/src/resolved_style.rs +797 -0
  53. package/vendor/blitz-dom/src/scrolling.rs +780 -0
  54. package/vendor/blitz-dom/src/selection.rs +122 -0
  55. package/vendor/blitz-dom/src/stylo.rs +1518 -0
  56. package/vendor/blitz-dom/src/stylo_device.rs +88 -0
  57. package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
  58. package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
  59. package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
  60. package/vendor/blitz-dom/src/traversal.rs +600 -0
  61. package/vendor/blitz-dom/src/tree.rs +136 -0
  62. package/vendor/blitz-dom/src/url.rs +63 -0
  63. package/vendor/blitz-dom/src/util.rs +256 -0
  64. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,265 @@
1
+ //! SVG image data and CSS intrinsic sizing for SVG.
2
+
3
+ use std::sync::Arc;
4
+
5
+ use usvg::roxmltree;
6
+
7
+ /// Dimensions declared on the root `<svg>` element, before any resolution.
8
+ ///
9
+ /// Unlike [`usvg::Tree::size`], which always produces a concrete size, this
10
+ /// preserves what the SVG actually declared: absent attributes are `None` and
11
+ /// percentage lengths are kept unresolved.
12
+ #[derive(Debug, Clone, Copy, Default)]
13
+ pub struct SvgIntrinsicDimensions {
14
+ /// The root `width` attribute, if declared. Percentages are unresolved.
15
+ pub width: Option<svgtypes::Length>,
16
+ /// The root `height` attribute, if declared. Percentages are unresolved.
17
+ pub height: Option<svgtypes::Length>,
18
+ /// The root `viewBox`, if declared and valid.
19
+ pub view_box: Option<usvg::NonZeroRect>,
20
+ /// The root `preserveAspectRatio` (default `xMidYMid meet`).
21
+ pub preserve_aspect_ratio: svgtypes::AspectRatio,
22
+ /// Whether the root declared a `viewBox` with a zero width or height,
23
+ /// which disables rendering of the element per the SVG spec.
24
+ pub degenerate_view_box: bool,
25
+ }
26
+
27
+ impl SvgIntrinsicDimensions {
28
+ /// Extract the `width`/`height`/`viewBox` attributes declared on the root
29
+ /// element of an already-parsed SVG document, with absent attributes as
30
+ /// `None` and percentages unresolved. [`usvg::Tree`] does not preserve
31
+ /// these (it always resolves to a concrete size), so they are read from
32
+ /// the XML document here.
33
+ pub fn from_xmltree(doc: &roxmltree::Document) -> Self {
34
+ let root = doc.root_element();
35
+
36
+ let parse_length = |name: &str| -> Option<svgtypes::Length> {
37
+ root.attribute(name)?.parse::<svgtypes::Length>().ok()
38
+ };
39
+ // Parsed manually rather than via `svgtypes::ViewBox`, which rejects
40
+ // zero sizes: a zero `viewBox` width/height is distinguished from an
41
+ // invalid `viewBox` as it disables rendering of the element.
42
+ let view_box_dims = root.attribute("viewBox").and_then(|s| {
43
+ let mut numbers = svgtypes::NumberListParser::from(s);
44
+ let x = numbers.next()?.ok()? as f32;
45
+ let y = numbers.next()?.ok()? as f32;
46
+ let w = numbers.next()?.ok()? as f32;
47
+ let h = numbers.next()?.ok()? as f32;
48
+ (numbers.next().is_none() && w.is_finite() && h.is_finite() && w >= 0.0 && h >= 0.0)
49
+ .then_some((x, y, w, h))
50
+ });
51
+ let view_box =
52
+ view_box_dims.and_then(|(x, y, w, h)| usvg::NonZeroRect::from_xywh(x, y, w, h));
53
+ let degenerate_view_box = view_box_dims.is_some_and(|(_, _, w, h)| w == 0.0 || h == 0.0);
54
+ let preserve_aspect_ratio = root
55
+ .attribute("preserveAspectRatio")
56
+ .and_then(|s| s.parse().ok())
57
+ .unwrap_or_default();
58
+
59
+ Self {
60
+ width: parse_length("width"),
61
+ height: parse_length("height"),
62
+ view_box,
63
+ preserve_aspect_ratio,
64
+ degenerate_view_box,
65
+ }
66
+ }
67
+ }
68
+
69
+ /// A parsed SVG image.
70
+ ///
71
+ /// usvg always resolves the root `<svg>` to a concrete [`usvg::Tree::size`],
72
+ /// falling back to the `viewBox` size when `width`/`height` are absent or given
73
+ /// as percentages. For CSS sizing purposes, however, such an SVG has *no*
74
+ /// intrinsic width/height (only an intrinsic aspect ratio). The accessors on
75
+ /// this type resolve the CSS intrinsic dimensions from the declared root
76
+ /// attributes, which are captured at parse time.
77
+ #[derive(Debug, Clone)]
78
+ pub struct SvgImageData {
79
+ /// The parsed SVG tree.
80
+ pub tree: Arc<usvg::Tree>,
81
+ /// The dimensions declared on the root `<svg>` element.
82
+ pub intrinsic_dimensions: SvgIntrinsicDimensions,
83
+ }
84
+
85
+ impl SvgImageData {
86
+ /// Parse an SVG image from raw data, capturing both the rendered
87
+ /// [`usvg::Tree`] and the declared root dimensions from a single XML
88
+ /// parse.
89
+ ///
90
+ /// Like [`usvg::Tree::from_data`], gzip-compressed data (SVGZ) is
91
+ /// decompressed first.
92
+ pub fn from_data(data: &[u8], options: &usvg::Options) -> Result<Self, usvg::Error> {
93
+ // Gzip magic bytes, matching the SVGZ detection in `usvg::Tree::from_data`.
94
+ let decompressed;
95
+ let data = if data.starts_with(&[0x1f, 0x8b]) {
96
+ decompressed = usvg::decompress_svgz(data)?;
97
+ decompressed.as_slice()
98
+ } else {
99
+ data
100
+ };
101
+
102
+ let text = std::str::from_utf8(data).map_err(|_| usvg::Error::NotAnUtf8Str)?;
103
+ let xml_options = roxmltree::ParsingOptions {
104
+ allow_dtd: true,
105
+ ..Default::default()
106
+ };
107
+ let doc = roxmltree::Document::parse_with_options(text, xml_options)
108
+ .map_err(usvg::Error::ParsingFailed)?;
109
+ let tree = usvg::Tree::from_xmltree(&doc, options)?;
110
+ Ok(Self {
111
+ tree: Arc::new(tree),
112
+ intrinsic_dimensions: SvgIntrinsicDimensions::from_xmltree(&doc),
113
+ })
114
+ }
115
+
116
+ /// The intrinsic width in CSS px, present only when the root `<svg>`
117
+ /// declared an absolute (non-percentage) `width`.
118
+ pub fn intrinsic_width(&self) -> Option<f32> {
119
+ use svgtypes::LengthUnit;
120
+ let declared = self
121
+ .intrinsic_dimensions
122
+ .width
123
+ .is_some_and(|len| len.unit != LengthUnit::Percent);
124
+ declared.then(|| self.tree.size().width())
125
+ }
126
+
127
+ /// The intrinsic height in CSS px, present only when the root `<svg>`
128
+ /// declared an absolute (non-percentage) `height`.
129
+ pub fn intrinsic_height(&self) -> Option<f32> {
130
+ use svgtypes::LengthUnit;
131
+ let declared = self
132
+ .intrinsic_dimensions
133
+ .height
134
+ .is_some_and(|len| len.unit != LengthUnit::Percent);
135
+ declared.then(|| self.tree.size().height())
136
+ }
137
+
138
+ /// The aspect ratio of the root `<svg>`'s `viewBox`, if it declares one.
139
+ pub fn viewbox_aspect_ratio(&self) -> Option<f32> {
140
+ self.intrinsic_dimensions
141
+ .view_box
142
+ .map(|view_box| view_box.width() / view_box.height())
143
+ }
144
+
145
+ /// The transform from the rendered tree (in [`usvg::Tree::size`] units) to a
146
+ /// viewport of the given size, applying the root `viewBox` and
147
+ /// `preserveAspectRatio`. Without a `viewBox` the tree is stretched to fill it.
148
+ pub fn viewport_transform(&self, width: f32, height: f32) -> usvg::Transform {
149
+ let size = self.tree.size();
150
+ let stretch = usvg::Transform::from_scale(width / size.width(), height / size.height());
151
+ let Some(view_box) = self.intrinsic_dimensions.view_box else {
152
+ return stretch;
153
+ };
154
+ let aspect = self.intrinsic_dimensions.preserve_aspect_ratio;
155
+ // usvg has already mapped the `viewBox` onto `Tree::size`, so undo that first.
156
+ let view_box_to_tree = view_box_transform(view_box, aspect, size.width(), size.height());
157
+ match view_box_to_tree.invert() {
158
+ Some(tree_to_view_box) => {
159
+ view_box_transform(view_box, aspect, width, height).pre_concat(tree_to_view_box)
160
+ }
161
+ None => stretch,
162
+ }
163
+ }
164
+
165
+ /// The root `width` attribute resolved against a containing block width:
166
+ /// percentages resolve against the containing block (`None` if it is
167
+ /// indefinite) and an absent attribute is `None`.
168
+ ///
169
+ /// This is only appropriate for an inline `<svg>` element, where the
170
+ /// attributes behave as presentation attributes. SVG used as an image
171
+ /// (e.g. `<img src>` or a background) must use [`Self::intrinsic_width`],
172
+ /// as its intrinsic dimensions are context-free per CSS.
173
+ pub fn resolved_width(&self, container_width: Option<f32>) -> Option<f32> {
174
+ use svgtypes::LengthUnit;
175
+ match self.intrinsic_dimensions.width {
176
+ Some(len) if len.unit != LengthUnit::Percent => Some(self.tree.size().width()),
177
+ Some(len) => container_width.map(|cw| cw * (len.number as f32) / 100.0),
178
+ None => None,
179
+ }
180
+ }
181
+
182
+ /// The root `height` attribute resolved against a containing block height.
183
+ /// See [`Self::resolved_width`].
184
+ pub fn resolved_height(&self, container_height: Option<f32>) -> Option<f32> {
185
+ use svgtypes::LengthUnit;
186
+ match self.intrinsic_dimensions.height {
187
+ Some(len) if len.unit != LengthUnit::Percent => Some(self.tree.size().height()),
188
+ Some(len) => container_height.map(|ch| ch * (len.number as f32) / 100.0),
189
+ None => None,
190
+ }
191
+ }
192
+
193
+ /// The intrinsic aspect ratio of the SVG: the ratio of its declared
194
+ /// `width`/`height` when both are absolute lengths, otherwise the
195
+ /// `viewBox` ratio, otherwise the ratio of the resolved
196
+ /// [`usvg::Tree::size`] (which is always non-zero).
197
+ pub fn aspect_ratio(&self) -> f32 {
198
+ match (self.intrinsic_width(), self.intrinsic_height()) {
199
+ (Some(w), Some(h)) => w / h,
200
+ _ => self.viewbox_aspect_ratio().unwrap_or_else(|| {
201
+ let size = self.tree.size();
202
+ size.width() / size.height()
203
+ }),
204
+ }
205
+ }
206
+
207
+ /// The intrinsic dimensions of the SVG resolved per CSS replaced element
208
+ /// sizing: a missing dimension is computed from the declared one and the
209
+ /// intrinsic aspect ratio; if neither is declared, the resolved
210
+ /// [`usvg::Tree::size`] is used as a fallback.
211
+ pub fn intrinsic_size(&self) -> (f32, f32) {
212
+ let aspect_ratio = self.aspect_ratio();
213
+ match (self.intrinsic_width(), self.intrinsic_height()) {
214
+ (Some(w), Some(h)) => (w, h),
215
+ (Some(w), None) => (w, w / aspect_ratio),
216
+ (None, Some(h)) => (h * aspect_ratio, h),
217
+ (None, None) => {
218
+ // No intrinsic dimensions. If there is an intrinsic aspect ratio, apply
219
+ // the CSS default sizing algorithm: contain within the default object
220
+ // size of 300x150. Otherwise fall back to the resolved tree size.
221
+ if self.viewbox_aspect_ratio().is_some() {
222
+ let scale = (300.0 / aspect_ratio).min(150.0);
223
+ (scale * aspect_ratio, scale)
224
+ } else {
225
+ let size = self.tree.size();
226
+ (size.width(), size.height())
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ /// Map a `viewBox` onto a viewport of the given size per `preserveAspectRatio`.
234
+ fn view_box_transform(
235
+ view_box: usvg::NonZeroRect,
236
+ aspect: svgtypes::AspectRatio,
237
+ width: f32,
238
+ height: f32,
239
+ ) -> usvg::Transform {
240
+ use svgtypes::Align;
241
+
242
+ let sx = width / view_box.width();
243
+ let sy = height / view_box.height();
244
+ let (sx, sy) = if aspect.align == Align::None {
245
+ (sx, sy)
246
+ } else if aspect.slice {
247
+ (sx.max(sy), sx.max(sy))
248
+ } else {
249
+ (sx.min(sy), sx.min(sy))
250
+ };
251
+ let (align_x, align_y) = match aspect.align {
252
+ Align::None | Align::XMinYMin => (0.0, 0.0),
253
+ Align::XMidYMin => (0.5, 0.0),
254
+ Align::XMaxYMin => (1.0, 0.0),
255
+ Align::XMinYMid => (0.0, 0.5),
256
+ Align::XMidYMid => (0.5, 0.5),
257
+ Align::XMaxYMid => (1.0, 0.5),
258
+ Align::XMinYMax => (0.0, 1.0),
259
+ Align::XMidYMax => (0.5, 1.0),
260
+ Align::XMaxYMax => (1.0, 1.0),
261
+ };
262
+ let tx = (width - view_box.width() * sx) * align_x - view_box.x() * sx;
263
+ let ty = (height - view_box.height() * sy) * align_y - view_box.y() * sy;
264
+ usvg::Transform::from_row(sx, 0.0, 0.0, sy, tx, ty)
265
+ }