blitz-pdf 0.1.1 → 0.1.2
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 +266 -12
- package/THIRD_PARTY_NOTICES.md +4 -2
- package/dist/browser.d.ts +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/document-options.d.ts +36 -0
- package/dist/document-options.d.ts.map +1 -0
- package/dist/document-options.js +236 -0
- package/dist/document-options.js.map +1 -0
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/options.d.ts +35 -1
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +21 -14
- package/dist/options.js.map +1 -1
- package/dist/types.d.ts +71 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts +2 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +7 -0
- package/dist/validation.js.map +1 -0
- package/examples/zugferd.mjs +41 -0
- package/licenses/RUST_DEPENDENCIES.txt +27 -0
- package/package.json +3 -2
- package/src/browser.ts +1 -1
- package/src/document-options.ts +218 -0
- package/src/node.ts +1 -1
- package/src/options.ts +18 -13
- package/src/types.ts +76 -1
- package/src/validation.ts +6 -0
- package/vendor/README.md +27 -2
- package/vendor/krilla/.cargo-ok +1 -0
- package/vendor/krilla/.cargo_vcs_info.json +6 -0
- package/vendor/krilla/Cargo.lock +1357 -0
- package/vendor/krilla/Cargo.toml +165 -0
- package/vendor/krilla/LICENSE_APACHE +202 -0
- package/vendor/krilla/LICENSE_MIT +21 -0
- package/vendor/krilla/README.md +156 -0
- package/vendor/krilla/examples/basic.rs +117 -0
- package/vendor/krilla/examples/empty_document.rs +25 -0
- package/vendor/krilla/examples/parley.rs +174 -0
- package/vendor/krilla/examples/simple_text.rs +118 -0
- package/vendor/krilla/examples/stream_builder.rs +93 -0
- package/vendor/krilla/icc/LICENSE +116 -0
- package/vendor/krilla/icc/README.md +1 -0
- package/vendor/krilla/icc/sGrey-v2-magic.icc +0 -0
- package/vendor/krilla/icc/sGrey-v4.icc +0 -0
- package/vendor/krilla/icc/sRGB-v2-magic.icc +0 -0
- package/vendor/krilla/icc/sRGB-v4.icc +0 -0
- package/vendor/krilla/src/chunk_container.rs +503 -0
- package/vendor/krilla/src/configure/PDF_A1.md +227 -0
- package/vendor/krilla/src/configure/PDF_A2.md +269 -0
- package/vendor/krilla/src/configure/PDF_A3.md +31 -0
- package/vendor/krilla/src/configure/PDF_A4.md +211 -0
- package/vendor/krilla/src/configure/PDF_UA1.md +202 -0
- package/vendor/krilla/src/configure/README.md +23 -0
- package/vendor/krilla/src/configure/mod.rs +197 -0
- package/vendor/krilla/src/configure/validate.rs +1244 -0
- package/vendor/krilla/src/configure/version.rs +134 -0
- package/vendor/krilla/src/content.rs +1352 -0
- package/vendor/krilla/src/data.rs +49 -0
- package/vendor/krilla/src/document.rs +156 -0
- package/vendor/krilla/src/error.rs +170 -0
- package/vendor/krilla/src/geom.rs +341 -0
- package/vendor/krilla/src/graphics/blend.rs +46 -0
- package/vendor/krilla/src/graphics/color.rs +552 -0
- package/vendor/krilla/src/graphics/graphic.rs +30 -0
- package/vendor/krilla/src/graphics/graphics_state.rs +251 -0
- package/vendor/krilla/src/graphics/icc.rs +218 -0
- package/vendor/krilla/src/graphics/image.rs +835 -0
- package/vendor/krilla/src/graphics/mask.rs +128 -0
- package/vendor/krilla/src/graphics/mod.rs +14 -0
- package/vendor/krilla/src/graphics/paint.rs +434 -0
- package/vendor/krilla/src/graphics/separation.rs +86 -0
- package/vendor/krilla/src/graphics/shading_function.rs +865 -0
- package/vendor/krilla/src/graphics/shading_pattern.rs +60 -0
- package/vendor/krilla/src/graphics/tiling_pattern.rs +119 -0
- package/vendor/krilla/src/graphics/xobject.rs +150 -0
- package/vendor/krilla/src/interactive/action.rs +68 -0
- package/vendor/krilla/src/interactive/annotation.rs +283 -0
- package/vendor/krilla/src/interactive/destination.rs +289 -0
- package/vendor/krilla/src/interactive/mod.rs +3 -0
- package/vendor/krilla/src/interchange/embed.rs +240 -0
- package/vendor/krilla/src/interchange/metadata.rs +564 -0
- package/vendor/krilla/src/interchange/mod.rs +4 -0
- package/vendor/krilla/src/interchange/outline.rs +263 -0
- package/vendor/krilla/src/interchange/tagging/fmt.rs +733 -0
- package/vendor/krilla/src/interchange/tagging/generate.toml +474 -0
- package/vendor/krilla/src/interchange/tagging/generated.rs +4754 -0
- package/vendor/krilla/src/interchange/tagging/mod.rs +1324 -0
- package/vendor/krilla/src/interchange/tagging/tag.rs +686 -0
- package/vendor/krilla/src/lib.rs +179 -0
- package/vendor/krilla/src/num.rs +26 -0
- package/vendor/krilla/src/page.rs +612 -0
- package/vendor/krilla/src/pdf.rs +313 -0
- package/vendor/krilla/src/resource.rs +436 -0
- package/vendor/krilla/src/serialize.rs +1101 -0
- package/vendor/krilla/src/stream.rs +368 -0
- package/vendor/krilla/src/surface.rs +664 -0
- package/vendor/krilla/src/text/cid.rs +488 -0
- package/vendor/krilla/src/text/font.rs +351 -0
- package/vendor/krilla/src/text/glyph/bitmap.rs +81 -0
- package/vendor/krilla/src/text/glyph/colr.rs +581 -0
- package/vendor/krilla/src/text/glyph/mod.rs +205 -0
- package/vendor/krilla/src/text/glyph/outline.rs +72 -0
- package/vendor/krilla/src/text/glyph/svg.rs +34 -0
- package/vendor/krilla/src/text/group.rs +463 -0
- package/vendor/krilla/src/text/mod.rs +232 -0
- package/vendor/krilla/src/text/shape.rs +102 -0
- package/vendor/krilla/src/text/type3.rs +514 -0
- package/vendor/krilla/src/util.rs +286 -0
- package/vendor/stylo/properties/longhands.toml +0 -1
- package/vendor/stylo/stylesheets/rule_parser.rs +1 -1
- package/wasm/blitz_pdf.js +10 -5
- package/wasm/blitz_pdf_bg.wasm +0 -0
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
use skrifa::color::{Brush, ColorPainter, ColorStop, CompositeMode};
|
|
2
|
+
use skrifa::outline::DrawSettings;
|
|
3
|
+
use skrifa::raw::types::BoundingBox;
|
|
4
|
+
use skrifa::raw::TableProvider;
|
|
5
|
+
use skrifa::MetadataProvider;
|
|
6
|
+
use tiny_skia_path::{Path, PathBuilder};
|
|
7
|
+
|
|
8
|
+
use crate::geom::Transform;
|
|
9
|
+
use crate::graphics::blend::BlendMode;
|
|
10
|
+
use crate::graphics::color::rgb;
|
|
11
|
+
use crate::graphics::mask::{Mask, MaskType};
|
|
12
|
+
use crate::graphics::paint::{
|
|
13
|
+
Fill, FillRule, LinearGradient, RadialGradient, SpreadMethod, Stop, SweepGradient,
|
|
14
|
+
};
|
|
15
|
+
use crate::num::NormalizedF32;
|
|
16
|
+
use crate::surface::Surface;
|
|
17
|
+
use crate::text::outline::OutlineBuilder;
|
|
18
|
+
use crate::text::Font;
|
|
19
|
+
use crate::text::GlyphId;
|
|
20
|
+
|
|
21
|
+
pub(crate) fn has_colr_data(font: &Font, glyph: GlyphId) -> bool {
|
|
22
|
+
font.font_ref()
|
|
23
|
+
.color_glyphs()
|
|
24
|
+
.get(glyph.to_skrifa())
|
|
25
|
+
.is_some()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Draw a COLR-based glyph on a surface.
|
|
29
|
+
pub(crate) fn draw_glyph(
|
|
30
|
+
font: Font,
|
|
31
|
+
context_color: rgb::Color,
|
|
32
|
+
glyph: GlyphId,
|
|
33
|
+
surface: &mut Surface,
|
|
34
|
+
) -> Option<()> {
|
|
35
|
+
// Drawing COLR glyphs is a bit tricky, because it's possible that an error
|
|
36
|
+
// occurs while we are drawing, in which case we cannot revert it anymore since
|
|
37
|
+
// we already drew the instructions onto the surface. Because of this, we first
|
|
38
|
+
// convert the glyph into a more accessible bytecode representation and only
|
|
39
|
+
// if that succeeds do we iterate over the bytecode to draw onto the canvas.
|
|
40
|
+
|
|
41
|
+
let colr_glyphs = font.font_ref().color_glyphs();
|
|
42
|
+
let colr_glyph = colr_glyphs.get(glyph.to_skrifa())?;
|
|
43
|
+
|
|
44
|
+
let mut colr_canvas = ColrBuilder::new(font.clone(), context_color);
|
|
45
|
+
colr_glyph
|
|
46
|
+
.paint(font.location_ref(), &mut colr_canvas)
|
|
47
|
+
.ok()?;
|
|
48
|
+
let instructions = colr_canvas.finish()?;
|
|
49
|
+
|
|
50
|
+
surface.push_transform(&Transform::from_scale(1.0, -1.0));
|
|
51
|
+
interpret(instructions, surface);
|
|
52
|
+
surface.pop();
|
|
53
|
+
|
|
54
|
+
Some(())
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Interpret the glyph bytecode
|
|
58
|
+
fn interpret(instructions: Vec<Instruction>, surface: &mut Surface) {
|
|
59
|
+
for instruction in instructions {
|
|
60
|
+
match instruction {
|
|
61
|
+
Instruction::Layer(mode, instructions) => {
|
|
62
|
+
let blend = mode.to_blend_mode().unwrap_or(BlendMode::Normal);
|
|
63
|
+
surface.push_blend_mode(blend);
|
|
64
|
+
surface.push_isolated();
|
|
65
|
+
interpret(instructions, surface);
|
|
66
|
+
surface.pop();
|
|
67
|
+
surface.pop();
|
|
68
|
+
}
|
|
69
|
+
Instruction::Composite {
|
|
70
|
+
source,
|
|
71
|
+
mode,
|
|
72
|
+
backdrop,
|
|
73
|
+
} => interpret_composite(source, mode, backdrop, surface),
|
|
74
|
+
Instruction::Filled(fill, mut clips) => {
|
|
75
|
+
let filled = clips.split_off(clips.len() - 1);
|
|
76
|
+
|
|
77
|
+
let num_clips = clips.len();
|
|
78
|
+
for path in clips {
|
|
79
|
+
surface.push_clip_path(&crate::geom::Path(path), &FillRule::NonZero);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let old_fill = surface.get_fill().cloned();
|
|
83
|
+
let old_stroke = surface.get_stroke().cloned();
|
|
84
|
+
|
|
85
|
+
surface.set_fill(Some(*fill));
|
|
86
|
+
surface.set_stroke(None);
|
|
87
|
+
|
|
88
|
+
surface.draw_path(&crate::geom::Path(filled[0].clone()));
|
|
89
|
+
|
|
90
|
+
surface.set_fill(old_fill);
|
|
91
|
+
surface.set_stroke(old_stroke);
|
|
92
|
+
|
|
93
|
+
for _ in 0..num_clips {
|
|
94
|
+
surface.pop();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fn interpret_composite(
|
|
102
|
+
source: Vec<Instruction>,
|
|
103
|
+
mode: CompositeMode,
|
|
104
|
+
backdrop: Vec<Instruction>,
|
|
105
|
+
surface: &mut Surface,
|
|
106
|
+
) {
|
|
107
|
+
match mode {
|
|
108
|
+
CompositeMode::Clear => {}
|
|
109
|
+
CompositeMode::Src => {
|
|
110
|
+
surface.push_isolated();
|
|
111
|
+
interpret(source, surface);
|
|
112
|
+
surface.pop();
|
|
113
|
+
}
|
|
114
|
+
CompositeMode::Dest => {
|
|
115
|
+
surface.push_isolated();
|
|
116
|
+
interpret(backdrop, surface);
|
|
117
|
+
surface.pop();
|
|
118
|
+
}
|
|
119
|
+
CompositeMode::SrcOver => {
|
|
120
|
+
surface.push_isolated();
|
|
121
|
+
interpret(backdrop, surface);
|
|
122
|
+
interpret(source, surface);
|
|
123
|
+
surface.pop();
|
|
124
|
+
}
|
|
125
|
+
CompositeMode::DestOver => {
|
|
126
|
+
surface.push_isolated();
|
|
127
|
+
interpret(source, surface);
|
|
128
|
+
interpret(backdrop, surface);
|
|
129
|
+
surface.pop();
|
|
130
|
+
}
|
|
131
|
+
CompositeMode::SrcIn => interpret_masked(source, backdrop, surface),
|
|
132
|
+
CompositeMode::DestIn => interpret_masked(backdrop, source, surface),
|
|
133
|
+
_ => {
|
|
134
|
+
let Some(blend) = mode.to_blend_mode() else {
|
|
135
|
+
surface.push_isolated();
|
|
136
|
+
interpret(backdrop, surface);
|
|
137
|
+
interpret(source, surface);
|
|
138
|
+
surface.pop();
|
|
139
|
+
return;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
surface.push_isolated();
|
|
143
|
+
interpret(backdrop, surface);
|
|
144
|
+
surface.push_blend_mode(blend);
|
|
145
|
+
surface.push_isolated();
|
|
146
|
+
interpret(source, surface);
|
|
147
|
+
surface.pop();
|
|
148
|
+
surface.pop();
|
|
149
|
+
surface.pop();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fn interpret_masked(visible: Vec<Instruction>, mask: Vec<Instruction>, surface: &mut Surface) {
|
|
155
|
+
let mask_stream = {
|
|
156
|
+
let mut builder = surface.stream_builder();
|
|
157
|
+
let mut mask_surface = builder.surface();
|
|
158
|
+
interpret(mask, &mut mask_surface);
|
|
159
|
+
mask_surface.finish();
|
|
160
|
+
builder.finish()
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
surface.push_mask(Mask::new(mask_stream, MaskType::Alpha));
|
|
164
|
+
interpret(visible, surface);
|
|
165
|
+
surface.pop();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// The context necessary for creating the bytecode of a COLR-based glyph.
|
|
169
|
+
struct ColrBuilder {
|
|
170
|
+
font: Font,
|
|
171
|
+
context_color: rgb::Color,
|
|
172
|
+
clips: Vec<Vec<Path>>,
|
|
173
|
+
stack: Vec<Vec<Instruction>>,
|
|
174
|
+
layers: Vec<CompositeMode>,
|
|
175
|
+
transforms: Vec<Transform>,
|
|
176
|
+
error: bool,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// A bytecode instruction for drawing a COLR glyph.
|
|
180
|
+
enum Instruction {
|
|
181
|
+
Layer(CompositeMode, Vec<Instruction>),
|
|
182
|
+
Composite {
|
|
183
|
+
source: Vec<Instruction>,
|
|
184
|
+
mode: CompositeMode,
|
|
185
|
+
backdrop: Vec<Instruction>,
|
|
186
|
+
},
|
|
187
|
+
Filled(Box<Fill>, Vec<Path>),
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
trait CompositeModeExt {
|
|
191
|
+
fn to_blend_mode(self) -> Option<BlendMode>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
impl CompositeModeExt for CompositeMode {
|
|
195
|
+
fn to_blend_mode(self) -> Option<BlendMode> {
|
|
196
|
+
match self {
|
|
197
|
+
CompositeMode::SrcOver => Some(BlendMode::Normal),
|
|
198
|
+
CompositeMode::Screen => Some(BlendMode::Screen),
|
|
199
|
+
CompositeMode::Overlay => Some(BlendMode::Overlay),
|
|
200
|
+
CompositeMode::Darken => Some(BlendMode::Darken),
|
|
201
|
+
CompositeMode::Lighten => Some(BlendMode::Lighten),
|
|
202
|
+
CompositeMode::ColorDodge => Some(BlendMode::ColorDodge),
|
|
203
|
+
CompositeMode::ColorBurn => Some(BlendMode::ColorBurn),
|
|
204
|
+
CompositeMode::HardLight => Some(BlendMode::HardLight),
|
|
205
|
+
CompositeMode::SoftLight => Some(BlendMode::SoftLight),
|
|
206
|
+
CompositeMode::Difference => Some(BlendMode::Difference),
|
|
207
|
+
CompositeMode::Exclusion => Some(BlendMode::Exclusion),
|
|
208
|
+
CompositeMode::Multiply => Some(BlendMode::Multiply),
|
|
209
|
+
CompositeMode::HslHue => Some(BlendMode::Hue),
|
|
210
|
+
CompositeMode::HslColor => Some(BlendMode::Color),
|
|
211
|
+
CompositeMode::HslLuminosity => Some(BlendMode::Luminosity),
|
|
212
|
+
CompositeMode::HslSaturation => Some(BlendMode::Saturation),
|
|
213
|
+
_ => None,
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
impl ColrBuilder {
|
|
219
|
+
pub fn new(font: Font, context_color: rgb::Color) -> Self {
|
|
220
|
+
Self {
|
|
221
|
+
font,
|
|
222
|
+
context_color,
|
|
223
|
+
stack: vec![vec![]],
|
|
224
|
+
transforms: vec![Transform::identity()],
|
|
225
|
+
clips: vec![vec![]],
|
|
226
|
+
layers: vec![],
|
|
227
|
+
error: false,
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
pub fn finish(mut self) -> Option<Vec<Instruction>> {
|
|
232
|
+
if self.error {
|
|
233
|
+
return None;
|
|
234
|
+
} else if let Some(instructions) = self.stack.pop() {
|
|
235
|
+
return Some(instructions);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
None
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
impl ColrBuilder {
|
|
243
|
+
fn palette_index_to_color(
|
|
244
|
+
&self,
|
|
245
|
+
palette_index: u16,
|
|
246
|
+
alpha: f32,
|
|
247
|
+
) -> Option<(rgb::Color, NormalizedF32)> {
|
|
248
|
+
if palette_index != u16::MAX {
|
|
249
|
+
let color = self
|
|
250
|
+
.font
|
|
251
|
+
.font_ref()
|
|
252
|
+
.cpal()
|
|
253
|
+
.ok()?
|
|
254
|
+
.color_records_array()?
|
|
255
|
+
.ok()?[palette_index as usize];
|
|
256
|
+
|
|
257
|
+
Some((
|
|
258
|
+
rgb::Color::new(color.red, color.green, color.blue),
|
|
259
|
+
NormalizedF32::new(alpha * color.alpha as f32 / 255.0)?,
|
|
260
|
+
))
|
|
261
|
+
} else {
|
|
262
|
+
Some((self.context_color, NormalizedF32::new(alpha)?))
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
fn stops(&self, stops: &[ColorStop]) -> Option<Vec<Stop>> {
|
|
267
|
+
let mut converted_stops = vec![];
|
|
268
|
+
|
|
269
|
+
for stop in stops {
|
|
270
|
+
let (color, alpha) = self.palette_index_to_color(stop.palette_index, stop.alpha)?;
|
|
271
|
+
|
|
272
|
+
converted_stops.push(Stop {
|
|
273
|
+
offset: NormalizedF32::new(stop.offset)?,
|
|
274
|
+
color: color.into(),
|
|
275
|
+
opacity: alpha,
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
Some(converted_stops)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
trait ExtendExt {
|
|
284
|
+
fn to_spread_method(&self) -> SpreadMethod;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
impl ExtendExt for skrifa::color::Extend {
|
|
288
|
+
fn to_spread_method(&self) -> SpreadMethod {
|
|
289
|
+
match self {
|
|
290
|
+
skrifa::color::Extend::Pad => SpreadMethod::Pad,
|
|
291
|
+
skrifa::color::Extend::Repeat => SpreadMethod::Repeat,
|
|
292
|
+
skrifa::color::Extend::Reflect => SpreadMethod::Reflect,
|
|
293
|
+
skrifa::color::Extend::Unknown => SpreadMethod::Pad,
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
impl ColorPainter for ColrBuilder {
|
|
299
|
+
fn push_transform(&mut self, transform: skrifa::color::Transform) {
|
|
300
|
+
let Some(last_transform) = self.transforms.last() else {
|
|
301
|
+
self.error = true;
|
|
302
|
+
return;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
let new_transform = last_transform.pre_concat(Transform::from_row(
|
|
306
|
+
transform.xx,
|
|
307
|
+
transform.yx,
|
|
308
|
+
transform.xy,
|
|
309
|
+
transform.yy,
|
|
310
|
+
transform.dx,
|
|
311
|
+
transform.dy,
|
|
312
|
+
));
|
|
313
|
+
self.transforms.push(new_transform);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
fn pop_transform(&mut self) {
|
|
317
|
+
let Some(_) = self.transforms.pop() else {
|
|
318
|
+
self.error = true;
|
|
319
|
+
return;
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
fn push_clip_glyph(&mut self, glyph_id: skrifa::GlyphId) {
|
|
324
|
+
let Some(mut old) = self.clips.last().cloned() else {
|
|
325
|
+
self.error = true;
|
|
326
|
+
return;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
let mut glyph_builder = OutlineBuilder::new();
|
|
330
|
+
let outline_glyphs = self.font.outline_glyphs();
|
|
331
|
+
let Some(outline_glyph) = outline_glyphs.get(glyph_id) else {
|
|
332
|
+
self.error = true;
|
|
333
|
+
return;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
let Ok(_) = outline_glyph.draw(
|
|
337
|
+
DrawSettings::unhinted(skrifa::instance::Size::unscaled(), self.font.location_ref()),
|
|
338
|
+
&mut glyph_builder,
|
|
339
|
+
) else {
|
|
340
|
+
self.error = true;
|
|
341
|
+
return;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
let Some(path) = glyph_builder
|
|
345
|
+
.finish()
|
|
346
|
+
.and_then(|p| p.transform(self.transforms.last()?.to_tsp()))
|
|
347
|
+
else {
|
|
348
|
+
self.error = true;
|
|
349
|
+
return;
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
old.push(path);
|
|
353
|
+
|
|
354
|
+
self.clips.push(old);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
fn push_clip_box(&mut self, clip_box: BoundingBox<f32>) {
|
|
358
|
+
let Some(mut old) = self.clips.last().cloned() else {
|
|
359
|
+
self.error = true;
|
|
360
|
+
return;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
let mut path_builder = PathBuilder::new();
|
|
364
|
+
path_builder.move_to(clip_box.x_min, clip_box.y_min);
|
|
365
|
+
path_builder.line_to(clip_box.x_min, clip_box.y_max);
|
|
366
|
+
path_builder.line_to(clip_box.x_max, clip_box.y_max);
|
|
367
|
+
path_builder.line_to(clip_box.x_max, clip_box.y_min);
|
|
368
|
+
path_builder.close();
|
|
369
|
+
|
|
370
|
+
let Some(path) = path_builder
|
|
371
|
+
.finish()
|
|
372
|
+
.and_then(|p| p.transform(self.transforms.last()?.to_tsp()))
|
|
373
|
+
else {
|
|
374
|
+
self.error = true;
|
|
375
|
+
return;
|
|
376
|
+
};
|
|
377
|
+
old.push(path);
|
|
378
|
+
|
|
379
|
+
self.clips.push(old);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
fn pop_clip(&mut self) {
|
|
383
|
+
let Some(_) = self.clips.pop() else {
|
|
384
|
+
self.error = true;
|
|
385
|
+
return;
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
fn fill(&mut self, brush: Brush<'_>) {
|
|
390
|
+
if let Some(fill) = match brush {
|
|
391
|
+
Brush::Solid {
|
|
392
|
+
palette_index,
|
|
393
|
+
alpha,
|
|
394
|
+
} => {
|
|
395
|
+
let (color, alpha) = match self.palette_index_to_color(palette_index, alpha) {
|
|
396
|
+
Some(c) => c,
|
|
397
|
+
None => {
|
|
398
|
+
self.error = true;
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
Some(Fill {
|
|
403
|
+
paint: color.into(),
|
|
404
|
+
opacity: alpha,
|
|
405
|
+
rule: Default::default(),
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
Brush::LinearGradient {
|
|
409
|
+
p0,
|
|
410
|
+
p1,
|
|
411
|
+
color_stops,
|
|
412
|
+
extend,
|
|
413
|
+
} => {
|
|
414
|
+
let stops = match self.stops(color_stops) {
|
|
415
|
+
Some(s) => s,
|
|
416
|
+
None => {
|
|
417
|
+
self.error = true;
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
let Some(transform) = self.transforms.last().copied() else {
|
|
423
|
+
self.error = true;
|
|
424
|
+
return;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
let linear = LinearGradient {
|
|
428
|
+
x1: p0.x,
|
|
429
|
+
y1: p0.y,
|
|
430
|
+
x2: p1.x,
|
|
431
|
+
y2: p1.y,
|
|
432
|
+
stops,
|
|
433
|
+
spread_method: extend.to_spread_method(),
|
|
434
|
+
transform,
|
|
435
|
+
anti_alias: false,
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
Some(Fill {
|
|
439
|
+
paint: linear.into(),
|
|
440
|
+
opacity: NormalizedF32::ONE,
|
|
441
|
+
rule: Default::default(),
|
|
442
|
+
})
|
|
443
|
+
}
|
|
444
|
+
Brush::RadialGradient {
|
|
445
|
+
c0,
|
|
446
|
+
r0,
|
|
447
|
+
c1,
|
|
448
|
+
r1,
|
|
449
|
+
color_stops,
|
|
450
|
+
extend,
|
|
451
|
+
} => {
|
|
452
|
+
let stops = match self.stops(color_stops) {
|
|
453
|
+
Some(s) => s,
|
|
454
|
+
None => {
|
|
455
|
+
self.error = true;
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
let Some(transform) = self.transforms.last().copied() else {
|
|
461
|
+
self.error = true;
|
|
462
|
+
return;
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
let radial = RadialGradient {
|
|
466
|
+
fx: c0.x,
|
|
467
|
+
fy: c0.y,
|
|
468
|
+
fr: r0,
|
|
469
|
+
cx: c1.x,
|
|
470
|
+
cy: c1.y,
|
|
471
|
+
cr: r1,
|
|
472
|
+
stops,
|
|
473
|
+
spread_method: extend.to_spread_method(),
|
|
474
|
+
transform,
|
|
475
|
+
anti_alias: false,
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
Some(Fill {
|
|
479
|
+
paint: radial.into(),
|
|
480
|
+
opacity: NormalizedF32::ONE,
|
|
481
|
+
rule: Default::default(),
|
|
482
|
+
})
|
|
483
|
+
}
|
|
484
|
+
Brush::SweepGradient {
|
|
485
|
+
c0,
|
|
486
|
+
start_angle,
|
|
487
|
+
end_angle,
|
|
488
|
+
color_stops,
|
|
489
|
+
extend,
|
|
490
|
+
} => {
|
|
491
|
+
let stops = match self.stops(color_stops) {
|
|
492
|
+
Some(s) => s,
|
|
493
|
+
None => {
|
|
494
|
+
self.error = true;
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
let Some(mut transform) = self.transforms.last().copied() else {
|
|
500
|
+
self.error = true;
|
|
501
|
+
return;
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// krilla sweep gradients go in a different direction than COLR, so we need
|
|
505
|
+
// to invert y-axis.
|
|
506
|
+
|
|
507
|
+
transform = transform.pre_concat(Transform::from_scale(1.0, -1.0));
|
|
508
|
+
|
|
509
|
+
let sweep = SweepGradient {
|
|
510
|
+
cx: c0.x,
|
|
511
|
+
cy: -c0.y,
|
|
512
|
+
start_angle,
|
|
513
|
+
end_angle,
|
|
514
|
+
stops,
|
|
515
|
+
spread_method: extend.to_spread_method(),
|
|
516
|
+
transform,
|
|
517
|
+
anti_alias: false,
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
Some(Fill {
|
|
521
|
+
paint: sweep.into(),
|
|
522
|
+
opacity: NormalizedF32::ONE,
|
|
523
|
+
rule: Default::default(),
|
|
524
|
+
})
|
|
525
|
+
}
|
|
526
|
+
} {
|
|
527
|
+
// The proper implementation would be to apply all clip paths and then draw the
|
|
528
|
+
// whole "visible" area with the fill. However, this seems to produce artifacts in
|
|
529
|
+
// Google Chrome when zooming. So instead, what we do is that we apply all clip paths except
|
|
530
|
+
// for the last one, and the last one we use to actually perform the fill.
|
|
531
|
+
let Some(clips) = self.clips.last().map(|paths| paths.to_vec()) else {
|
|
532
|
+
self.error = true;
|
|
533
|
+
return;
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
let Some(stack) = self.stack.last_mut() else {
|
|
537
|
+
self.error = true;
|
|
538
|
+
return;
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
stack.push(Instruction::Filled(Box::new(fill), clips));
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
fn push_layer(&mut self, composite_mode: CompositeMode) {
|
|
546
|
+
self.layers.push(composite_mode);
|
|
547
|
+
self.stack.push(vec![]);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
fn pop_layer(&mut self) {
|
|
551
|
+
let (Some(composite), Some(mut instructions)) = (self.layers.pop(), self.stack.pop())
|
|
552
|
+
else {
|
|
553
|
+
self.error = true;
|
|
554
|
+
return;
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
let Some(stack) = self.stack.last_mut() else {
|
|
558
|
+
self.error = true;
|
|
559
|
+
return;
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// See the skrifa code to see how it handles composite operations.
|
|
563
|
+
// https://github.com/googlefonts/fontations/blob/fd0178ce4ea48301a7f06b35a6fce9879c102292/skrifa/src/color/traversal.rs#L566-L593
|
|
564
|
+
// Everything is wrapped in a `SrcOver` base layer.
|
|
565
|
+
if composite == CompositeMode::SrcOver {
|
|
566
|
+
if let Some(Instruction::Layer(_, _)) = instructions.last() {
|
|
567
|
+
let Some(Instruction::Layer(mode, source)) = instructions.pop() else {
|
|
568
|
+
unreachable!();
|
|
569
|
+
};
|
|
570
|
+
stack.push(Instruction::Composite {
|
|
571
|
+
source,
|
|
572
|
+
mode,
|
|
573
|
+
backdrop: instructions,
|
|
574
|
+
});
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
stack.push(Instruction::Layer(composite, instructions));
|
|
580
|
+
}
|
|
581
|
+
}
|