blitz-pdf 0.1.0 → 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.
Files changed (114) hide show
  1. package/README.md +300 -10
  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/examples/zugferd.mjs +41 -0
  22. package/licenses/RUST_DEPENDENCIES.txt +27 -0
  23. package/package.json +3 -2
  24. package/src/browser.ts +1 -1
  25. package/src/document-options.ts +218 -0
  26. package/src/node.ts +1 -1
  27. package/src/options.ts +18 -13
  28. package/src/types.ts +76 -1
  29. package/src/validation.ts +6 -0
  30. package/vendor/README.md +27 -2
  31. package/vendor/krilla/.cargo-ok +1 -0
  32. package/vendor/krilla/.cargo_vcs_info.json +6 -0
  33. package/vendor/krilla/Cargo.lock +1357 -0
  34. package/vendor/krilla/Cargo.toml +165 -0
  35. package/vendor/krilla/LICENSE_APACHE +202 -0
  36. package/vendor/krilla/LICENSE_MIT +21 -0
  37. package/vendor/krilla/README.md +156 -0
  38. package/vendor/krilla/examples/basic.rs +117 -0
  39. package/vendor/krilla/examples/empty_document.rs +25 -0
  40. package/vendor/krilla/examples/parley.rs +174 -0
  41. package/vendor/krilla/examples/simple_text.rs +118 -0
  42. package/vendor/krilla/examples/stream_builder.rs +93 -0
  43. package/vendor/krilla/icc/LICENSE +116 -0
  44. package/vendor/krilla/icc/README.md +1 -0
  45. package/vendor/krilla/icc/sGrey-v2-magic.icc +0 -0
  46. package/vendor/krilla/icc/sGrey-v4.icc +0 -0
  47. package/vendor/krilla/icc/sRGB-v2-magic.icc +0 -0
  48. package/vendor/krilla/icc/sRGB-v4.icc +0 -0
  49. package/vendor/krilla/src/chunk_container.rs +503 -0
  50. package/vendor/krilla/src/configure/PDF_A1.md +227 -0
  51. package/vendor/krilla/src/configure/PDF_A2.md +269 -0
  52. package/vendor/krilla/src/configure/PDF_A3.md +31 -0
  53. package/vendor/krilla/src/configure/PDF_A4.md +211 -0
  54. package/vendor/krilla/src/configure/PDF_UA1.md +202 -0
  55. package/vendor/krilla/src/configure/README.md +23 -0
  56. package/vendor/krilla/src/configure/mod.rs +197 -0
  57. package/vendor/krilla/src/configure/validate.rs +1244 -0
  58. package/vendor/krilla/src/configure/version.rs +134 -0
  59. package/vendor/krilla/src/content.rs +1352 -0
  60. package/vendor/krilla/src/data.rs +49 -0
  61. package/vendor/krilla/src/document.rs +156 -0
  62. package/vendor/krilla/src/error.rs +170 -0
  63. package/vendor/krilla/src/geom.rs +341 -0
  64. package/vendor/krilla/src/graphics/blend.rs +46 -0
  65. package/vendor/krilla/src/graphics/color.rs +552 -0
  66. package/vendor/krilla/src/graphics/graphic.rs +30 -0
  67. package/vendor/krilla/src/graphics/graphics_state.rs +251 -0
  68. package/vendor/krilla/src/graphics/icc.rs +218 -0
  69. package/vendor/krilla/src/graphics/image.rs +835 -0
  70. package/vendor/krilla/src/graphics/mask.rs +128 -0
  71. package/vendor/krilla/src/graphics/mod.rs +14 -0
  72. package/vendor/krilla/src/graphics/paint.rs +434 -0
  73. package/vendor/krilla/src/graphics/separation.rs +86 -0
  74. package/vendor/krilla/src/graphics/shading_function.rs +865 -0
  75. package/vendor/krilla/src/graphics/shading_pattern.rs +60 -0
  76. package/vendor/krilla/src/graphics/tiling_pattern.rs +119 -0
  77. package/vendor/krilla/src/graphics/xobject.rs +150 -0
  78. package/vendor/krilla/src/interactive/action.rs +68 -0
  79. package/vendor/krilla/src/interactive/annotation.rs +283 -0
  80. package/vendor/krilla/src/interactive/destination.rs +289 -0
  81. package/vendor/krilla/src/interactive/mod.rs +3 -0
  82. package/vendor/krilla/src/interchange/embed.rs +240 -0
  83. package/vendor/krilla/src/interchange/metadata.rs +564 -0
  84. package/vendor/krilla/src/interchange/mod.rs +4 -0
  85. package/vendor/krilla/src/interchange/outline.rs +263 -0
  86. package/vendor/krilla/src/interchange/tagging/fmt.rs +733 -0
  87. package/vendor/krilla/src/interchange/tagging/generate.toml +474 -0
  88. package/vendor/krilla/src/interchange/tagging/generated.rs +4754 -0
  89. package/vendor/krilla/src/interchange/tagging/mod.rs +1324 -0
  90. package/vendor/krilla/src/interchange/tagging/tag.rs +686 -0
  91. package/vendor/krilla/src/lib.rs +179 -0
  92. package/vendor/krilla/src/num.rs +26 -0
  93. package/vendor/krilla/src/page.rs +612 -0
  94. package/vendor/krilla/src/pdf.rs +313 -0
  95. package/vendor/krilla/src/resource.rs +436 -0
  96. package/vendor/krilla/src/serialize.rs +1101 -0
  97. package/vendor/krilla/src/stream.rs +368 -0
  98. package/vendor/krilla/src/surface.rs +664 -0
  99. package/vendor/krilla/src/text/cid.rs +488 -0
  100. package/vendor/krilla/src/text/font.rs +351 -0
  101. package/vendor/krilla/src/text/glyph/bitmap.rs +81 -0
  102. package/vendor/krilla/src/text/glyph/colr.rs +581 -0
  103. package/vendor/krilla/src/text/glyph/mod.rs +205 -0
  104. package/vendor/krilla/src/text/glyph/outline.rs +72 -0
  105. package/vendor/krilla/src/text/glyph/svg.rs +34 -0
  106. package/vendor/krilla/src/text/group.rs +463 -0
  107. package/vendor/krilla/src/text/mod.rs +232 -0
  108. package/vendor/krilla/src/text/shape.rs +102 -0
  109. package/vendor/krilla/src/text/type3.rs +514 -0
  110. package/vendor/krilla/src/util.rs +286 -0
  111. package/vendor/stylo/properties/longhands.toml +0 -1
  112. package/vendor/stylo/stylesheets/rule_parser.rs +1 -1
  113. package/wasm/blitz_pdf.js +10 -5
  114. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,865 @@
