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.
Files changed (64) hide show
  1. package/README.md +6 -6
  2. package/THIRD_PARTY_NOTICES.md +5 -2
  3. package/docs/layout-regressions.md +65 -0
  4. package/docs/pagination-regressions.md +7 -6
  5. package/package.json +1 -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,136 @@
1
+ //! Versioned storage for the nodes of the DOM tree.
2
+
3
+ use std::ops::{Index, IndexMut};
4
+ use std::sync::atomic::{AtomicU64, Ordering};
5
+
6
+ use blitz_traits::node_id::NodeId;
7
+ use slotmap::{Key as _, KeyData, SlotMap};
8
+
9
+ use crate::Node;
10
+
11
+ slotmap::new_key_type! {
12
+ /// The internal [`slotmap`] key for node storage. Only used at the
13
+ /// storage boundary: all public APIs use [`NodeId`].
14
+ struct NodeKey;
15
+ }
16
+
17
+ #[inline(always)]
18
+ fn to_key(id: NodeId) -> NodeKey {
19
+ NodeKey::from(KeyData::from_ffi(id.as_u64()))
20
+ }
21
+
22
+ #[inline(always)]
23
+ fn to_id(key: NodeKey) -> NodeId {
24
+ NodeId::from_u64(key.data().as_ffi())
25
+ }
26
+
27
+ /// The versioned map in which the nodes of the DOM tree are stored, backed by
28
+ /// a [`slotmap::SlotMap`].
29
+ ///
30
+ /// Nodes are addressed by [`NodeId`], which carries the slot's version in
31
+ /// addition to its index: when a node is dropped and its slot reused, ids
32
+ /// referring to the dropped node no longer resolve ([`NodeTree::get`] returns
33
+ /// `None`, and indexing panics) instead of aliasing the new occupant.
34
+ pub struct NodeTree {
35
+ map: SlotMap<NodeKey, Node>,
36
+ /// Bumped whenever box geometry may have moved (layout ran or a scroll
37
+ /// offset changed). Values derived from `final_layout()` (e.g. hoisted
38
+ /// paint child offsets) are memoised against it. Starts at 1 so that a
39
+ /// stamp of 0 is never current.
40
+ geometry_generation: AtomicU64,
41
+ /// Scroll offset of the viewport (the root element scrolls the viewport,
42
+ /// so its offset lives here rather than on the node).
43
+ viewport_scroll: crate::Point<f64>,
44
+ }
45
+
46
+ impl NodeTree {
47
+ pub(crate) fn new() -> Self {
48
+ Self {
49
+ map: SlotMap::with_key(),
50
+ geometry_generation: AtomicU64::new(1),
51
+ viewport_scroll: crate::Point::ZERO,
52
+ }
53
+ }
54
+
55
+ pub fn viewport_scroll(&self) -> crate::Point<f64> {
56
+ self.viewport_scroll
57
+ }
58
+
59
+ /// Memoised paint geometry may depend on the viewport scroll, so this
60
+ /// bumps the geometry generation like any other scroll write.
61
+ pub(crate) fn set_viewport_scroll(&mut self, scroll: crate::Point<f64>) {
62
+ self.viewport_scroll = scroll;
63
+ self.bump_geometry_generation();
64
+ }
65
+
66
+ /// See the `geometry_generation` field.
67
+ pub fn geometry_generation(&self) -> u64 {
68
+ self.geometry_generation.load(Ordering::Relaxed)
69
+ }
70
+
71
+ pub(crate) fn bump_geometry_generation(&self) {
72
+ self.geometry_generation.fetch_add(1, Ordering::Relaxed);
73
+ }
74
+
75
+ /// The number of live nodes in the map.
76
+ pub fn len(&self) -> usize {
77
+ self.map.len()
78
+ }
79
+
80
+ pub fn is_empty(&self) -> bool {
81
+ self.map.is_empty()
82
+ }
83
+
84
+ /// Whether `id` resolves to a live node.
85
+ pub fn contains_key(&self, id: NodeId) -> bool {
86
+ self.map.contains_key(to_key(id))
87
+ }
88
+
89
+ /// Get a reference to the node with the given id, if it is still live.
90
+ pub fn get(&self, id: NodeId) -> Option<&Node> {
91
+ self.map.get(to_key(id))
92
+ }
93
+
94
+ /// Get a mutable reference to the node with the given id, if it is still live.
95
+ pub fn get_mut(&mut self, id: NodeId) -> Option<&mut Node> {
96
+ self.map.get_mut(to_key(id))
97
+ }
98
+
99
+ /// Insert a node constructed with knowledge of its own id.
100
+ pub(crate) fn insert_with_key(&mut self, f: impl FnOnce(NodeId) -> Node) -> NodeId {
101
+ to_id(self.map.insert_with_key(|key| f(to_id(key))))
102
+ }
103
+
104
+ /// Remove the node with the given id, returning it if it was still live.
105
+ pub(crate) fn remove(&mut self, id: NodeId) -> Option<Node> {
106
+ self.map.remove(to_key(id))
107
+ }
108
+
109
+ /// Iterate over all live `(NodeId, &Node)` pairs.
110
+ pub fn iter(&self) -> impl Iterator<Item = (NodeId, &Node)> {
111
+ self.map.iter().map(|(key, node)| (to_id(key), node))
112
+ }
113
+
114
+ /// Iterate over all live `(NodeId, &mut Node)` pairs.
115
+ pub fn iter_mut(&mut self) -> impl Iterator<Item = (NodeId, &mut Node)> {
116
+ self.map.iter_mut().map(|(key, node)| (to_id(key), node))
117
+ }
118
+ }
119
+
120
+ impl Index<NodeId> for NodeTree {
121
+ type Output = Node;
122
+
123
+ #[track_caller]
124
+ #[inline]
125
+ fn index(&self, id: NodeId) -> &Node {
126
+ &self.map[to_key(id)]
127
+ }
128
+ }
129
+
130
+ impl IndexMut<NodeId> for NodeTree {
131
+ #[track_caller]
132
+ #[inline]
133
+ fn index_mut(&mut self, id: NodeId) -> &mut Node {
134
+ &mut self.map[to_key(id)]
135
+ }
136
+ }
@@ -0,0 +1,63 @@
1
+ use std::ops::Deref;
2
+ use std::str::FromStr;
3
+
4
+ use style::servo_arc::Arc as ServoArc;
5
+ use style::stylesheets::UrlExtraData;
6
+ use url::{Position, Url};
7
+
8
+ #[derive(Clone)]
9
+ pub(crate) struct DocumentUrl {
10
+ base_url: ServoArc<Url>,
11
+ }
12
+
13
+ impl DocumentUrl {
14
+ /// Create a stylo `UrlExtraData` from the URL
15
+ pub(crate) fn url_extra_data(&self) -> UrlExtraData {
16
+ UrlExtraData(ServoArc::clone(&self.base_url))
17
+ }
18
+
19
+ pub(crate) fn resolve_relative(&self, raw: &str) -> Option<url::Url> {
20
+ self.base_url.join(raw).ok()
21
+ }
22
+
23
+ /// Returns `true` if `other` refers to the same document as this URL, i.e. it is
24
+ /// identical to this URL except (possibly) for the fragment (`#...`) component.
25
+ ///
26
+ /// This is used to decide whether following a link should perform in-page fragment
27
+ /// navigation (scrolling) rather than a full navigation.
28
+ pub(crate) fn is_same_document(&self, other: &Url) -> bool {
29
+ let this: &Url = &self.base_url;
30
+ this[..Position::AfterQuery] == other[..Position::AfterQuery]
31
+ }
32
+ }
33
+
34
+ impl Default for DocumentUrl {
35
+ fn default() -> Self {
36
+ Self::from_str("data:text/css;charset=utf-8;base64,").unwrap()
37
+ }
38
+ }
39
+ impl FromStr for DocumentUrl {
40
+ type Err = <Url as FromStr>::Err;
41
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
42
+ let base_url = ServoArc::new(Url::parse(s)?);
43
+ Ok(Self { base_url })
44
+ }
45
+ }
46
+ impl From<Url> for DocumentUrl {
47
+ fn from(base_url: Url) -> Self {
48
+ Self {
49
+ base_url: ServoArc::new(base_url),
50
+ }
51
+ }
52
+ }
53
+ impl From<ServoArc<Url>> for DocumentUrl {
54
+ fn from(base_url: ServoArc<Url>) -> Self {
55
+ Self { base_url }
56
+ }
57
+ }
58
+ impl Deref for DocumentUrl {
59
+ type Target = Url;
60
+ fn deref(&self) -> &Self::Target {
61
+ &self.base_url
62
+ }
63
+ }
@@ -0,0 +1,256 @@
1
+ use crate::node::{Node, NodeData};
2
+ use color::{AlphaColor, Srgb};
3
+ use keyboard_types::Modifiers;
4
+ use std::borrow::Cow;
5
+ use style::color::AbsoluteColor;
6
+
7
+ #[cfg(target_os = "macos")]
8
+ pub(crate) const ACTION_MOD: Modifiers = Modifiers::SUPER;
9
+ #[cfg(not(target_os = "macos"))]
10
+ pub(crate) const ACTION_MOD: Modifiers = Modifiers::CONTROL;
11
+
12
+ pub type Color = AlphaColor<Srgb>;
13
+
14
+ /// Decode raw font bytes, decompressing WOFF/WOFF2 if the `woff` feature is enabled.
15
+ /// Returns the original slice unchanged for TTF/OTF input, and also on decompression
16
+ /// failure. With the `woff` feature disabled, all input passes through unchanged.
17
+ pub fn decode_font_bytes(bytes: &[u8]) -> Cow<'_, [u8]> {
18
+ if bytes.len() < 4 {
19
+ return Cow::Borrowed(bytes);
20
+ }
21
+ match &bytes[0..4] {
22
+ #[cfg(feature = "woff")]
23
+ b"wOFF" => wuff::decompress_woff1(bytes)
24
+ .map(Cow::Owned)
25
+ .unwrap_or_else(|_| {
26
+ #[cfg(feature = "tracing")]
27
+ tracing::warn!("Failed to decompress woff1 font");
28
+ Cow::Borrowed(bytes)
29
+ }),
30
+ #[cfg(feature = "woff")]
31
+ b"wOF2" => wuff::decompress_woff2(bytes)
32
+ .map(Cow::Owned)
33
+ .unwrap_or_else(|_| {
34
+ #[cfg(feature = "tracing")]
35
+ tracing::warn!("Failed to decompress woff2 font");
36
+ Cow::Borrowed(bytes)
37
+ }),
38
+ _ => Cow::Borrowed(bytes),
39
+ }
40
+ }
41
+
42
+ #[cfg(feature = "svg")]
43
+ use std::sync::{Arc, LazyLock};
44
+ #[cfg(feature = "svg")]
45
+ use usvg::fontdb;
46
+ #[cfg(feature = "svg")]
47
+ pub(crate) static FONT_DB: LazyLock<Arc<fontdb::Database>> = LazyLock::new(|| {
48
+ let mut db = fontdb::Database::new();
49
+ db.load_system_fonts();
50
+ Arc::new(db)
51
+ });
52
+
53
+ /// Which kind of CSS image layer list (`background-image` or `mask-image`) to
54
+ /// flush from style to dedicated storage on the node.
55
+ #[derive(Clone, Copy, Debug)]
56
+ pub enum ImageLayerKind {
57
+ Background,
58
+ Mask,
59
+ }
60
+
61
+ impl ImageLayerKind {
62
+ pub fn image_type(self, idx: usize) -> ImageType {
63
+ match self {
64
+ Self::Background => ImageType::Background(idx),
65
+ Self::Mask => ImageType::Mask(idx),
66
+ }
67
+ }
68
+ }
69
+
70
+ #[derive(Clone, Copy, Debug)]
71
+ pub enum ImageType {
72
+ Image,
73
+ Background(usize),
74
+ Mask(usize),
75
+ }
76
+
77
+ /// A point
78
+ #[derive(Clone, Debug, Copy, Eq, PartialEq)]
79
+ pub struct Point<T> {
80
+ /// The x coordinate
81
+ pub x: T,
82
+ /// The y coordinate
83
+ pub y: T,
84
+ }
85
+
86
+ impl Point<f64> {
87
+ pub const ZERO: Self = Point { x: 0.0, y: 0.0 };
88
+ }
89
+
90
+ // Debug print an RcDom
91
+ pub fn walk_tree(indent: usize, node: &Node) {
92
+ // Skip all-whitespace text nodes entirely
93
+ if let NodeData::Text(data) = &node.data {
94
+ if data.content.chars().all(|c| c.is_ascii_whitespace()) {
95
+ return;
96
+ }
97
+ }
98
+
99
+ print!("{}", " ".repeat(indent));
100
+ let id = node.id;
101
+ match &node.data {
102
+ NodeData::Document(_) => println!("#Document {id}"),
103
+
104
+ NodeData::Text(data) => {
105
+ if data.content.chars().all(|c| c.is_ascii_whitespace()) {
106
+ println!("{id} #text: <whitespace>");
107
+ } else {
108
+ let content = data.content.trim();
109
+ if content.len() > 10 {
110
+ println!(
111
+ "#text {id}: {}...",
112
+ content
113
+ .split_at(content.char_indices().take(10).last().unwrap().0)
114
+ .0
115
+ .escape_default()
116
+ )
117
+ } else {
118
+ println!("#text {id}: {}", data.content.trim().escape_default())
119
+ }
120
+ }
121
+ }
122
+
123
+ NodeData::Comment { .. } => println!("<!-- COMMENT {id} -->"),
124
+
125
+ NodeData::AnonymousBlock(_) => println!("{id} AnonymousBlock"),
126
+
127
+ NodeData::Element(data) => {
128
+ print!("<{} {id}", data.name.local);
129
+ for attr in data.attrs.iter() {
130
+ print!(" {}=\"{}\"", attr.name.local, attr.value);
131
+ }
132
+ if !node.children.is_empty() {
133
+ println!(">");
134
+ } else {
135
+ println!("/>");
136
+ }
137
+ } // NodeData::Doctype {
138
+ // ref name,
139
+ // ref public_id,
140
+ // ref system_id,
141
+ // } => println!("<!DOCTYPE {} \"{}\" \"{}\">", name, public_id, system_id),
142
+ // NodeData::ProcessingInstruction { .. } => unreachable!(),
143
+ }
144
+
145
+ if !node.children.is_empty() {
146
+ for child_id in node.children.iter() {
147
+ walk_tree(indent + 2, node.with(*child_id));
148
+ }
149
+
150
+ if let NodeData::Element(data) = &node.data {
151
+ println!("{}</{}>", " ".repeat(indent), data.name.local);
152
+ }
153
+ }
154
+ }
155
+
156
+ /// Parse an SVG image.
157
+ #[cfg(feature = "svg")]
158
+ pub(crate) fn parse_svg_image(source: &[u8]) -> Result<crate::node::SvgImageData, usvg::Error> {
159
+ let options = usvg::Options {
160
+ fontdb: Arc::clone(&*FONT_DB),
161
+ ..Default::default()
162
+ };
163
+ crate::node::SvgImageData::from_data(source, &options)
164
+ }
165
+
166
+ pub trait ToColorColor {
167
+ /// Converts a color into the `AlphaColor<Srgb>` type from the `color` crate
168
+ fn as_color_color(&self) -> Color;
169
+ }
170
+ impl ToColorColor for AbsoluteColor {
171
+ fn as_color_color(&self) -> Color {
172
+ Color::new(
173
+ *self
174
+ .to_color_space(style::color::ColorSpace::Srgb)
175
+ .raw_components(),
176
+ )
177
+ }
178
+ }
179
+
180
+ #[cfg(all(test, feature = "svg"))]
181
+ mod svg_tests {
182
+ use super::parse_svg_image;
183
+
184
+ #[test]
185
+ fn svg_without_xmlns_parses() {
186
+ let src = br#"<svg viewBox="0 0 24 24"><path d="M0 0h24v24H0z"/></svg>"#;
187
+ let svg = parse_svg_image(src).unwrap();
188
+ assert!(!svg.tree.root().children().is_empty());
189
+ }
190
+
191
+ #[test]
192
+ fn missing_height_is_computed_from_width_and_viewbox_ratio() {
193
+ let src = br#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="200"><rect width="100%" height="100%" fill="green"/></svg>"#;
194
+ let svg = parse_svg_image(src).unwrap();
195
+ assert_eq!(svg.intrinsic_width(), Some(200.0));
196
+ assert_eq!(svg.intrinsic_height(), None);
197
+ assert_eq!(svg.viewbox_aspect_ratio(), Some(1.0));
198
+ assert_eq!(svg.tree.size().width(), 200.0);
199
+ assert_eq!(svg.intrinsic_size(), (200.0, 200.0));
200
+ }
201
+
202
+ #[test]
203
+ fn viewbox_only_has_no_intrinsic_dimensions() {
204
+ let src = br#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 485 58"></svg>"#;
205
+ let svg = parse_svg_image(src).unwrap();
206
+ assert_eq!(svg.intrinsic_width(), None);
207
+ assert_eq!(svg.intrinsic_height(), None);
208
+ // The aspect ratio is still available from the viewBox.
209
+ assert!((svg.aspect_ratio() - (485.0 / 58.0)).abs() < 1e-3);
210
+ }
211
+
212
+ #[test]
213
+ fn absolute_dimensions_are_intrinsic() {
214
+ let src = br#"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="16" viewBox="0 0 48 32"></svg>"#;
215
+ let svg = parse_svg_image(src).unwrap();
216
+ assert_eq!(svg.intrinsic_width(), Some(24.0));
217
+ assert_eq!(svg.intrinsic_height(), Some(16.0));
218
+ }
219
+
220
+ #[test]
221
+ fn percentage_dimensions_are_not_intrinsic() {
222
+ let src = br#"<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="50%" viewBox="0 0 200 100"></svg>"#;
223
+ let svg = parse_svg_image(src).unwrap();
224
+ assert_eq!(svg.intrinsic_width(), None);
225
+ assert_eq!(svg.intrinsic_height(), None);
226
+ }
227
+
228
+ #[test]
229
+ fn unit_lengths_are_intrinsic() {
230
+ let src = br#"<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="1.5em" viewBox="0 0 48 32"></svg>"#;
231
+ let svg = parse_svg_image(src).unwrap();
232
+ assert!(svg.intrinsic_width().is_some());
233
+ assert!(svg.intrinsic_height().is_some());
234
+ }
235
+
236
+ #[test]
237
+ fn non_numeric_dimensions_are_not_intrinsic() {
238
+ let src = br#"<svg xmlns="http://www.w3.org/2000/svg" width="auto" height="foo" viewBox="0 0 200 100"></svg>"#;
239
+ let svg = parse_svg_image(src).unwrap();
240
+ assert_eq!(svg.intrinsic_width(), None);
241
+ assert_eq!(svg.intrinsic_height(), None);
242
+ }
243
+ }
244
+
245
+ /// Creates an markup5ever::QualName.
246
+ /// Given a local name and an optional namespace
247
+ #[macro_export]
248
+ macro_rules! qual_name {
249
+ ($local:tt $(, $ns:ident)?) => {
250
+ $crate::QualName {
251
+ prefix: None,
252
+ ns: $crate::ns!($($ns)?),
253
+ local: $crate::local_name!($local),
254
+ }
255
+ };
256
+ }
Binary file