blitz-pdf 0.1.5 → 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.
- package/README.md +61 -9
- package/THIRD_PARTY_NOTICES.md +20 -0
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/licenses/Noto-OFL.txt +94 -0
- package/licenses/NotoCJK-OFL.txt +92 -0
- package/licenses/NotoEmoji-OFL.txt +93 -0
- package/licenses/Unifont-OFL.txt +90 -0
- package/licenses/font-sources.json +106 -0
- package/package.json +1 -1
- package/src/types.ts +2 -1
- package/vendor/README.md +43 -0
- package/vendor/blitz-paint/Cargo.toml +50 -0
- package/vendor/blitz-paint/LICENSE-APACHE +176 -0
- package/vendor/blitz-paint/LICENSE-MIT +23 -0
- package/vendor/blitz-paint/src/color.rs +66 -0
- package/vendor/blitz-paint/src/debug_overlay.rs +168 -0
- package/vendor/blitz-paint/src/filters.rs +40 -0
- package/vendor/blitz-paint/src/gradient.rs +536 -0
- package/vendor/blitz-paint/src/kurbo_css/css_box.rs +847 -0
- package/vendor/blitz-paint/src/kurbo_css/mod.rs +77 -0
- package/vendor/blitz-paint/src/kurbo_css/non_uniform_radii.rs +60 -0
- package/vendor/blitz-paint/src/layers.rs +95 -0
- package/vendor/blitz-paint/src/lib.rs +190 -0
- package/vendor/blitz-paint/src/render/background.rs +968 -0
- package/vendor/blitz-paint/src/render/border.rs +631 -0
- package/vendor/blitz-paint/src/render/box_shadow.rs +157 -0
- package/vendor/blitz-paint/src/render/clip_path.rs +492 -0
- package/vendor/blitz-paint/src/render/form_controls.rs +103 -0
- package/vendor/blitz-paint/src/render/mask.rs +144 -0
- package/vendor/blitz-paint/src/render.rs +1351 -0
- package/vendor/blitz-paint/src/sizing.rs +63 -0
- package/vendor/blitz-paint/src/text.rs +747 -0
- package/vendor/fontique/Cargo.toml +194 -0
- package/vendor/fontique/LICENSE-APACHE +201 -0
- package/vendor/fontique/LICENSE-MIT +25 -0
- package/vendor/fontique/README.md +54 -0
- package/vendor/fontique/examples/generic_families.rs +37 -0
- package/vendor/fontique/src/attributes.rs +42 -0
- package/vendor/fontique/src/backend/android.rs +218 -0
- package/vendor/fontique/src/backend/coretext.rs +206 -0
- package/vendor/fontique/src/backend/dwrite.rs +395 -0
- package/vendor/fontique/src/backend/fontconfig.rs +844 -0
- package/vendor/fontique/src/backend/mod.rs +75 -0
- package/vendor/fontique/src/charmap.rs +127 -0
- package/vendor/fontique/src/collection/mod.rs +1016 -0
- package/vendor/fontique/src/collection/query.rs +392 -0
- package/vendor/fontique/src/fallback.rs +293 -0
- package/vendor/fontique/src/family.rs +111 -0
- package/vendor/fontique/src/family_name.rs +153 -0
- package/vendor/fontique/src/font.rs +562 -0
- package/vendor/fontique/src/generic.rs +36 -0
- package/vendor/fontique/src/impl_fontconfig.rs +159 -0
- package/vendor/fontique/src/lib.rs +87 -0
- package/vendor/fontique/src/matching.rs +483 -0
- package/vendor/fontique/src/scan.rs +312 -0
- package/vendor/fontique/src/script.rs +271 -0
- package/vendor/fontique/src/source.rs +108 -0
- package/vendor/fontique/src/source_cache.rs +254 -0
- package/wasm/blitz_pdf_bg.wasm +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
use super::ElementCx;
|
|
2
|
+
use crate::color::{Color, ToColorColor as _};
|
|
3
|
+
use anyrender::PaintScene;
|
|
4
|
+
use blitz_dom::local_name;
|
|
5
|
+
use kurbo::{Affine, BezPath, Cap, Circle, Join, Point, RoundedRect, Stroke, Vec2};
|
|
6
|
+
use peniko::Fill;
|
|
7
|
+
use style::dom::TElement as _;
|
|
8
|
+
|
|
9
|
+
impl ElementCx<'_, '_> {
|
|
10
|
+
pub(super) fn draw_input(&self, scene: &mut impl PaintScene) {
|
|
11
|
+
if self.node.local_name() != "input" {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
let Some(checked) = self.element.checkbox_input_checked() else {
|
|
15
|
+
return;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let type_attr = self.node.attr(local_name!("type"));
|
|
19
|
+
let disabled = self.node.attr(local_name!("disabled")).is_some();
|
|
20
|
+
|
|
21
|
+
// TODO this should be coming from css accent-color, but I couldn't find how to retrieve it
|
|
22
|
+
let accent_color = if disabled {
|
|
23
|
+
Color::from_rgba8(209, 209, 209, 255)
|
|
24
|
+
} else {
|
|
25
|
+
self.style.clone_color().as_srgb_color()
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let width = self.frame.border_box.width();
|
|
29
|
+
let height = self.frame.border_box.height();
|
|
30
|
+
let min_dimension = width.min(height);
|
|
31
|
+
let scale = (min_dimension - 4.0).max(0.0) / 16.0;
|
|
32
|
+
|
|
33
|
+
let frame = self.frame.border_box.to_rounded_rect(scale * 2.0);
|
|
34
|
+
|
|
35
|
+
match type_attr {
|
|
36
|
+
Some("checkbox") => {
|
|
37
|
+
draw_checkbox(scene, checked, frame, self.transform, accent_color, scale);
|
|
38
|
+
}
|
|
39
|
+
Some("radio") => {
|
|
40
|
+
let center = frame.center();
|
|
41
|
+
draw_radio_button(scene, checked, center, self.transform, accent_color, scale);
|
|
42
|
+
}
|
|
43
|
+
_ => {}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fn draw_checkbox(
|
|
49
|
+
scene: &mut impl PaintScene,
|
|
50
|
+
checked: bool,
|
|
51
|
+
frame: RoundedRect,
|
|
52
|
+
transform: Affine,
|
|
53
|
+
accent_color: Color,
|
|
54
|
+
scale: f64,
|
|
55
|
+
) {
|
|
56
|
+
if checked {
|
|
57
|
+
scene.fill(Fill::NonZero, transform, accent_color, None, &frame);
|
|
58
|
+
//Tick code derived from masonry
|
|
59
|
+
let mut path = BezPath::new();
|
|
60
|
+
path.move_to((2.0, 9.0));
|
|
61
|
+
path.line_to((6.0, 13.0));
|
|
62
|
+
path.line_to((14.0, 2.0));
|
|
63
|
+
|
|
64
|
+
path.apply_affine(Affine::translate(Vec2 { x: 2.0, y: 1.0 }).then_scale(scale));
|
|
65
|
+
|
|
66
|
+
let style = Stroke {
|
|
67
|
+
width: 2.0 * scale,
|
|
68
|
+
join: Join::Round,
|
|
69
|
+
miter_limit: 10.0,
|
|
70
|
+
start_cap: Cap::Round,
|
|
71
|
+
end_cap: Cap::Round,
|
|
72
|
+
dash_pattern: Default::default(),
|
|
73
|
+
dash_offset: 0.0,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
scene.stroke(&style, transform, Color::WHITE, None, &path);
|
|
77
|
+
} else {
|
|
78
|
+
scene.fill(Fill::NonZero, transform, Color::WHITE, None, &frame);
|
|
79
|
+
scene.stroke(&Stroke::default(), transform, accent_color, None, &frame);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fn draw_radio_button(
|
|
84
|
+
scene: &mut impl PaintScene,
|
|
85
|
+
checked: bool,
|
|
86
|
+
center: Point,
|
|
87
|
+
transform: Affine,
|
|
88
|
+
accent_color: Color,
|
|
89
|
+
scale: f64,
|
|
90
|
+
) {
|
|
91
|
+
let outer_ring = Circle::new(center, 8.0 * scale);
|
|
92
|
+
let gap = Circle::new(center, 6.0 * scale);
|
|
93
|
+
let inner_circle = Circle::new(center, 4.0 * scale);
|
|
94
|
+
if checked {
|
|
95
|
+
scene.fill(Fill::NonZero, transform, accent_color, None, &outer_ring);
|
|
96
|
+
scene.fill(Fill::NonZero, transform, Color::WHITE, None, &gap);
|
|
97
|
+
scene.fill(Fill::NonZero, transform, accent_color, None, &inner_circle);
|
|
98
|
+
} else {
|
|
99
|
+
const GRAY: Color = color::palette::css::GRAY;
|
|
100
|
+
scene.fill(Fill::NonZero, transform, GRAY, None, &outer_ring);
|
|
101
|
+
scene.fill(Fill::NonZero, transform, Color::WHITE, None, &gap);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
//! Rendering for the CSS `mask` properties (`mask-image`, `mask-position`,
|
|
2
|
+
//! `mask-size`, `mask-repeat`, `mask-clip`, `mask-origin`).
|
|
3
|
+
//!
|
|
4
|
+
//! Masks are applied by:
|
|
5
|
+
//!
|
|
6
|
+
//! 1. Pushing an isolation layer (clipped to the mask painting area) before the
|
|
7
|
+
//! element is painted
|
|
8
|
+
//! 2. Painting the element (and its descendants) as normal
|
|
9
|
+
//! 3. Pushing a `Compose::DestIn` layer and drawing the mask image layers into it,
|
|
10
|
+
//! which multiplies the alpha of the already-painted element by the alpha of
|
|
11
|
+
//! the mask
|
|
12
|
+
//! 4. Popping both layers
|
|
13
|
+
//!
|
|
14
|
+
//! The mask image layers themselves are positioned/sized/repeated using the same
|
|
15
|
+
//! code as `background-image` layers (see `background.rs`), as the `mask-*` and
|
|
16
|
+
//! `background-*` properties share computed value types.
|
|
17
|
+
use crate::render::background::get_cyclic;
|
|
18
|
+
|
|
19
|
+
use super::ElementCx;
|
|
20
|
+
use super::background::ImageLayerStyles;
|
|
21
|
+
use anyrender::PaintScene;
|
|
22
|
+
use peniko::{BlendMode, Compose, Fill, Mix};
|
|
23
|
+
use style::properties::generated::longhands::mask_composite::single_value::computed_value::T as StyloMaskComposite;
|
|
24
|
+
use style::values::generics::image::GenericImage;
|
|
25
|
+
|
|
26
|
+
#[cfg(feature = "tracing")]
|
|
27
|
+
use tracing::warn;
|
|
28
|
+
|
|
29
|
+
/// An opacity just below 1.0, used to force the renderer to allocate an isolated
|
|
30
|
+
/// buffer for the mask isolation layer. Renderers (e.g. vello_cpu) may optimize
|
|
31
|
+
/// layers with `Mix::Normal`/`Compose::SrcOver` and an opacity of exactly 1.0 into
|
|
32
|
+
/// plain non-isolated clip layers, in which case the `Compose::DestIn` mask
|
|
33
|
+
/// compositing would erase the backdrop behind the element rather than just the
|
|
34
|
+
/// element's own content.
|
|
35
|
+
const ALMOST_OPAQUE: f32 = 1.0 - f32::EPSILON;
|
|
36
|
+
|
|
37
|
+
impl ElementCx<'_, '_> {
|
|
38
|
+
/// Whether the element has any CSS `mask-image` layers
|
|
39
|
+
pub(super) fn has_css_mask(&self) -> bool {
|
|
40
|
+
self.style
|
|
41
|
+
.get_svg()
|
|
42
|
+
.mask_image
|
|
43
|
+
.0
|
|
44
|
+
.iter()
|
|
45
|
+
.any(|image| !matches!(image, GenericImage::None))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// If the element has a CSS mask, push an isolation layer for the masked content
|
|
49
|
+
/// to be drawn into. Returns whether a layer was pushed, which should later be
|
|
50
|
+
/// passed to [`maybe_pop_css_mask_layer`](Self::maybe_pop_css_mask_layer).
|
|
51
|
+
pub(super) fn maybe_push_css_mask_layer(&self, scene: &mut impl PaintScene) -> bool {
|
|
52
|
+
if !self.has_css_mask() {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Content outside of the mask painting area (at largest the border box) has
|
|
57
|
+
// a mask alpha of 0, so we can clip the isolation layer to the border box.
|
|
58
|
+
scene.push_layer(
|
|
59
|
+
Fill::NonZero,
|
|
60
|
+
Mix::Normal,
|
|
61
|
+
ALMOST_OPAQUE,
|
|
62
|
+
self.transform,
|
|
63
|
+
&self.frame.border_box_path(),
|
|
64
|
+
None,
|
|
65
|
+
None,
|
|
66
|
+
);
|
|
67
|
+
true
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Apply the CSS mask to the content drawn since the corresponding
|
|
71
|
+
/// [`maybe_push_css_mask_layer`](Self::maybe_push_css_mask_layer) by drawing the
|
|
72
|
+
/// mask image layers with `Compose::DestIn`, then pop the isolation layer.
|
|
73
|
+
pub(super) fn maybe_pop_css_mask_layer(&self, scene: &mut impl PaintScene, layer_pushed: bool) {
|
|
74
|
+
if !layer_pushed {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
scene.push_layer(
|
|
79
|
+
Fill::NonZero,
|
|
80
|
+
BlendMode::new(Mix::Normal, Compose::DestIn),
|
|
81
|
+
1.0,
|
|
82
|
+
self.transform,
|
|
83
|
+
&self.frame.border_box_path(),
|
|
84
|
+
None,
|
|
85
|
+
None,
|
|
86
|
+
);
|
|
87
|
+
self.draw_css_mask(scene);
|
|
88
|
+
scene.pop_layer(); // Mask (DestIn) layer
|
|
89
|
+
scene.pop_layer(); // Isolation layer
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Draw the mask image layers (analogous to `draw_background` for `background-image`)
|
|
93
|
+
fn draw_css_mask(&self, scene: &mut impl PaintScene) {
|
|
94
|
+
let svg_styles = self.style.get_svg();
|
|
95
|
+
let image_data = &self.element.mask_images;
|
|
96
|
+
let layer_count = svg_styles.mask_image.0.len();
|
|
97
|
+
|
|
98
|
+
for idx in (0..layer_count).rev() {
|
|
99
|
+
let layer = ImageLayerStyles::from_svg(svg_styles, image_data, idx);
|
|
100
|
+
let mask_clip_path = self.box_path(layer.clip);
|
|
101
|
+
|
|
102
|
+
// TODO: support `mask-mode: luminance` (luminance masks are currently
|
|
103
|
+
// rendered as alpha masks).
|
|
104
|
+
#[cfg(feature = "tracing")]
|
|
105
|
+
{
|
|
106
|
+
use style::properties::generated::longhands::mask_mode::single_value::computed_value::T as StyloMaskMode;
|
|
107
|
+
let mask_mode = &svg_styles.mask_mode.0;
|
|
108
|
+
if matches!(get_cyclic(&mask_mode, idx), StyloMaskMode::Luminance) {
|
|
109
|
+
warn!("mask-mode: luminance is not supported (falling back to alpha)");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Each mask layer is composited with the (already drawn) layers below it
|
|
114
|
+
// using the Porter-Duff operator given by its `mask-composite` value. The
|
|
115
|
+
// bottommost layer has no layers below it and is always drawn with SrcOver.
|
|
116
|
+
let compose = if idx == layer_count - 1 {
|
|
117
|
+
Compose::SrcOver
|
|
118
|
+
} else {
|
|
119
|
+
let composite_list = &svg_styles.mask_composite.0;
|
|
120
|
+
match get_cyclic(composite_list, idx) {
|
|
121
|
+
StyloMaskComposite::Add => Compose::SrcOver,
|
|
122
|
+
StyloMaskComposite::Subtract => Compose::SrcOut,
|
|
123
|
+
StyloMaskComposite::Intersect => Compose::SrcIn,
|
|
124
|
+
StyloMaskComposite::Exclude => Compose::Xor,
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// The layer is pushed unconditionally (even for `mask-image: none` layers,
|
|
129
|
+
// which draw nothing) as compositing a transparent black layer with e.g.
|
|
130
|
+
// `intersect` clears the mask built up so far.
|
|
131
|
+
scene.push_layer(
|
|
132
|
+
Fill::NonZero,
|
|
133
|
+
BlendMode::new(Mix::Normal, compose),
|
|
134
|
+
1.0,
|
|
135
|
+
self.transform,
|
|
136
|
+
&mask_clip_path,
|
|
137
|
+
None,
|
|
138
|
+
None,
|
|
139
|
+
);
|
|
140
|
+
self.draw_image_layer(scene, &layer);
|
|
141
|
+
scene.pop_layer();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|