1
+ use std::hash::{Hash, Hasher};
2
+ use std::ops::DerefMut;
3
+ use std::sync::Arc;
4
+
5
+ use bumpalo::Bump;
6
+ use pdf_writer::types::{FunctionShadingType, PostScriptOp};
7
+ use pdf_writer::{Chunk, Finish, Ref};
8
+ use tiny_skia_path::Point;
9
+
10
+ use crate::chunk_container::ChunkContainer;
11
+ use crate::configure::ValidationError;
12
+ use crate::geom::{Rect, Transform};
13
+ use crate::graphics::color::luma;
14
+ use crate::graphics::color::{Color, ColorSpace};
15
+ use crate::graphics::paint::{LinearGradient, RadialGradient, SweepGradient};
16
+ use crate::graphics::paint::{SpreadMethod, Stop};
17
+ use crate::num::NormalizedF32;
18
+ use crate::resource;
19
+ use crate::resource::Resourceable;
20
+ use crate::serialize::{Cacheable, MaybeDeviceColorSpace, SerializeContext};
21
+ use crate::stream::FilterStreamBuilder;
22
+ use crate::util::set_colorspace;
23
+
24
+ #[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
25
+ pub(crate) enum GradientType {
26
+ Sweep,
27
+ Linear,
28
+ }
29
+
30
+ #[derive(Debug, PartialEq, Clone)]
31
+ pub(crate) struct RadialAxialGradient {
32
+ pub(crate) coords: Vec<f32>,
33
+ pub(crate) shading_type: FunctionShadingType,
34
+ pub(crate) stops: Vec<Stop>,
35
+ pub(crate) anti_alias: bool,
36
+ }
37
+
38
+ impl Eq for RadialAxialGradient {}
39
+
40
+ impl Hash for RadialAxialGradient {
41
+ fn hash<H: Hasher>(&self, state: &mut H) {
42
+ for el in &self.coords {
43
+ el.to_bits().hash(state);
44
+ }
45
+
46
+ self.shading_type.hash(state);
47
+ self.stops.hash(state);
48
+ self.anti_alias.hash(state);
49
+ }
50
+ }
51
+
52
+ #[derive(Debug, PartialEq, Clone)]
53
+ pub(crate) struct PostScriptGradient {
54
+ pub(crate) min: f32,
55
+ pub(crate) max: f32,
56
+ // Only used for sweep gradient
57
+ pub(crate) cx: f32,
58
+ // Only used for sweep gradient
59
+ pub(crate) cy: f32,
60
+ pub(crate) stops: Vec<Stop>,
61
+ pub(crate) domain: Rect,
62
+ pub(crate) spread_method: SpreadMethod,
63
+ pub(crate) gradient_type: GradientType,
64
+ pub(crate) anti_alias: bool,
65
+ }
66
+
67
+ impl Eq for PostScriptGradient {}
68
+
69
+ impl Hash for PostScriptGradient {
70
+ fn hash<H: Hasher>(&self, state: &mut H) {
71
+ self.min.to_bits().hash(state);
72
+ self.max.to_bits().hash(state);
73
+ self.stops.hash(state);
74
+ self.domain.hash(state);
75
+ self.spread_method.hash(state);
76
+ self.gradient_type.hash(state);
77
+ self.anti_alias.hash(state);
78
+ }
79
+ }
80
+
81
+ #[derive(Debug, Hash, Eq, PartialEq, Clone)]
82
+ pub(crate) enum GradientProperties {
83
+ RadialAxialGradient(RadialAxialGradient),
84
+ PostScriptGradient(PostScriptGradient),
85
+ }
86
+
87
+ impl GradientProperties {
88
+ // Check if the gradient could be encoded as a solid fill instead.
89
+ pub(crate) fn single_stop_color(&self) -> Option<(&Color, NormalizedF32)> {
90
+ match self {
91
+ GradientProperties::RadialAxialGradient(rag) => {
92
+ if rag.stops.len() == 1 {
93
+ return Some((&rag.stops[0].color, rag.stops[0].opacity));
94
+ }
95
+ }
96
+ GradientProperties::PostScriptGradient(psg) => {
97
+ if psg.stops.len() == 1 {
98
+ return Some((&psg.stops[0].color, psg.stops[0].opacity));
99
+ }
100
+ }
101
+ }
102
+
103
+ None
104
+ }
105
+ }
106
+
107
+ pub(crate) trait GradientPropertiesExt {
108
+ fn gradient_properties(self, bbox: Rect) -> (GradientProperties, Transform);
109
+ }
110
+
111
+ fn get_expanded_bbox(mut bbox: Rect, shading_transform: Transform) -> Rect {
112
+ // We need to make sure the shading covers the whole bbox of the object after
113
+ // the transform as been applied. In order to know that, we need to calculate the
114
+ // resulting bbox from the inverted transform.
115
+ bbox.expand(&bbox.transform(shading_transform.invert().unwrap()).unwrap());
116
+ bbox
117
+ }
118
+
119
+ /// When writing a PostScript shading, we assume that both points are on a horizontal
120
+ /// line. Here, we calculate by how much we need to rotate the second point so that it
121
+ /// is horizontal to the first point, as well as the position of the rotated point.
122
+ fn get_point_ts(start: Point, end: Point) -> (Transform, f32, f32) {
123
+ let dist = start.distance(end);
124
+
125
+ let dx = end.x - start.x;
126
+ let dy = end.y - start.y;
127
+ let angle = dy.atan2(dx).to_degrees();
128
+
129
+ (
130
+ Transform::from_rotate_at(angle, start.x, start.y),
131
+ start.x,
132
+ start.x + dist,
133
+ )
134
+ }
135
+
136
+ impl GradientPropertiesExt for LinearGradient {
137
+ fn gradient_properties(self, bbox: Rect) -> (GradientProperties, Transform) {
138
+ if self.spread_method == SpreadMethod::Pad {
139
+ (
140
+ GradientProperties::RadialAxialGradient(RadialAxialGradient {
141
+ coords: vec![self.x1, self.y1, self.x2, self.y2],
142
+ shading_type: FunctionShadingType::Axial,
143
+ stops: self.stops,
144
+ anti_alias: self.anti_alias,
145
+ }),
146
+ self.transform,
147
+ )
148
+ } else {
149
+ let p1 = Point::from_xy(self.x1, self.y1);
150
+ let p2 = Point::from_xy(self.x2, self.y2);
151
+
152
+ let (ts, min, max) = get_point_ts(p1, p2);
153
+ (
154
+ GradientProperties::PostScriptGradient(PostScriptGradient {
155
+ min,
156
+ max,
157
+ cx: 0.0,
158
+ cy: 0.0,
159
+ stops: self.stops,
160
+ domain: get_expanded_bbox(bbox, self.transform.pre_concat(ts)),
161
+ spread_method: self.spread_method,
162
+ gradient_type: GradientType::Linear,
163
+ anti_alias: self.anti_alias,
164
+ }),
165
+ self.transform.pre_concat(ts),
166
+ )
167
+ }
168
+ }
169
+ }
170
+
171
+ impl GradientPropertiesExt for SweepGradient {
172
+ fn gradient_properties(self, bbox: Rect) -> (GradientProperties, Transform) {
173
+ let min = self.start_angle;
174
+ let max = self.end_angle;
175
+
176
+ let transform = self.transform;
177
+
178
+ (
179
+ GradientProperties::PostScriptGradient(PostScriptGradient {
180
+ min,
181
+ max,
182
+ cx: self.cx,
183
+ cy: self.cy,
184
+ stops: self.stops,
185
+ domain: get_expanded_bbox(bbox, transform),
186
+ spread_method: self.spread_method,
187
+ gradient_type: GradientType::Sweep,
188
+ anti_alias: self.anti_alias,
189
+ }),
190
+ transform,
191
+ )
192
+ }
193
+ }
194
+
195
+ impl GradientPropertiesExt for RadialGradient {
196
+ fn gradient_properties(self, _: Rect) -> (GradientProperties, Transform) {
197
+ // TODO: Support other spread methods
198
+ (
199
+ GradientProperties::RadialAxialGradient(RadialAxialGradient {
200
+ coords: vec![self.fx, self.fy, self.fr, self.cx, self.cy, self.cr],
201
+ shading_type: FunctionShadingType::Radial,
202
+ stops: self.stops,
203
+ anti_alias: self.anti_alias,
204
+ }),
205
+ self.transform,
206
+ )
207
+ }
208
+ }
209
+
210
+ #[derive(Debug, Hash, Eq, PartialEq)]
211
+ struct Repr {
212
+ pub(crate) properties: GradientProperties,
213
+ pub(crate) use_opacities: bool,
214
+ }
215
+
216
+ #[derive(Debug, Hash, Eq, PartialEq, Clone)]
217
+ pub(crate) struct ShadingFunction(Arc<Repr>);
218
+
219
+ impl ShadingFunction {
220
+ pub(crate) fn new(properties: GradientProperties, use_opacities: bool) -> Self {
221
+ Self(Arc::new(Repr {
222
+ properties,
223
+ use_opacities,
224
+ }))
225
+ }
226
+ }
227
+
228
+ impl Cacheable for ShadingFunction {
229
+ fn serialize(
230
+ self,
231
+ sc: &mut SerializeContext,
232
+ chunk_container: &mut ChunkContainer,
233
+ root_ref: Ref,
234
+ ) {
235
+ let mut stream_chunk = sc.new_chunk();
236
+
237
+ match &self.0.properties {
238
+ GradientProperties::RadialAxialGradient(rag) => {
239
+ let shading_cs =
240
+ shading_color_space(sc, rag.stops[0].color.clone(), self.0.use_opacities);
241
+ let registered_cs = sc.register_colorspace(chunk_container, shading_cs);
242
+ let chunk = &mut chunk_container.non_stream.shading_functions;
243
+ serialize_axial_radial_shading(
244
+ sc,
245
+ chunk,
246
+ root_ref,
247
+ rag,
248
+ self.0.use_opacities,
249
+ registered_cs,
250
+ )
251
+ }
252
+ GradientProperties::PostScriptGradient(psg) => {
253
+ sc.register_validation_error(ValidationError::ContainsPostScript(sc.location));
254
+ let shading_cs =
255
+ shading_color_space(sc, psg.stops[0].color.clone(), self.0.use_opacities);
256
+ let registered_cs = sc.register_colorspace(chunk_container, shading_cs);
257
+ let chunk = &mut chunk_container.non_stream.shading_functions;
258
+ serialize_postscript_shading(
259
+ sc,
260
+ chunk,
261
+ &mut stream_chunk,
262
+ root_ref,
263
+ psg,
264
+ self.0.use_opacities,
265
+ registered_cs,
266
+ )
267
+ }
268
+ }
269
+
270
+ // Note: The stream chunk might be empty.
271
+ chunk_container.streams.shading_functions.push(stream_chunk);
272
+ }
273
+ }
274
+
275
+ impl Resourceable for ShadingFunction {
276
+ type Resource = resource::Shading;
277
+ }
278
+
279
+ fn shading_color_space(sc: &mut SerializeContext, color: Color, use_opacities: bool) -> ColorSpace {
280
+ if use_opacities {
281
+ luma::color_space(sc.serialize_settings().no_device_cs).into()
282
+ } else {
283
+ color.color_space(sc)
284
+ }
285
+ }
286
+
287
+ fn serialize_postscript_shading(
288
+ sc: &mut SerializeContext,
289
+ chunk: &mut Chunk,
290
+ stream_chunk: &mut Chunk,
291
+ root_ref: Ref,
292
+ post_script_gradient: &PostScriptGradient,
293
+ use_opacities: bool,
294
+ cs: MaybeDeviceColorSpace,
295
+ ) {
296
+ let domain = post_script_gradient.domain;
297
+
298
+ let bump = Bump::new();
299
+ let function_ref =
300
+ select_postscript_function(post_script_gradient, stream_chunk, sc, &bump, use_opacities);
301
+ let mut shading = chunk.function_shading(root_ref);
302
+ shading.shading_type(FunctionShadingType::Function);
303
+
304
+ set_colorspace(cs, shading.deref_mut());
305
+
306
+ // Write the identity matrix, because ghostscript has a bug where
307
+ // it thinks the entry is mandatory.
308
+ shading.matrix([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
309
+ shading.anti_alias(post_script_gradient.anti_alias);
310
+ shading.function(function_ref);
311
+
312
+ shading.domain([domain.left(), domain.right(), domain.top(), domain.bottom()]);
313
+ shading.finish();
314
+ }
315
+
316
+ fn serialize_axial_radial_shading(
317
+ sc: &mut SerializeContext,
318
+ chunk: &mut Chunk,
319
+ root_ref: Ref,
320
+ radial_axial_gradient: &RadialAxialGradient,
321
+ use_opacities: bool,
322
+ cs: MaybeDeviceColorSpace,
323
+ ) {
324
+ let function_ref =
325
+ select_axial_radial_function(radial_axial_gradient, chunk, sc, use_opacities);
326
+ let mut shading = chunk.function_shading(root_ref);
327
+ if radial_axial_gradient.shading_type == FunctionShadingType::Radial {
328
+ shading.shading_type(FunctionShadingType::Radial);
329
+ } else {
330
+ shading.shading_type(FunctionShadingType::Axial);
331
+ }
332
+
333
+ set_colorspace(cs, shading.deref_mut());
334
+
335
+ shading.anti_alias(radial_axial_gradient.anti_alias);
336
+ shading.function(function_ref);
337
+ shading.coords(radial_axial_gradient.coords.iter().copied());
338
+ shading.extend([true, true]);
339
+ shading.finish();
340
+ }
341
+
342
+ fn select_axial_radial_function(
343
+ properties: &RadialAxialGradient,
344
+ chunk: &mut Chunk,
345
+ sc: &mut SerializeContext,
346
+ use_opacities: bool,
347
+ ) -> Ref {
348
+ debug_assert!(properties.stops.len() > 1);
349
+
350
+ let mut stops = properties.stops.clone();
351
+
352
+ if let Some(first) = stops.first() {
353
+ if first.offset.get() != 0.0 {
354
+ let mut new_stop = first.clone();
355
+ new_stop.offset = NormalizedF32::ZERO;
356
+ stops.insert(0, new_stop);
357
+ }
358
+ }
359
+
360
+ if let Some(last) = stops.last() {
361
+ if last.offset.get() != 1.0 {
362
+ let mut new_stop = last.clone();
363
+ new_stop.offset = NormalizedF32::ONE;
364
+ stops.push(new_stop);
365
+ }
366
+ }
367
+
368
+ if stops.len() == 2 {
369
+ if use_opacities {
370
+ serialize_exponential(
371
+ vec![stops[0].opacity.get()],
372
+ vec![stops[1].opacity.get()],
373
+ chunk,
374
+ sc,
375
+ )
376
+ } else {
377
+ serialize_exponential(
378
+ stops[0]
379
+ .color
380
+ .to_pdf_color()
381
+ .into_iter()
382
+ .collect::<Vec<_>>(),
383
+ stops[1]
384
+ .color
385
+ .to_pdf_color()
386
+ .into_iter()
387
+ .collect::<Vec<_>>(),
388
+ chunk,
389
+ sc,
390
+ )
391
+ }
392
+ } else {
393
+ serialize_stitching(&stops, chunk, sc, use_opacities)
394
+ }
395
+ }
396
+
397
+ fn select_postscript_function(
398
+ properties: &PostScriptGradient,
399
+ chunk: &mut Chunk,
400
+ sc: &mut SerializeContext,
401
+ bump: &Bump,
402
+ use_opacities: bool,
403
+ ) -> Ref {
404
+ debug_assert!(properties.stops.len() > 1);
405
+
406
+ if properties.gradient_type == GradientType::Linear {
407
+ serialize_linear_postscript(properties, chunk, sc, use_opacities)
408
+ } else if properties.gradient_type == GradientType::Sweep {
409
+ serialize_sweep_postscript(properties, chunk, sc, bump, use_opacities)
410
+ } else {
411
+ todo!();
412
+ }
413
+ }
414
+
415
+ // Not working yet
416
+ // fn serialize_radial_postscript(
417
+ // properties: &GradientProperties,
418
+ // sc: &mut SerializeContext,
419
+ // bbox: &Rect,
420
+ // ) -> Ref {
421
+ // let root_ref = sc.new_ref();
422
+ //
423
+ // let start_code = [
424
+ // "{".to_string(),
425
+ // // Stack: x y
426
+ // "80 exch 80 sub dup mul 3 1 roll sub dup mul add sqrt 120 div 0 0".to_string(),
427
+ // ];
428
+ //
429
+ // let end_code = ["}".to_string()];
430
+ //
431
+ // let mut code = Vec::new();
432
+ // code.extend(start_code);
433
+ // // code.push(encode_spread_method(min, max, properties.spread_method));
434
+ // // code.push(encode_stops(&properties.stops, min, max));
435
+ // code.extend(end_code);
436
+ //
437
+ // let code = code.join(" ").into_bytes();
438
+ // let mut postscript_function = sc.chunk_mut().post_script_function(root_ref, &code);
439
+ // postscript_function.domain([bbox.left(), bbox.right(), bbox.top(), bbox.bottom()]);
440
+ // postscript_function.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
441
+ //
442
+ // root_ref
443
+ // }
444
+
445
+ fn serialize_sweep_postscript(
446
+ properties: &PostScriptGradient,
447
+ chunk: &mut Chunk,
448
+ sc: &mut SerializeContext,
449
+ bump: &Bump,
450
+ use_opacities: bool,
451
+ ) -> Ref {
452
+ use pdf_writer::types::PostScriptOp::*;
453
+
454
+ let root_ref = sc.new_ref();
455
+
456
+ let min: f32 = properties.min;
457
+ let max: f32 = properties.max;
458
+
459
+ let mut code = vec![];
460
+ code.extend([
461
+ // Stack: x y
462
+ // Shift by cy, so gradient actually starts from the center
463
+ Real(properties.cy),
464
+ Sub,
465
+ Exch,
466
+ // y x
467
+ // Shift by cx, so gradient actually starts from the center
468
+ Real(properties.cx),
469
+ Sub,
470
+ // Make sure x is never 0.
471
+ Dup,
472
+ Dup,
473
+ Real(0.0001),
474
+ Lt,
475
+ Exch,
476
+ Real(-0.0001),
477
+ Gt,
478
+ And,
479
+ If(bump.alloc([Pop, Real(0.0001)])),
480
+ // Get the angle
481
+ Atan,
482
+ ]);
483
+
484
+ encode_spread_method(min, max, &mut code, bump, properties.spread_method);
485
+ encode_postscript_stops(&properties.stops, min, max, &mut code, bump, use_opacities);
486
+
487
+ let encoded = PostScriptOp::encode(&code);
488
+ sc.register_limits(encoded.limits());
489
+ let encoded = FilterStreamBuilder::new_from_content_stream(&encoded, &sc.serialize_settings())
490
+ .finish(&sc.serialize_settings());
491
+ let mut postscript_function = chunk.post_script_function(root_ref, encoded.encoded_data());
492
+ encoded.write_filters(postscript_function.deref_mut().deref_mut());
493
+ postscript_function.domain([
494
+ properties.domain.left(),
495
+ properties.domain.right(),
496
+ properties.domain.top(),
497
+ properties.domain.bottom(),
498
+ ]);
499
+
500
+ if use_opacities {
501
+ postscript_function.range([0.0, 1.0]);
502
+ } else {
503
+ postscript_function.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
504
+ }
505
+
506
+ root_ref
507
+ }
508
+
509
+ const MAX_POSTSCRIPT_STOPS: usize = 97;
510
+
511
+ fn trim_stops(stops: &[Stop]) -> Vec<Stop> {
512
+ let len = stops.len();
513
+ let factor = len as f32 / MAX_POSTSCRIPT_STOPS as f32;
514
+ let mut cur_index: f32 = 0.0;
515
+
516
+ let get_index = |i: f32| i.round() as usize;
517
+
518
+ let mut new_stops = vec![];
519
+
520
+ while get_index(cur_index) < stops.len() {
521
+ new_stops.push(stops[get_index(cur_index)].clone());
522
+ cur_index += factor;
523
+ }
524
+
525
+ new_stops
526
+ }
527
+
528
+ fn serialize_linear_postscript(
529
+ properties: &PostScriptGradient,
530
+ chunk: &mut Chunk,
531
+ sc: &mut SerializeContext,
532
+ use_opacities: bool,
533
+ ) -> Ref {
534
+ use pdf_writer::types::PostScriptOp::*;
535
+
536
+ let bump = Bump::new();
537
+ let root_ref = sc.new_ref();
538
+
539
+ let min: f32 = properties.min;
540
+ let max: f32 = properties.max;
541
+
542
+ let mut code = vec![];
543
+ code.extend([
544
+ // Stack: x y
545
+ // Ignore the y coordinate. We account for it in the gradient transform.
546
+ Pop,
547
+ // x
548
+ ]);
549
+
550
+ encode_spread_method(min, max, &mut code, &bump, properties.spread_method);
551
+ encode_postscript_stops(&properties.stops, min, max, &mut code, &bump, use_opacities);
552
+
553
+ let encoded = PostScriptOp::encode(&code);
554
+ sc.register_limits(encoded.limits());
555
+ let encoded = FilterStreamBuilder::new_from_content_stream(&encoded, &sc.serialize_settings())
556
+ .finish(&sc.serialize_settings());
557
+ let mut postscript_function = chunk.post_script_function(root_ref, encoded.encoded_data());
558
+ encoded.write_filters(postscript_function.deref_mut().deref_mut());
559
+ postscript_function.domain([
560
+ properties.domain.left(),
561
+ properties.domain.right(),
562
+ properties.domain.top(),
563
+ properties.domain.bottom(),
564
+ ]);
565
+
566
+ if use_opacities {
567
+ postscript_function.range([0.0, 1.0]);
568
+ } else {
569
+ postscript_function.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
570
+ }
571
+
572
+ root_ref
573
+ }
574
+
575
+ /// Postscript code that, given an arbitrary x coordinate, normalizes it to an x coordinate
576
+ /// between min and max that yields the correct color, depending on the spread mode. In the case
577
+ /// of the `Pad` spread methods, the coordinate will not be normalized since the Postscript functions
578
+ /// assign the correct value by default.
579
+ fn encode_spread_method<'a>(
580
+ min: f32,
581
+ max: f32,
582
+ code: &mut Vec<PostScriptOp<'a>>,
583
+ bump: &'a Bump,
584
+ spread_method: SpreadMethod,
585
+ ) {
586
+ use pdf_writer::types::PostScriptOp::*;
587
+
588
+ if spread_method == SpreadMethod::Pad {
589
+ return;
590
+ }
591
+
592
+ let length = max - min;
593
+
594
+ code.extend([
595
+ // We do the following:
596
+ // 1. Normalize by doing n = x - min.
597
+ // 2. Calculate the "interval" we are in by doing i = floor(n / length)
598
+ // 3. Calculate the offset by doing o = n - i * length
599
+ // 4. If the spread method is repeat, we additionally calculate o = length - 0 if i % 2 == 1
600
+ // 5. Calculate the final value with x_new = min + o.
601
+
602
+ // Current stack:
603
+ // x
604
+ Real(length),
605
+ Real(min),
606
+ // x length min
607
+ Integer(2),
608
+ Index,
609
+ // x length min x
610
+ Integer(1),
611
+ Index,
612
+ // x length min x min
613
+ Sub,
614
+ // x length min n
615
+ Dup,
616
+ // x length min n n
617
+ Integer(3),
618
+ Index,
619
+ // x length min n n length
620
+ Div,
621
+ // x length min n {n/length}
622
+ Floor,
623
+ // x length min n i
624
+ Exch,
625
+ // x length min i n
626
+ Integer(1),
627
+ Index,
628
+ // x length min i n i
629
+ Integer(4),
630
+ Index,
631
+ // x length min i n i length
632
+ Mul,
633
+ // x length min i n {i * length}
634
+ Sub,
635
+ // x length min i o
636
+ Exch,
637
+ // x length min o i
638
+ Cvi,
639
+ Abs,
640
+ // x length min o abs(i)
641
+ Integer(2),
642
+ Mod,
643
+ // x length min o {abs(i) % 2}
644
+ // See https://github.com/google/skia/blob/645b77ce61449951cb9f3cf754b47d4977b68e1a/src/pdf/SkPDFGradientShader.cpp#L402-L408
645
+ // for why we check > 0 instead of == 1.
646
+ Integer(0),
647
+ Gt,
648
+ // x length min o {(abs(i) % 2) > 0}
649
+ if spread_method == SpreadMethod::Reflect {
650
+ If(bump.alloc([Integer(2), Index, Exch, Sub]))
651
+ } else {
652
+ Pop
653
+ },
654
+ // x length min o
655
+ Add,
656
+ // x length x_new
657
+ Integer(3),
658
+ Integer(1),
659
+ Roll,
660
+ // x_new x length
661
+ Pop,
662
+ Pop,
663
+ // x_new
664
+ ]);
665
+ }
666
+
667
+ /// Postscript code that, given an x coordinate between the min and max
668
+ /// of a gradient, returns the interpolated color value depending on where it
669
+ /// lies within the stops.
670
+ fn encode_postscript_stops<'a>(
671
+ stops: &[Stop],
672
+ min: f32,
673
+ max: f32,
674
+ code: &mut Vec<PostScriptOp<'a>>,
675
+ bump: &'a Bump,
676
+ use_opacities: bool,
677
+ ) {
678
+ // Our algorithm requires the stops to be padded.
679
+ let mut stops = stops.to_vec();
680
+
681
+ // Most viewers have a nesting depth on how many `elseif` can be nested (100 for mupdf,
682
+ // 128 for Chrome and 256 for Acrobat), so we trim the stops if they are too large.
683
+ // this is of course a bit unfortunate, but it should be pretty rare to have that many
684
+ // stops, and if we do, the stops are most likely going to be very similar, so it's
685
+ // safe to just sample from in-between.
686
+ if stops.len() > MAX_POSTSCRIPT_STOPS {
687
+ stops = trim_stops(&stops);
688
+ }
689
+
690
+ if let Some(first) = stops.first() {
691
+ let mut first = first.clone();
692
+ first.offset = NormalizedF32::ZERO;
693
+ stops.insert(0, first);
694
+ }
695
+
696
+ if let Some(last) = stops.last() {
697
+ let mut last = last.clone();
698
+ last.offset = NormalizedF32::ONE;
699
+ stops.push(last);
700
+ }
701
+
702
+ encode_stops_impl(&stops, min, max, code, bump, use_opacities);
703
+ }
704
+
705
+ fn encode_stops_impl<'a>(
706
+ stops: &[Stop],
707
+ min: f32,
708
+ max: f32,
709
+ code: &mut Vec<PostScriptOp<'a>>,
710
+ bump: &'a Bump,
711
+ use_opacities: bool,
712
+ ) {
713
+ use pdf_writer::types::PostScriptOp::*;
714
+
715
+ let encode_two_stops =
716
+ |c0: &[f32], c1: &[f32], min: f32, max: f32, code: &mut Vec<PostScriptOp>| {
717
+ if min == max {
718
+ code.push(Pop);
719
+ code.extend(c0.iter().map(|n| Real(*n)));
720
+ return;
721
+ }
722
+
723
+ // Sanity check that both stops have the same number of components.
724
+ assert_eq!(
725
+ c0.len(),
726
+ c1.len(),
727
+ "cannot create gradient with stops from different color spaces"
728
+ );
729
+
730
+ // Normalize the x coordinate to be between 0 and 1.
731
+ code.extend([Real(min), Sub, Real(max), Real(min), Sub, Div]);
732
+
733
+ for i in 0..c0.len() {
734
+ // Interpolate each color component c0 + x_norm * (x1 - c0).
735
+ code.extend([
736
+ Integer(i as i32),
737
+ Index,
738
+ Real(c0[i]),
739
+ Exch,
740
+ Real(c1[i]),
741
+ Real(c0[i]),
742
+ Sub,
743
+ Mul,
744
+ Add,
745
+ ]);
746
+ // x_norm, c0, c1, ...
747
+ }
748
+ // Remove x_norm from the stack.
749
+ code.extend([Integer((c0.len() + 1) as i32), Integer(-1), Roll, Pop]);
750
+ // c0, c1, c2, ...
751
+ };
752
+
753
+ if stops.len() == 1 {
754
+ if use_opacities {
755
+ code.push(Real(stops[0].opacity.get()));
756
+ } else {
757
+ code.extend(stops[0].color.to_pdf_color().into_iter().map(Real));
758
+ }
759
+ } else {
760
+ let length = max - min;
761
+ let stops_min = min + length * stops[0].offset.get();
762
+ let stops_max = min + length * stops[1].offset.get();
763
+ // Write the if conditions to find the corresponding set of two stops.
764
+
765
+ let if_stops = bump.alloc(vec![]);
766
+ if use_opacities {
767
+ encode_two_stops(
768
+ &[stops[0].opacity.get()],
769
+ &[stops[1].opacity.get()],
770
+ stops_min,
771
+ stops_max,
772
+ if_stops,
773
+ )
774
+ } else {
775
+ encode_two_stops(
776
+ &stops[0]
777
+ .color
778
+ .to_pdf_color()
779
+ .into_iter()
780
+ .collect::<Vec<_>>(),
781
+ &stops[1]
782
+ .color
783
+ .to_pdf_color()
784
+ .into_iter()
785
+ .collect::<Vec<_>>(),
786
+ stops_min,
787
+ stops_max,
788
+ if_stops,
789
+ )
790
+ };
791
+ let else_stops = bump.alloc(vec![]);
792
+ encode_stops_impl(&stops[1..], min, max, else_stops, bump, use_opacities);
793
+
794
+ code.extend([Dup, Real(stops_max), Le, IfElse(if_stops, else_stops)]);
795
+ }
796
+ }
797
+
798
+ fn serialize_stitching(
799
+ stops: &[Stop],
800
+ chunk: &mut Chunk,
801
+ sc: &mut SerializeContext,
802
+ use_opacities: bool,
803
+ ) -> Ref {
804
+ let root_ref = sc.new_ref();
805
+ let mut functions = vec![];
806
+ let mut bounds = vec![];
807
+ let mut encode = vec![];
808
+ let mut count = 0;
809
+
810
+ for window in stops.windows(2) {
811
+ let (first, second) = (&window[0], &window[1]);
812
+ bounds.push(second.offset.get());
813
+
814
+ let (c0_components, c1_components) = if use_opacities {
815
+ (vec![first.opacity.get()], vec![second.opacity.get()])
816
+ } else {
817
+ (
818
+ first.color.to_pdf_color().into_iter().collect::<Vec<_>>(),
819
+ second.color.to_pdf_color().into_iter().collect::<Vec<_>>(),
820
+ )
821
+ };
822
+
823
+ count = c0_components.len();
824
+
825
+ let exp_ref = serialize_exponential(c0_components, c1_components, chunk, sc);
826
+
827
+ functions.push(exp_ref);
828
+ encode.extend([0.0, 1.0]);
829
+ }
830
+
831
+ bounds.pop();
832
+ let mut stitching_function = chunk.stitching_function(root_ref);
833
+ stitching_function.domain([0.0, 1.0]);
834
+ stitching_function.range([0.0, 1.0].repeat(count));
835
+ stitching_function.functions(functions);
836
+ stitching_function.bounds(bounds);
837
+ stitching_function.encode(encode);
838
+
839
+ root_ref
840
+ }
841
+
842
+ fn serialize_exponential(
843
+ c0: Vec<f32>,
844
+ c1: Vec<f32>,
845
+ chunk: &mut Chunk,
846
+ sc: &mut SerializeContext,
847
+ ) -> Ref {
848
+ let root_ref = sc.new_ref();
849
+ assert_eq!(
850
+ c0.len(),
851
+ c1.len(),
852
+ "cannot create gradient with stops from different color spaces"
853
+ );
854
+ let num_components = c0.len();
855
+
856
+ let mut exp = chunk.exponential_function(root_ref);
857
+
858
+ exp.range([0.0, 1.0].repeat(num_components));
859
+ exp.c0(c0);
860
+ exp.c1(c1);
861
+ exp.domain([0.0, 1.0]);
862
+ exp.n(1.0);
863
+ exp.finish();
864
+ root_ref
865
+ }