blitz-pdf 0.1.3 → 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.
- package/README.md +6 -6
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/docs/layout-regressions.md +65 -0
- package/docs/pagination-regressions.md +7 -6
- package/package.json +1 -1
- package/vendor/README.md +35 -0
- package/vendor/blitz-dom/Cargo.toml +113 -0
- package/vendor/blitz-dom/LICENSE-APACHE +176 -0
- package/vendor/blitz-dom/LICENSE-MIT +23 -0
- package/vendor/blitz-dom/assets/default.css +1142 -0
- package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
- package/vendor/blitz-dom/src/accessibility.rs +255 -0
- package/vendor/blitz-dom/src/config.rs +97 -0
- package/vendor/blitz-dom/src/cssom.rs +971 -0
- package/vendor/blitz-dom/src/debug.rs +206 -0
- package/vendor/blitz-dom/src/document.rs +3395 -0
- package/vendor/blitz-dom/src/events/driver.rs +427 -0
- package/vendor/blitz-dom/src/events/focus.rs +40 -0
- package/vendor/blitz-dom/src/events/ime.rs +31 -0
- package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
- package/vendor/blitz-dom/src/events/mod.rs +315 -0
- package/vendor/blitz-dom/src/events/pointer.rs +849 -0
- package/vendor/blitz-dom/src/font_metrics.rs +177 -0
- package/vendor/blitz-dom/src/form.rs +463 -0
- package/vendor/blitz-dom/src/html.rs +38 -0
- package/vendor/blitz-dom/src/iframe.rs +186 -0
- package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
- package/vendor/blitz-dom/src/layout/damage.rs +642 -0
- package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
- package/vendor/blitz-dom/src/layout/list.rs +237 -0
- package/vendor/blitz-dom/src/layout/mod.rs +969 -0
- package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
- package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
- package/vendor/blitz-dom/src/layout/table.rs +967 -0
- package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
- package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
- package/vendor/blitz-dom/src/lib.rs +160 -0
- package/vendor/blitz-dom/src/mutator.rs +1818 -0
- package/vendor/blitz-dom/src/net.rs +649 -0
- package/vendor/blitz-dom/src/node/attributes.rs +60 -0
- package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
- package/vendor/blitz-dom/src/node/element.rs +1106 -0
- package/vendor/blitz-dom/src/node/mod.rs +30 -0
- package/vendor/blitz-dom/src/node/node.rs +2036 -0
- package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
- package/vendor/blitz-dom/src/node/serialize.rs +225 -0
- package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
- package/vendor/blitz-dom/src/node/svg.rs +265 -0
- package/vendor/blitz-dom/src/node/text.rs +804 -0
- package/vendor/blitz-dom/src/query_selector.rs +275 -0
- package/vendor/blitz-dom/src/resolve.rs +474 -0
- package/vendor/blitz-dom/src/resolved_style.rs +797 -0
- package/vendor/blitz-dom/src/scrolling.rs +780 -0
- package/vendor/blitz-dom/src/selection.rs +122 -0
- package/vendor/blitz-dom/src/stylo.rs +1518 -0
- package/vendor/blitz-dom/src/stylo_device.rs +88 -0
- package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
- package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
- package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
- package/vendor/blitz-dom/src/traversal.rs +600 -0
- package/vendor/blitz-dom/src/tree.rs +136 -0
- package/vendor/blitz-dom/src/url.rs +63 -0
- package/vendor/blitz-dom/src/util.rs +256 -0
- 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
|
+
}
|