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,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
|
+
}
|