blitz-pdf 0.1.1 → 0.1.3

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 (115) hide show
  1. package/README.md +292 -12
  2. package/THIRD_PARTY_NOTICES.md +4 -2
  3. package/dist/browser.d.ts +1 -1
  4. package/dist/browser.d.ts.map +1 -1
  5. package/dist/document-options.d.ts +36 -0
  6. package/dist/document-options.d.ts.map +1 -0
  7. package/dist/document-options.js +236 -0
  8. package/dist/document-options.js.map +1 -0
  9. package/dist/node.d.ts +1 -1
  10. package/dist/node.d.ts.map +1 -1
  11. package/dist/options.d.ts +35 -1
  12. package/dist/options.d.ts.map +1 -1
  13. package/dist/options.js +21 -14
  14. package/dist/options.js.map +1 -1
  15. package/dist/types.d.ts +71 -1
  16. package/dist/types.d.ts.map +1 -1
  17. package/dist/validation.d.ts +2 -0
  18. package/dist/validation.d.ts.map +1 -0
  19. package/dist/validation.js +7 -0
  20. package/dist/validation.js.map +1 -0
  21. package/docs/pagination-regressions.md +88 -0
  22. package/examples/zugferd.mjs +41 -0
  23. package/licenses/RUST_DEPENDENCIES.txt +27 -0
  24. package/package.json +4 -2
  25. package/src/browser.ts +1 -1
  26. package/src/document-options.ts +218 -0
  27. package/src/node.ts +1 -1
  28. package/src/options.ts +18 -13
  29. package/src/types.ts +76 -1
  30. package/src/validation.ts +6 -0
  31. package/vendor/README.md +27 -2
  32. package/vendor/krilla/.cargo-ok +1 -0
  33. package/vendor/krilla/.cargo_vcs_info.json +6 -0
  34. package/vendor/krilla/Cargo.lock +1357 -0
  35. package/vendor/krilla/Cargo.toml +165 -0
  36. package/vendor/krilla/LICENSE_APACHE +202 -0
  37. package/vendor/krilla/LICENSE_MIT +21 -0
  38. package/vendor/krilla/README.md +156 -0
  39. package/vendor/krilla/examples/basic.rs +117 -0
  40. package/vendor/krilla/examples/empty_document.rs +25 -0
  41. package/vendor/krilla/examples/parley.rs +174 -0
  42. package/vendor/krilla/examples/simple_text.rs +118 -0
  43. package/vendor/krilla/examples/stream_builder.rs +93 -0
  44. package/vendor/krilla/icc/LICENSE +116 -0
  45. package/vendor/krilla/icc/README.md +1 -0
  46. package/vendor/krilla/icc/sGrey-v2-magic.icc +0 -0
  47. package/vendor/krilla/icc/sGrey-v4.icc +0 -0
  48. package/vendor/krilla/icc/sRGB-v2-magic.icc +0 -0
  49. package/vendor/krilla/icc/sRGB-v4.icc +0 -0
  50. package/vendor/krilla/src/chunk_container.rs +503 -0
  51. package/vendor/krilla/src/configure/PDF_A1.md +227 -0
  52. package/vendor/krilla/src/configure/PDF_A2.md +269 -0
  53. package/vendor/krilla/src/configure/PDF_A3.md +31 -0
  54. package/vendor/krilla/src/configure/PDF_A4.md +211 -0
  55. package/vendor/krilla/src/configure/PDF_UA1.md +202 -0
  56. package/vendor/krilla/src/configure/README.md +23 -0
  57. package/vendor/krilla/src/configure/mod.rs +197 -0
  58. package/vendor/krilla/src/configure/validate.rs +1244 -0
  59. package/vendor/krilla/src/configure/version.rs +134 -0
  60. package/vendor/krilla/src/content.rs +1352 -0
  61. package/vendor/krilla/src/data.rs +49 -0
  62. package/vendor/krilla/src/document.rs +156 -0
  63. package/vendor/krilla/src/error.rs +170 -0
  64. package/vendor/krilla/src/geom.rs +341 -0
  65. package/vendor/krilla/src/graphics/blend.rs +46 -0
  66. package/vendor/krilla/src/graphics/color.rs +552 -0
  67. package/vendor/krilla/src/graphics/graphic.rs +30 -0
  68. package/vendor/krilla/src/graphics/graphics_state.rs +251 -0
  69. package/vendor/krilla/src/graphics/icc.rs +218 -0
  70. package/vendor/krilla/src/graphics/image.rs +835 -0
  71. package/vendor/krilla/src/graphics/mask.rs +128 -0
  72. package/vendor/krilla/src/graphics/mod.rs +14 -0
  73. package/vendor/krilla/src/graphics/paint.rs +434 -0
  74. package/vendor/krilla/src/graphics/separation.rs +86 -0
  75. package/vendor/krilla/src/graphics/shading_function.rs +865 -0
  76. package/vendor/krilla/src/graphics/shading_pattern.rs +60 -0
  77. package/vendor/krilla/src/graphics/tiling_pattern.rs +119 -0
  78. package/vendor/krilla/src/graphics/xobject.rs +150 -0
  79. package/vendor/krilla/src/interactive/action.rs +68 -0
  80. package/vendor/krilla/src/interactive/annotation.rs +283 -0
  81. package/vendor/krilla/src/interactive/destination.rs +289 -0
  82. package/vendor/krilla/src/interactive/mod.rs +3 -0
  83. package/vendor/krilla/src/interchange/embed.rs +240 -0
  84. package/vendor/krilla/src/interchange/metadata.rs +564 -0
  85. package/vendor/krilla/src/interchange/mod.rs +4 -0
  86. package/vendor/krilla/src/interchange/outline.rs +263 -0
  87. package/vendor/krilla/src/interchange/tagging/fmt.rs +733 -0
  88. package/vendor/krilla/src/interchange/tagging/generate.toml +474 -0
  89. package/vendor/krilla/src/interchange/tagging/generated.rs +4754 -0
  90. package/vendor/krilla/src/interchange/tagging/mod.rs +1324 -0
  91. package/vendor/krilla/src/interchange/tagging/tag.rs +686 -0
  92. package/vendor/krilla/src/lib.rs +179 -0
  93. package/vendor/krilla/src/num.rs +26 -0
  94. package/vendor/krilla/src/page.rs +612 -0
  95. package/vendor/krilla/src/pdf.rs +313 -0
  96. package/vendor/krilla/src/resource.rs +436 -0
  97. package/vendor/krilla/src/serialize.rs +1101 -0
  98. package/vendor/krilla/src/stream.rs +368 -0
  99. package/vendor/krilla/src/surface.rs +664 -0
  100. package/vendor/krilla/src/text/cid.rs +488 -0
  101. package/vendor/krilla/src/text/font.rs +351 -0
  102. package/vendor/krilla/src/text/glyph/bitmap.rs +81 -0
  103. package/vendor/krilla/src/text/glyph/colr.rs +581 -0
  104. package/vendor/krilla/src/text/glyph/mod.rs +205 -0
  105. package/vendor/krilla/src/text/glyph/outline.rs +72 -0
  106. package/vendor/krilla/src/text/glyph/svg.rs +34 -0
  107. package/vendor/krilla/src/text/group.rs +463 -0
  108. package/vendor/krilla/src/text/mod.rs +232 -0
  109. package/vendor/krilla/src/text/shape.rs +102 -0
  110. package/vendor/krilla/src/text/type3.rs +514 -0
  111. package/vendor/krilla/src/util.rs +286 -0
  112. package/vendor/stylo/properties/longhands.toml +0 -1
  113. package/vendor/stylo/stylesheets/rule_parser.rs +1 -1
  114. package/wasm/blitz_pdf.js +10 -5
  115. 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
+ }