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,1352 @@
|
|
|
1
|
+
//! A low-level abstraction over a single content stream.
|
|
2
|
+
|
|
3
|
+
use std::cell::RefCell;
|
|
4
|
+
use std::collections::HashSet;
|
|
5
|
+
use std::rc::Rc;
|
|
6
|
+
use std::sync::Arc;
|
|
7
|
+
|
|
8
|
+
use float_cmp::approx_eq;
|
|
9
|
+
use pdf_writer::types::TextRenderingMode;
|
|
10
|
+
use pdf_writer::{Content, Finish, Name, Ref, Str, TextStr};
|
|
11
|
+
use tiny_skia_path::{Path, PathSegment, PathVerb};
|
|
12
|
+
|
|
13
|
+
use crate::chunk_container::ChunkContainer;
|
|
14
|
+
use crate::color::rgb;
|
|
15
|
+
use crate::configure::ValidationError;
|
|
16
|
+
#[cfg(feature = "raster-images")]
|
|
17
|
+
use crate::geom::Size;
|
|
18
|
+
use crate::geom::{Point, Rect, Transform};
|
|
19
|
+
use crate::graphics::color::{Color, ColorSpace};
|
|
20
|
+
use crate::graphics::graphics_state::{ExtGState, GraphicsStates};
|
|
21
|
+
#[cfg(feature = "raster-images")]
|
|
22
|
+
use crate::graphics::image::Image;
|
|
23
|
+
use crate::graphics::mask::Mask;
|
|
24
|
+
use crate::graphics::paint::{Fill, FillRule, InnerPaint, LineCap, LineJoin, Paint, Stroke};
|
|
25
|
+
use crate::graphics::shading_function::{
|
|
26
|
+
GradientProperties, GradientPropertiesExt, ShadingFunction,
|
|
27
|
+
};
|
|
28
|
+
use crate::graphics::shading_pattern::ShadingPattern;
|
|
29
|
+
use crate::graphics::tiling_pattern::TilingPattern;
|
|
30
|
+
use crate::graphics::xobject::XObject;
|
|
31
|
+
use crate::interchange::tagging::ContentTag;
|
|
32
|
+
use crate::num::NormalizedF32;
|
|
33
|
+
use crate::resource;
|
|
34
|
+
use crate::resource::{Resource, ResourceDictionaryBuilder};
|
|
35
|
+
use crate::serialize::{MaybeDeviceColorSpace, SerializeContext};
|
|
36
|
+
use crate::stream::Stream;
|
|
37
|
+
use crate::text::group::{use_text_spanner, GlyphGroup, GlyphGrouper, GlyphSpan, GlyphSpanner};
|
|
38
|
+
use crate::text::type3::ColoredGlyph;
|
|
39
|
+
use crate::text::{Font, FontContainer, FontIdentifier, PdfFont, PDF_UNITS_PER_EM};
|
|
40
|
+
use crate::text::{Glyph, GlyphId};
|
|
41
|
+
use crate::util::{calculate_stroke_bbox, NameExt};
|
|
42
|
+
|
|
43
|
+
pub(crate) struct ContentBuilder {
|
|
44
|
+
rd_builder: ResourceDictionaryBuilder,
|
|
45
|
+
content: Content,
|
|
46
|
+
validation_errors: HashSet<ValidationError>,
|
|
47
|
+
root_transform: Transform,
|
|
48
|
+
graphics_states: GraphicsStates,
|
|
49
|
+
bbox: Option<Rect>,
|
|
50
|
+
uses_mask: bool,
|
|
51
|
+
// Calculating the bbox of text is expensive, so we should avoid doing it if not needed.
|
|
52
|
+
// The only time we really need it is if we are currently inside of an XObject, where we
|
|
53
|
+
// need to provide a bbox of all its contents. If we are on the main page stream, we only
|
|
54
|
+
// need it if automatic size detection is enabled.
|
|
55
|
+
bbox_important: bool,
|
|
56
|
+
/// A temporary buffer that's reused across the builder.
|
|
57
|
+
scratch: Vec<u8>,
|
|
58
|
+
pub(crate) active_marked_content: bool,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Stores either a device-specific color space,
|
|
62
|
+
/// or the name of a different colorspace (e.g. ICCBased) stored in
|
|
63
|
+
/// the current resource dictionary
|
|
64
|
+
enum ContentColorSpace {
|
|
65
|
+
Device,
|
|
66
|
+
Named(String),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
impl ContentBuilder {
|
|
70
|
+
pub(crate) fn new(
|
|
71
|
+
root_transform: Transform,
|
|
72
|
+
bbox_important: bool,
|
|
73
|
+
sc: &SerializeContext,
|
|
74
|
+
) -> Self {
|
|
75
|
+
Self {
|
|
76
|
+
rd_builder: ResourceDictionaryBuilder::new(),
|
|
77
|
+
validation_errors: HashSet::new(),
|
|
78
|
+
content: sc.new_content(),
|
|
79
|
+
root_transform,
|
|
80
|
+
uses_mask: false,
|
|
81
|
+
bbox_important,
|
|
82
|
+
graphics_states: GraphicsStates::new(),
|
|
83
|
+
bbox: None,
|
|
84
|
+
scratch: Vec::new(),
|
|
85
|
+
active_marked_content: false,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
pub(crate) fn content_save_state(&mut self) {
|
|
90
|
+
self.content.save_state();
|
|
91
|
+
|
|
92
|
+
if self.content.state_nesting_depth() > 28 {
|
|
93
|
+
self.validation_errors
|
|
94
|
+
.insert(ValidationError::TooHighQNestingLevel);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
pub(crate) fn finish(self, sc: &mut SerializeContext) -> Stream {
|
|
99
|
+
let buf = self.content.finish();
|
|
100
|
+
sc.register_limits(buf.limits());
|
|
101
|
+
|
|
102
|
+
Stream::new(
|
|
103
|
+
buf.to_vec(),
|
|
104
|
+
self.bbox
|
|
105
|
+
.unwrap_or(Rect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap()),
|
|
106
|
+
self.validation_errors.into_iter().collect(),
|
|
107
|
+
self.rd_builder.finish(),
|
|
108
|
+
self.uses_mask,
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn start_marked_content_prelude(&mut self) {
|
|
113
|
+
if self.active_marked_content {
|
|
114
|
+
panic!("can't start marked content twice");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
self.active_marked_content = true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[track_caller]
|
|
121
|
+
pub(crate) fn start_marked_content(&mut self, name: Name) {
|
|
122
|
+
self.start_marked_content_prelude();
|
|
123
|
+
self.content.begin_marked_content(name);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#[track_caller]
|
|
127
|
+
pub(crate) fn start_marked_content_with_properties(
|
|
128
|
+
&mut self,
|
|
129
|
+
sc: &mut SerializeContext,
|
|
130
|
+
mcid: Option<i32>,
|
|
131
|
+
tag: ContentTag,
|
|
132
|
+
) {
|
|
133
|
+
self.start_marked_content_prelude();
|
|
134
|
+
|
|
135
|
+
let mut mc = self
|
|
136
|
+
.content
|
|
137
|
+
.begin_marked_content_with_properties(tag.name());
|
|
138
|
+
let mut properties = mc.properties();
|
|
139
|
+
|
|
140
|
+
if let Some(mcid) = mcid {
|
|
141
|
+
properties.pairs([(Name(b"MCID"), mcid)]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Page height extracted from transform and passed to function to allow
|
|
145
|
+
// its dependants to flip the y-axis, mirroring Krilla conventions.
|
|
146
|
+
let page_height = self.root_transform.ty();
|
|
147
|
+
tag.write_properties(sc, properties, page_height);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
pub(crate) fn end_marked_content(&mut self) {
|
|
151
|
+
if !self.active_marked_content {
|
|
152
|
+
panic!("can't end marked content when none has been started");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
self.content.end_marked_content();
|
|
156
|
+
self.active_marked_content = false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
pub(crate) fn concat_transform(&mut self, transform: &Transform) {
|
|
160
|
+
self.graphics_states.transform(*transform);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn cur_transform_with_root_transform(&self) -> Transform {
|
|
164
|
+
self.root_transform.pre_concat(self.cur_transform())
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
pub(crate) fn cur_transform(&self) -> Transform {
|
|
168
|
+
self.graphics_states.cur().transform()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
pub(crate) fn save_graphics_state(&mut self) {
|
|
172
|
+
self.graphics_states.save_state();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
pub(crate) fn restore_graphics_state(&mut self) {
|
|
176
|
+
self.graphics_states.restore_state();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
pub(crate) fn set_blend_mode(&mut self, blend_mode: pdf_writer::types::BlendMode) {
|
|
180
|
+
if blend_mode != pdf_writer::types::BlendMode::Normal {
|
|
181
|
+
let state = ExtGState::new().blend_mode(blend_mode);
|
|
182
|
+
self.graphics_states.combine(&state);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
pub(crate) fn expand_bbox(&mut self, new_bbox: Rect) {
|
|
187
|
+
let new_bbox = self.graphics_states.transform_bbox(new_bbox);
|
|
188
|
+
if let Some(bbox) = &mut self.bbox {
|
|
189
|
+
bbox.expand(&new_bbox);
|
|
190
|
+
} else {
|
|
191
|
+
self.bbox = Some(new_bbox);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
pub(crate) fn draw_path(
|
|
196
|
+
&mut self,
|
|
197
|
+
path: &Path,
|
|
198
|
+
mut fill: Option<&Fill>,
|
|
199
|
+
stroke: Option<&Stroke>,
|
|
200
|
+
sc: &mut SerializeContext,
|
|
201
|
+
chunk_container: &mut ChunkContainer,
|
|
202
|
+
) {
|
|
203
|
+
if fill.is_none() && stroke.is_none() {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Zero-size geometry, don't draw.
|
|
208
|
+
if path.bounds().width() == 0.0 && path.bounds().height() == 0.0 {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// See issue 199.
|
|
213
|
+
let is_line = path.verbs().len() == 2
|
|
214
|
+
&& path.verbs()[0] == PathVerb::Move
|
|
215
|
+
&& path.verbs()[1] == PathVerb::Line;
|
|
216
|
+
let dont_fill = path.bounds().width() == 0.0 || path.bounds().height() == 0.0 || is_line;
|
|
217
|
+
|
|
218
|
+
if dont_fill {
|
|
219
|
+
match stroke.is_some() {
|
|
220
|
+
// Some PDF viewers have bugs where they slightly fill zero-sized lines, so
|
|
221
|
+
// don't draw them in the first place.
|
|
222
|
+
false => return,
|
|
223
|
+
// Don't fill, but we still want to draw the stroke, so don't return.
|
|
224
|
+
true => {
|
|
225
|
+
fill = None;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let bbox_important = self.bbox_important;
|
|
231
|
+
let calculate_bbox = |is_solid: bool| bbox_important || !is_solid;
|
|
232
|
+
|
|
233
|
+
let stroke_bbox = |stroke: &Stroke| {
|
|
234
|
+
if calculate_bbox(matches!(&stroke.paint.0, InnerPaint::Color(_))) {
|
|
235
|
+
calculate_stroke_bbox(stroke, path).unwrap_or(Rect::from_tsp(path.bounds()))
|
|
236
|
+
} else {
|
|
237
|
+
Rect::from_tsp(path.bounds())
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
let fill_prep = |sb: &mut ContentBuilder, fill: &Fill| {
|
|
242
|
+
let has_pattern = matches!(fill.paint.0, InnerPaint::Pattern(_));
|
|
243
|
+
let fill_opacity = fill.opacity;
|
|
244
|
+
sb.expand_bbox(Rect::from_tsp(path.bounds()));
|
|
245
|
+
|
|
246
|
+
// PDF viewers don't show patterns with fill/stroke opacities consistently.
|
|
247
|
+
// Because of this, the opacity is accounted for in the pattern itself.
|
|
248
|
+
if !has_pattern {
|
|
249
|
+
sb.set_fill_opacity(fill_opacity);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
let stroke_prep = |sb: &mut ContentBuilder, stroke: &Stroke, stroke_bbox: Rect| {
|
|
254
|
+
let is_pattern = matches!(stroke.paint.0, InnerPaint::Pattern(_));
|
|
255
|
+
let stroke_opacity = stroke.opacity;
|
|
256
|
+
sb.expand_bbox(stroke_bbox);
|
|
257
|
+
|
|
258
|
+
// See comment in `set_fill_properties`
|
|
259
|
+
if !is_pattern {
|
|
260
|
+
sb.set_stroke_opacity(stroke_opacity);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
let fill_op = |sb: &mut ContentBuilder,
|
|
265
|
+
sc: &mut SerializeContext,
|
|
266
|
+
chunk_container: &mut ChunkContainer,
|
|
267
|
+
fill: &Fill| {
|
|
268
|
+
let fill_rule = fill.rule;
|
|
269
|
+
sb.content_set_fill_properties(
|
|
270
|
+
Rect::from_tsp(path.bounds()),
|
|
271
|
+
fill,
|
|
272
|
+
sc,
|
|
273
|
+
chunk_container,
|
|
274
|
+
);
|
|
275
|
+
sb.content_draw_path(path.segments());
|
|
276
|
+
|
|
277
|
+
match fill_rule {
|
|
278
|
+
FillRule::NonZero => sb.content.fill_nonzero(),
|
|
279
|
+
FillRule::EvenOdd => sb.content.fill_even_odd(),
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
let stroke_op = |sb: &mut ContentBuilder,
|
|
284
|
+
sc: &mut SerializeContext,
|
|
285
|
+
chunk_container: &mut ChunkContainer,
|
|
286
|
+
stroke: &Stroke,
|
|
287
|
+
stroke_bbox: Rect| {
|
|
288
|
+
sb.content_set_stroke_properties(stroke_bbox, stroke, sc, chunk_container);
|
|
289
|
+
sb.content_draw_path(path.segments());
|
|
290
|
+
sb.content.stroke();
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
let fill_stroke_op = |sb: &mut ContentBuilder,
|
|
294
|
+
sc: &mut SerializeContext,
|
|
295
|
+
chunk_container: &mut ChunkContainer,
|
|
296
|
+
fill: &Fill,
|
|
297
|
+
stroke: &Stroke,
|
|
298
|
+
stroke_bbox: Rect| {
|
|
299
|
+
let fill_rule = fill.rule;
|
|
300
|
+
sb.content_set_fill_properties(
|
|
301
|
+
Rect::from_tsp(path.bounds()),
|
|
302
|
+
fill,
|
|
303
|
+
sc,
|
|
304
|
+
chunk_container,
|
|
305
|
+
);
|
|
306
|
+
sb.content_set_stroke_properties(stroke_bbox, stroke, sc, chunk_container);
|
|
307
|
+
sb.content_draw_path(path.segments());
|
|
308
|
+
|
|
309
|
+
match fill_rule {
|
|
310
|
+
FillRule::NonZero => sb.content.fill_nonzero_and_stroke(),
|
|
311
|
+
FillRule::EvenOdd => sb.content.fill_even_odd_and_stroke(),
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
match (fill, stroke) {
|
|
316
|
+
(Some(fill), None) => {
|
|
317
|
+
self.apply_isolated_op(
|
|
318
|
+
|sb, _, _| {
|
|
319
|
+
fill_prep(sb, fill);
|
|
320
|
+
},
|
|
321
|
+
|sb, sc, chunk_container| {
|
|
322
|
+
fill_op(sb, sc, chunk_container, fill);
|
|
323
|
+
},
|
|
324
|
+
sc,
|
|
325
|
+
chunk_container,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
(None, Some(stroke)) => {
|
|
329
|
+
let stroke_bbox = stroke_bbox(stroke);
|
|
330
|
+
|
|
331
|
+
self.apply_isolated_op(
|
|
332
|
+
|sb, _, _| {
|
|
333
|
+
stroke_prep(sb, stroke, stroke_bbox);
|
|
334
|
+
},
|
|
335
|
+
|sb, sc, chunk_container| {
|
|
336
|
+
stroke_op(sb, sc, chunk_container, stroke, stroke_bbox);
|
|
337
|
+
},
|
|
338
|
+
sc,
|
|
339
|
+
chunk_container,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
(Some(fill), Some(stroke)) => {
|
|
343
|
+
let stroke_bbox = stroke_bbox(stroke);
|
|
344
|
+
|
|
345
|
+
self.apply_isolated_op(
|
|
346
|
+
|sb, _, _| {
|
|
347
|
+
fill_prep(sb, fill);
|
|
348
|
+
stroke_prep(sb, stroke, stroke_bbox);
|
|
349
|
+
},
|
|
350
|
+
|sb, sc, chunk_container| {
|
|
351
|
+
fill_stroke_op(sb, sc, chunk_container, fill, stroke, stroke_bbox);
|
|
352
|
+
},
|
|
353
|
+
sc,
|
|
354
|
+
chunk_container,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
(None, None) => unreachable!(),
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
pub(crate) fn push_clip_path(&mut self, path: &Path, clip_rule: &FillRule) {
|
|
362
|
+
self.content_save_state();
|
|
363
|
+
self.content_draw_path(
|
|
364
|
+
path.clone()
|
|
365
|
+
.transform(self.cur_transform_with_root_transform().to_tsp())
|
|
366
|
+
.unwrap()
|
|
367
|
+
.segments(),
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
match clip_rule {
|
|
371
|
+
FillRule::NonZero => self.content.clip_nonzero(),
|
|
372
|
+
FillRule::EvenOdd => self.content.clip_even_odd(),
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
self.content.end_path();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
pub(crate) fn pop_clip_path(&mut self) {
|
|
379
|
+
self.content.restore_state();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#[allow(clippy::too_many_arguments)]
|
|
383
|
+
pub(crate) fn draw_glyphs(
|
|
384
|
+
&mut self,
|
|
385
|
+
start: Point,
|
|
386
|
+
sc: &mut SerializeContext,
|
|
387
|
+
chunk_container: &mut ChunkContainer,
|
|
388
|
+
fill: Option<&Fill>,
|
|
389
|
+
stroke: Option<&Stroke>,
|
|
390
|
+
context_color: rgb::Color,
|
|
391
|
+
glyphs: &[impl Glyph],
|
|
392
|
+
font: Font,
|
|
393
|
+
text: &str,
|
|
394
|
+
font_size: f32,
|
|
395
|
+
) {
|
|
396
|
+
if fill.is_none() && stroke.is_none() {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let (x, y) = (start.x, start.y);
|
|
401
|
+
self.graphics_states.save_state();
|
|
402
|
+
|
|
403
|
+
// Calculating the glyphs bbox is very expensive but not always necessary, so omit
|
|
404
|
+
// if not needed.
|
|
405
|
+
let bbox_important = self.bbox_important;
|
|
406
|
+
let calculate_bbox = |is_solid: bool| bbox_important || !is_solid;
|
|
407
|
+
|
|
408
|
+
let fill_action = |sb: &mut ContentBuilder,
|
|
409
|
+
sc: &mut SerializeContext,
|
|
410
|
+
chunk_container: &mut ChunkContainer,
|
|
411
|
+
fill: &Fill| {
|
|
412
|
+
let bbox = if calculate_bbox(matches!(&fill.paint.0, InnerPaint::Color(_))) {
|
|
413
|
+
let bbox = get_glyphs_bbox(glyphs, x, y, font_size, font.clone());
|
|
414
|
+
sb.expand_bbox(bbox);
|
|
415
|
+
bbox
|
|
416
|
+
} else {
|
|
417
|
+
Rect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap()
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
sb.content_set_fill_properties(bbox, fill, sc, chunk_container)
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
let stroke_action = |sb: &mut ContentBuilder,
|
|
424
|
+
sc: &mut SerializeContext,
|
|
425
|
+
chunk_container: &mut ChunkContainer,
|
|
426
|
+
stroke: &Stroke| {
|
|
427
|
+
let bbox = if calculate_bbox(matches!(&stroke.paint.0, InnerPaint::Color(_))) {
|
|
428
|
+
// TODO: Bbox should also account for stroke.
|
|
429
|
+
let bbox = get_glyphs_bbox(glyphs, x, y, font_size, font.clone());
|
|
430
|
+
sb.expand_bbox(bbox);
|
|
431
|
+
bbox
|
|
432
|
+
} else {
|
|
433
|
+
Rect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap()
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
sb.content_set_stroke_properties(bbox, stroke, sc, chunk_container);
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
let set_fill_opacity = |sb: &mut ContentBuilder, fill: &Fill| {
|
|
440
|
+
// PDF viewers don't show patterns with fill/stroke opacities consistently.
|
|
441
|
+
// Because of this, the opacity is accounted for in the pattern itself.
|
|
442
|
+
if !matches!(&fill.paint.0, &InnerPaint::Pattern(_)) {
|
|
443
|
+
sb.set_fill_opacity(fill.opacity);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
let set_stroke_opacity = |sb: &mut ContentBuilder, stroke: &Stroke| {
|
|
448
|
+
// PDF viewers don't show patterns with fill/stroke opacities consistently.
|
|
449
|
+
// Because of this, the opacity is accounted for in the pattern itself.
|
|
450
|
+
if !matches!(&stroke.paint.0, &InnerPaint::Pattern(_)) {
|
|
451
|
+
// PDF viewers don't show patterns with fill/stroke opacities consistently.
|
|
452
|
+
// Because of this, the opacity is accounted for in the pattern itself.
|
|
453
|
+
sb.set_stroke_opacity(stroke.opacity);
|
|
454
|
+
|
|
455
|
+
// See the comment in `stroke-action` for why we also set the fill opacity.
|
|
456
|
+
sb.set_fill_opacity(stroke.opacity);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
match (fill, stroke) {
|
|
461
|
+
(Some(f), Some(s)) => {
|
|
462
|
+
set_fill_opacity(self, f);
|
|
463
|
+
set_stroke_opacity(self, s);
|
|
464
|
+
|
|
465
|
+
self.fill_stroke_glyph_run(
|
|
466
|
+
x,
|
|
467
|
+
y,
|
|
468
|
+
sc,
|
|
469
|
+
chunk_container,
|
|
470
|
+
TextRenderingMode::FillStroke,
|
|
471
|
+
|sb, sc, chunk_container| {
|
|
472
|
+
fill_action(sb, sc, chunk_container, f);
|
|
473
|
+
stroke_action(sb, sc, chunk_container, s);
|
|
474
|
+
},
|
|
475
|
+
glyphs,
|
|
476
|
+
font.clone(),
|
|
477
|
+
context_color,
|
|
478
|
+
text,
|
|
479
|
+
font_size,
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
(Some(f), None) => {
|
|
483
|
+
set_fill_opacity(self, f);
|
|
484
|
+
|
|
485
|
+
self.fill_stroke_glyph_run(
|
|
486
|
+
x,
|
|
487
|
+
y,
|
|
488
|
+
sc,
|
|
489
|
+
chunk_container,
|
|
490
|
+
TextRenderingMode::Fill,
|
|
491
|
+
|sb, sc, chunk_container| {
|
|
492
|
+
fill_action(sb, sc, chunk_container, f);
|
|
493
|
+
},
|
|
494
|
+
glyphs,
|
|
495
|
+
font.clone(),
|
|
496
|
+
context_color,
|
|
497
|
+
text,
|
|
498
|
+
font_size,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
(None, Some(s)) => {
|
|
502
|
+
set_stroke_opacity(self, s);
|
|
503
|
+
|
|
504
|
+
self.fill_stroke_glyph_run(
|
|
505
|
+
x,
|
|
506
|
+
y,
|
|
507
|
+
sc,
|
|
508
|
+
chunk_container,
|
|
509
|
+
TextRenderingMode::Stroke,
|
|
510
|
+
|sb, sc, chunk_container| {
|
|
511
|
+
stroke_action(sb, sc, chunk_container, s);
|
|
512
|
+
},
|
|
513
|
+
glyphs,
|
|
514
|
+
font.clone(),
|
|
515
|
+
context_color,
|
|
516
|
+
text,
|
|
517
|
+
font_size,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
(None, None) => unreachable!(),
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
self.graphics_states.restore_state();
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/// Encode a successive sequence of glyphs that share the same properties and
|
|
527
|
+
/// can be encoded with one text showing operator.
|
|
528
|
+
#[allow(clippy::too_many_arguments)]
|
|
529
|
+
#[inline(always)]
|
|
530
|
+
fn encode_consecutive_glyph_run(
|
|
531
|
+
&mut self,
|
|
532
|
+
sc: &mut SerializeContext,
|
|
533
|
+
cur_x: &mut f32,
|
|
534
|
+
cur_y: f32,
|
|
535
|
+
font_identifier: FontIdentifier,
|
|
536
|
+
pdf_font: &dyn PdfFont,
|
|
537
|
+
size: f32,
|
|
538
|
+
context_color: rgb::Color,
|
|
539
|
+
glyphs: &[impl Glyph],
|
|
540
|
+
text: &str,
|
|
541
|
+
) {
|
|
542
|
+
let font_name = self
|
|
543
|
+
.rd_builder
|
|
544
|
+
.register_resource(sc.register_font_identifier(font_identifier));
|
|
545
|
+
self.content.set_font(font_name.to_pdf_name(), size);
|
|
546
|
+
self.content.set_text_matrix(
|
|
547
|
+
Transform::from_row(1.0, 0.0, 0.0, -1.0, *cur_x, cur_y).to_pdf_transform(),
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
for glyph in glyphs {
|
|
551
|
+
if glyph.glyph_id() == GlyphId::new(0)
|
|
552
|
+
|| pdf_font.font().postscript_name() == Some("LastResort")
|
|
553
|
+
{
|
|
554
|
+
sc.register_validation_error(ValidationError::ContainsNotDefGlyph(
|
|
555
|
+
pdf_font.font(),
|
|
556
|
+
glyph.location(),
|
|
557
|
+
text[glyph.text_range()].to_string(),
|
|
558
|
+
));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// If it's just a single glyph and without an x_offset, there is no need for individual
|
|
563
|
+
// positioning, so we can use `Tj` instead of `TJ`. The reason we're doing this is because
|
|
564
|
+
// Acrobat's copy-paste is a bit buggy if `/ActualText` is used at the end of a line, but
|
|
565
|
+
// only if `TJ` is used. With `Tj`, it's fine.
|
|
566
|
+
if let [glyph] = glyphs {
|
|
567
|
+
if approx_eq!(f32, glyph.x_offset(1.0), 0.0, epsilon = 0.001) {
|
|
568
|
+
self.encode_single_glyph(cur_x, pdf_font, size, context_color, glyph);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
self.encode_glyphs_with_individual_positioning(
|
|
574
|
+
cur_x,
|
|
575
|
+
pdf_font,
|
|
576
|
+
size,
|
|
577
|
+
context_color,
|
|
578
|
+
glyphs,
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
fn encode_single_glyph(
|
|
583
|
+
&mut self,
|
|
584
|
+
cur_x: &mut f32,
|
|
585
|
+
pdf_font: &dyn PdfFont,
|
|
586
|
+
size: f32,
|
|
587
|
+
context_color: rgb::Color,
|
|
588
|
+
glyph: &impl Glyph,
|
|
589
|
+
) {
|
|
590
|
+
let pdf_glyph = pdf_font
|
|
591
|
+
.get_gid(ColoredGlyph::new(glyph.glyph_id(), context_color))
|
|
592
|
+
.unwrap();
|
|
593
|
+
self.scratch.clear();
|
|
594
|
+
let encoded = &mut self.scratch;
|
|
595
|
+
pdf_glyph.encode_into(encoded);
|
|
596
|
+
self.content.show(Str(encoded));
|
|
597
|
+
*cur_x += glyph.x_advance(size);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
fn encode_glyphs_with_individual_positioning(
|
|
601
|
+
&mut self,
|
|
602
|
+
cur_x: &mut f32,
|
|
603
|
+
pdf_font: &dyn PdfFont,
|
|
604
|
+
size: f32,
|
|
605
|
+
context_color: rgb::Color,
|
|
606
|
+
glyphs: &[impl Glyph],
|
|
607
|
+
) {
|
|
608
|
+
self.scratch.clear();
|
|
609
|
+
let encoded = &mut self.scratch;
|
|
610
|
+
|
|
611
|
+
let mut adjustment = 0.0;
|
|
612
|
+
let mut positioned = self.content.show_positioned();
|
|
613
|
+
let mut items = positioned.items();
|
|
614
|
+
|
|
615
|
+
for glyph in glyphs {
|
|
616
|
+
let pdf_glyph = pdf_font
|
|
617
|
+
.get_gid(ColoredGlyph::new(glyph.glyph_id(), context_color))
|
|
618
|
+
.unwrap();
|
|
619
|
+
|
|
620
|
+
let scale = |val| val * pdf_font.units_per_em();
|
|
621
|
+
|
|
622
|
+
let x_advance = scale(glyph.x_advance(1.0));
|
|
623
|
+
let font_advance = pdf_font
|
|
624
|
+
.font()
|
|
625
|
+
.advance_width(glyph.glyph_id())
|
|
626
|
+
.map(|n| scale(n / pdf_font.font().units_per_em()));
|
|
627
|
+
let x_offset = scale(glyph.x_offset(1.0));
|
|
628
|
+
|
|
629
|
+
adjustment += x_offset;
|
|
630
|
+
|
|
631
|
+
// Make sure we don't write miniscule adjustments
|
|
632
|
+
if !approx_eq!(f32, adjustment, 0.0, epsilon = 0.001) {
|
|
633
|
+
if !encoded.is_empty() {
|
|
634
|
+
items.show(Str(encoded));
|
|
635
|
+
encoded.clear();
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Adjustment is always in 1000 units, even for Type3 fonts.
|
|
639
|
+
items.adjust(-(adjustment / pdf_font.units_per_em() * PDF_UNITS_PER_EM));
|
|
640
|
+
adjustment = 0.0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
pdf_glyph.encode_into(encoded);
|
|
644
|
+
|
|
645
|
+
if let Some(font_advance) = font_advance {
|
|
646
|
+
adjustment += x_advance - font_advance;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
adjustment -= x_offset;
|
|
650
|
+
// cur_x/cur_y and glyph metrics are in user space units.
|
|
651
|
+
*cur_x += glyph.x_advance(size);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if !encoded.is_empty() {
|
|
655
|
+
items.show(Str(encoded));
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
items.finish();
|
|
659
|
+
positioned.finish();
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
#[allow(clippy::too_many_arguments)]
|
|
663
|
+
fn fill_stroke_glyph_run(
|
|
664
|
+
&mut self,
|
|
665
|
+
x: f32,
|
|
666
|
+
ys: f32,
|
|
667
|
+
sc: &mut SerializeContext,
|
|
668
|
+
chunk_container: &mut ChunkContainer,
|
|
669
|
+
fill_render_mode: TextRenderingMode,
|
|
670
|
+
action: impl FnOnce(&mut ContentBuilder, &mut SerializeContext, &mut ChunkContainer),
|
|
671
|
+
glyphs: &[impl Glyph],
|
|
672
|
+
font: Font,
|
|
673
|
+
context_color: rgb::Color,
|
|
674
|
+
text: &str,
|
|
675
|
+
font_size: f32,
|
|
676
|
+
) {
|
|
677
|
+
if glyphs.is_empty() {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
self.apply_isolated_op(
|
|
682
|
+
|_, _, _| {},
|
|
683
|
+
|sb, sc, chunk_container| {
|
|
684
|
+
let mut cur_x = x;
|
|
685
|
+
let mut cur_y = ys;
|
|
686
|
+
|
|
687
|
+
// When the glyph ranges are monotonically decreasing, the glyphs are in reverse
|
|
688
|
+
// order and we should write `/ReversedChars` marked content.
|
|
689
|
+
let reversed = glyphs.len() > 1
|
|
690
|
+
&& glyphs
|
|
691
|
+
.windows(2)
|
|
692
|
+
.all(|w| w[0].text_range().start >= w[1].text_range().end);
|
|
693
|
+
|
|
694
|
+
action(sb, sc, chunk_container);
|
|
695
|
+
sb.content.begin_text();
|
|
696
|
+
|
|
697
|
+
let font_container = sc.register_font_container(font.clone());
|
|
698
|
+
let do_text_span = reversed
|
|
699
|
+
|| use_text_spanner(
|
|
700
|
+
glyphs,
|
|
701
|
+
text,
|
|
702
|
+
context_color,
|
|
703
|
+
&mut font_container.borrow_mut(),
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
if do_text_span {
|
|
707
|
+
// Separate into distinct glyph runs that either are encoded using actual text, or are
|
|
708
|
+
// not.
|
|
709
|
+
let spanned = GlyphSpanner::new(
|
|
710
|
+
glyphs,
|
|
711
|
+
text,
|
|
712
|
+
reversed,
|
|
713
|
+
sc.serialize_settings()
|
|
714
|
+
.validators()
|
|
715
|
+
.requires_codepoint_mappings(),
|
|
716
|
+
context_color,
|
|
717
|
+
font_container.clone(),
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
for fragment in spanned {
|
|
721
|
+
sb.fill_stroke_glyph_span(
|
|
722
|
+
&mut cur_x,
|
|
723
|
+
&mut cur_y,
|
|
724
|
+
fragment,
|
|
725
|
+
sc,
|
|
726
|
+
fill_render_mode,
|
|
727
|
+
font_container.clone(),
|
|
728
|
+
context_color,
|
|
729
|
+
text,
|
|
730
|
+
font_size,
|
|
731
|
+
reversed,
|
|
732
|
+
)
|
|
733
|
+
}
|
|
734
|
+
} else {
|
|
735
|
+
let glyph_span = GlyphSpan::Unspanned(glyphs);
|
|
736
|
+
|
|
737
|
+
sb.fill_stroke_glyph_span(
|
|
738
|
+
&mut cur_x,
|
|
739
|
+
&mut cur_y,
|
|
740
|
+
glyph_span,
|
|
741
|
+
sc,
|
|
742
|
+
fill_render_mode,
|
|
743
|
+
font_container.clone(),
|
|
744
|
+
context_color,
|
|
745
|
+
text,
|
|
746
|
+
font_size,
|
|
747
|
+
reversed,
|
|
748
|
+
)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
sb.content.end_text();
|
|
752
|
+
},
|
|
753
|
+
sc,
|
|
754
|
+
chunk_container,
|
|
755
|
+
)
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
#[allow(clippy::too_many_arguments)]
|
|
759
|
+
fn fill_stroke_glyph_span(
|
|
760
|
+
&mut self,
|
|
761
|
+
cur_x: &mut f32,
|
|
762
|
+
cur_y: &mut f32,
|
|
763
|
+
fragment: GlyphSpan<'_, impl Glyph>,
|
|
764
|
+
sc: &mut SerializeContext,
|
|
765
|
+
fill_render_mode: TextRenderingMode,
|
|
766
|
+
font_container: Rc<RefCell<FontContainer>>,
|
|
767
|
+
context_color: rgb::Color,
|
|
768
|
+
text: &str,
|
|
769
|
+
font_size: f32,
|
|
770
|
+
reversed: bool,
|
|
771
|
+
) {
|
|
772
|
+
if let Some(text) = fragment.actual_text() {
|
|
773
|
+
let mut actual_text = self
|
|
774
|
+
.content
|
|
775
|
+
.begin_marked_content_with_properties(Name(b"Span"));
|
|
776
|
+
actual_text.properties().actual_text(TextStr(text));
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if reversed {
|
|
780
|
+
self.content.begin_marked_content(Name(b"ReversedChars"));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Segment into glyph runs that can be encoded in one go using a PDF
|
|
784
|
+
// text showing operator (i.e. no y shift, same Type3 font, etc.)
|
|
785
|
+
let segmented = GlyphGrouper::new(font_container.clone(), context_color, fragment.glyphs());
|
|
786
|
+
|
|
787
|
+
for glyph_group in segmented {
|
|
788
|
+
self.fill_stroke_glyph_group(
|
|
789
|
+
cur_x,
|
|
790
|
+
cur_y,
|
|
791
|
+
glyph_group,
|
|
792
|
+
sc,
|
|
793
|
+
fill_render_mode,
|
|
794
|
+
font_container.clone(),
|
|
795
|
+
context_color,
|
|
796
|
+
text,
|
|
797
|
+
font_size,
|
|
798
|
+
)
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if reversed {
|
|
802
|
+
self.content.end_marked_content();
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if fragment.actual_text().is_some() {
|
|
806
|
+
self.content.end_marked_content();
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
#[allow(clippy::too_many_arguments)]
|
|
811
|
+
fn fill_stroke_glyph_group(
|
|
812
|
+
&mut self,
|
|
813
|
+
cur_x: &mut f32,
|
|
814
|
+
cur_y: &mut f32,
|
|
815
|
+
glyph_group: GlyphGroup<'_, impl Glyph>,
|
|
816
|
+
sc: &mut SerializeContext,
|
|
817
|
+
fill_render_mode: TextRenderingMode,
|
|
818
|
+
font_container: Rc<RefCell<FontContainer>>,
|
|
819
|
+
context_color: rgb::Color,
|
|
820
|
+
text: &str,
|
|
821
|
+
font_size: f32,
|
|
822
|
+
) {
|
|
823
|
+
let borrowed = font_container.borrow();
|
|
824
|
+
let pdf_font = borrowed
|
|
825
|
+
.get_from_identifier(glyph_group.font_identifier.clone())
|
|
826
|
+
.unwrap();
|
|
827
|
+
|
|
828
|
+
if fill_render_mode == TextRenderingMode::Fill || pdf_font.force_fill() {
|
|
829
|
+
self.content
|
|
830
|
+
.set_text_rendering_mode(TextRenderingMode::Fill);
|
|
831
|
+
} else if fill_render_mode == TextRenderingMode::FillStroke {
|
|
832
|
+
self.content
|
|
833
|
+
.set_text_rendering_mode(TextRenderingMode::FillStroke);
|
|
834
|
+
} else {
|
|
835
|
+
self.content
|
|
836
|
+
.set_text_rendering_mode(TextRenderingMode::Stroke);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
self.encode_consecutive_glyph_run(
|
|
840
|
+
sc,
|
|
841
|
+
cur_x,
|
|
842
|
+
*cur_y - glyph_group.y_offset * font_size,
|
|
843
|
+
glyph_group.font_identifier,
|
|
844
|
+
pdf_font,
|
|
845
|
+
font_size,
|
|
846
|
+
context_color,
|
|
847
|
+
glyph_group.glyphs,
|
|
848
|
+
text,
|
|
849
|
+
);
|
|
850
|
+
|
|
851
|
+
*cur_y -= glyph_group.y_advance * font_size;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
pub(crate) fn draw_xobject(
|
|
855
|
+
&mut self,
|
|
856
|
+
sc: &mut SerializeContext,
|
|
857
|
+
chunk_container: &mut ChunkContainer,
|
|
858
|
+
x_object: XObject,
|
|
859
|
+
state: &ExtGState,
|
|
860
|
+
) {
|
|
861
|
+
let bbox = x_object.bbox();
|
|
862
|
+
self.apply_isolated_op(
|
|
863
|
+
|sb, _, _| {
|
|
864
|
+
sb.graphics_states.combine(state);
|
|
865
|
+
sb.expand_bbox(bbox);
|
|
866
|
+
},
|
|
867
|
+
move |sb, sc, chunk_container| {
|
|
868
|
+
let x_object_name = sb
|
|
869
|
+
.rd_builder
|
|
870
|
+
.register_resource(sc.register_resourceable(chunk_container, x_object));
|
|
871
|
+
sb.content.x_object(x_object_name.to_pdf_name());
|
|
872
|
+
},
|
|
873
|
+
sc,
|
|
874
|
+
chunk_container,
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
#[allow(dead_code)]
|
|
879
|
+
pub(crate) fn draw_xobject_by_reference(
|
|
880
|
+
&mut self,
|
|
881
|
+
sc: &mut SerializeContext,
|
|
882
|
+
chunk_container: &mut ChunkContainer,
|
|
883
|
+
bbox: Rect,
|
|
884
|
+
x_object: Ref,
|
|
885
|
+
) {
|
|
886
|
+
// TODO: Consider bbox of XObject somehow?
|
|
887
|
+
self.apply_isolated_op(
|
|
888
|
+
|sb, _, _| {
|
|
889
|
+
sb.expand_bbox(bbox);
|
|
890
|
+
},
|
|
891
|
+
move |sb, _, _| {
|
|
892
|
+
let x_object_name = sb
|
|
893
|
+
.rd_builder
|
|
894
|
+
.register_resource(resource::XObject::new(x_object));
|
|
895
|
+
sb.content.x_object(x_object_name.to_pdf_name());
|
|
896
|
+
},
|
|
897
|
+
sc,
|
|
898
|
+
chunk_container,
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
pub(crate) fn draw_masked(
|
|
903
|
+
&mut self,
|
|
904
|
+
sc: &mut SerializeContext,
|
|
905
|
+
chunk_container: &mut ChunkContainer,
|
|
906
|
+
mask: Mask,
|
|
907
|
+
stream: Stream,
|
|
908
|
+
) {
|
|
909
|
+
let state = ExtGState::new().mask(mask, sc, chunk_container);
|
|
910
|
+
self.uses_mask = true;
|
|
911
|
+
let x_object = XObject::new(stream, false, true, None);
|
|
912
|
+
self.draw_xobject(sc, chunk_container, x_object, &state);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
pub(crate) fn draw_opacified(
|
|
916
|
+
&mut self,
|
|
917
|
+
sc: &mut SerializeContext,
|
|
918
|
+
chunk_container: &mut ChunkContainer,
|
|
919
|
+
opacity: NormalizedF32,
|
|
920
|
+
stream: Stream,
|
|
921
|
+
) {
|
|
922
|
+
let state = ExtGState::new()
|
|
923
|
+
.stroking_alpha(opacity)
|
|
924
|
+
.non_stroking_alpha(opacity);
|
|
925
|
+
let x_object = XObject::new(stream, true, false, None);
|
|
926
|
+
self.draw_xobject(sc, chunk_container, x_object, &state);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
pub(crate) fn draw_isolated(
|
|
930
|
+
&mut self,
|
|
931
|
+
sc: &mut SerializeContext,
|
|
932
|
+
chunk_container: &mut ChunkContainer,
|
|
933
|
+
stream: Stream,
|
|
934
|
+
) {
|
|
935
|
+
let state = ExtGState::new();
|
|
936
|
+
let x_object = XObject::new(stream, true, false, None);
|
|
937
|
+
self.draw_xobject(sc, chunk_container, x_object, &state);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
#[cfg(feature = "raster-images")]
|
|
941
|
+
pub(crate) fn draw_image(
|
|
942
|
+
&mut self,
|
|
943
|
+
image: Image,
|
|
944
|
+
size: Size,
|
|
945
|
+
sc: &mut SerializeContext,
|
|
946
|
+
chunk_container: &mut ChunkContainer,
|
|
947
|
+
) {
|
|
948
|
+
self.apply_isolated_op(
|
|
949
|
+
|sb, _, _| {
|
|
950
|
+
// Scale the image from 1x1 to the actual dimensions.
|
|
951
|
+
let transform =
|
|
952
|
+
Transform::from_row(size.width(), 0.0, 0.0, -size.height(), 0.0, size.height());
|
|
953
|
+
sb.concat_transform(&transform);
|
|
954
|
+
sb.expand_bbox(Rect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap());
|
|
955
|
+
},
|
|
956
|
+
move |sb, sc, chunk_container| {
|
|
957
|
+
let image_name = sb.rd_builder.register_resource(resource::XObject::new(
|
|
958
|
+
sc.register_image(chunk_container, image),
|
|
959
|
+
));
|
|
960
|
+
|
|
961
|
+
sb.content.x_object(image_name.to_pdf_name());
|
|
962
|
+
},
|
|
963
|
+
sc,
|
|
964
|
+
chunk_container,
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
pub(crate) fn draw_shading(
|
|
969
|
+
&mut self,
|
|
970
|
+
shading: &ShadingFunction,
|
|
971
|
+
sc: &mut SerializeContext,
|
|
972
|
+
chunk_container: &mut ChunkContainer,
|
|
973
|
+
) {
|
|
974
|
+
self.apply_isolated_op(
|
|
975
|
+
|_, _, _| {},
|
|
976
|
+
move |sb, sc, chunk_container| {
|
|
977
|
+
let sh = sb
|
|
978
|
+
.rd_builder
|
|
979
|
+
.register_resource(sc.register_resourceable(chunk_container, shading.clone()));
|
|
980
|
+
sb.content.shading(sh.to_pdf_name());
|
|
981
|
+
},
|
|
982
|
+
sc,
|
|
983
|
+
chunk_container,
|
|
984
|
+
)
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
fn set_fill_opacity(&mut self, alpha: NormalizedF32) {
|
|
988
|
+
if alpha.get() != 1.0 {
|
|
989
|
+
let state = ExtGState::new().non_stroking_alpha(alpha);
|
|
990
|
+
self.graphics_states.combine(&state);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
fn set_stroke_opacity(&mut self, alpha: NormalizedF32) {
|
|
995
|
+
if alpha.get() != 1.0 {
|
|
996
|
+
let state = ExtGState::new().stroking_alpha(alpha);
|
|
997
|
+
self.graphics_states.combine(&state);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
fn apply_isolated_op(
|
|
1002
|
+
&mut self,
|
|
1003
|
+
prep: impl FnOnce(&mut Self, &mut SerializeContext, &mut ChunkContainer),
|
|
1004
|
+
op: impl FnOnce(&mut Self, &mut SerializeContext, &mut ChunkContainer),
|
|
1005
|
+
sc: &mut SerializeContext,
|
|
1006
|
+
chunk_container: &mut ChunkContainer,
|
|
1007
|
+
) {
|
|
1008
|
+
self.save_graphics_state();
|
|
1009
|
+
self.content_save_state();
|
|
1010
|
+
|
|
1011
|
+
prep(self, sc, chunk_container);
|
|
1012
|
+
|
|
1013
|
+
let transform = self.cur_transform_with_root_transform();
|
|
1014
|
+
|
|
1015
|
+
if transform != Transform::identity() {
|
|
1016
|
+
self.content.transform(transform.to_pdf_transform());
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
let state = self.graphics_states.cur().ext_g_state().clone();
|
|
1020
|
+
|
|
1021
|
+
if !state.empty() {
|
|
1022
|
+
let ext = self.rd_builder.register_resource::<resource::ExtGState>(
|
|
1023
|
+
sc.register_resourceable(chunk_container, state),
|
|
1024
|
+
);
|
|
1025
|
+
self.content.set_parameters(ext.to_pdf_name());
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
op(self, sc, chunk_container);
|
|
1029
|
+
|
|
1030
|
+
self.content.restore_state();
|
|
1031
|
+
self.restore_graphics_state();
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
#[allow(clippy::too_many_arguments)]
|
|
1035
|
+
fn content_set_fill_stroke_properties(
|
|
1036
|
+
&mut self,
|
|
1037
|
+
bounds: Rect,
|
|
1038
|
+
paint: &Paint,
|
|
1039
|
+
opacity: NormalizedF32,
|
|
1040
|
+
sc: &mut SerializeContext,
|
|
1041
|
+
chunk_container: &mut ChunkContainer,
|
|
1042
|
+
mut set_pattern_fn: impl FnMut(&mut Content, String),
|
|
1043
|
+
mut set_solid_fn: impl FnMut(&mut Content, ContentColorSpace, &Color),
|
|
1044
|
+
) {
|
|
1045
|
+
let pattern_transform = |transform: Transform| -> Transform {
|
|
1046
|
+
transform.post_concat(self.cur_transform_with_root_transform())
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
let mut write_gradient =
|
|
1050
|
+
|gradient_props: GradientProperties,
|
|
1051
|
+
sc: &mut SerializeContext,
|
|
1052
|
+
chunk_container: &mut ChunkContainer,
|
|
1053
|
+
transform: Transform,
|
|
1054
|
+
content_builder: &mut ContentBuilder| {
|
|
1055
|
+
if let Some((color, opacity)) = gradient_props.single_stop_color() {
|
|
1056
|
+
// Write gradients with one stop as a solid color fill.
|
|
1057
|
+
content_builder.set_fill_opacity(opacity);
|
|
1058
|
+
let cs = color.color_space(sc);
|
|
1059
|
+
let color_space_resource =
|
|
1060
|
+
Self::cs_to_content_cs(content_builder, sc, chunk_container, cs);
|
|
1061
|
+
set_solid_fn(&mut content_builder.content, color_space_resource, color);
|
|
1062
|
+
} else {
|
|
1063
|
+
let shading_mask = Mask::new_from_shading(
|
|
1064
|
+
gradient_props.clone(),
|
|
1065
|
+
transform,
|
|
1066
|
+
bounds,
|
|
1067
|
+
sc,
|
|
1068
|
+
chunk_container,
|
|
1069
|
+
);
|
|
1070
|
+
|
|
1071
|
+
let shading_pattern = ShadingPattern::new(
|
|
1072
|
+
gradient_props,
|
|
1073
|
+
content_builder
|
|
1074
|
+
.cur_transform_with_root_transform()
|
|
1075
|
+
.pre_concat(transform),
|
|
1076
|
+
);
|
|
1077
|
+
let color_space = content_builder
|
|
1078
|
+
.rd_builder
|
|
1079
|
+
.register_resource::<resource::Pattern>(
|
|
1080
|
+
sc.register_resourceable(chunk_container, shading_pattern),
|
|
1081
|
+
);
|
|
1082
|
+
|
|
1083
|
+
if let Some(shading_mask) = shading_mask {
|
|
1084
|
+
let state = ExtGState::new().mask(shading_mask, sc, chunk_container);
|
|
1085
|
+
content_builder.uses_mask = true;
|
|
1086
|
+
|
|
1087
|
+
let ext = content_builder
|
|
1088
|
+
.rd_builder
|
|
1089
|
+
.register_resource::<resource::ExtGState>(
|
|
1090
|
+
sc.register_resourceable(chunk_container, state),
|
|
1091
|
+
);
|
|
1092
|
+
content_builder.content.set_parameters(ext.to_pdf_name());
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
set_pattern_fn(&mut content_builder.content, color_space);
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
match &paint.0 {
|
|
1100
|
+
InnerPaint::Color(c) => {
|
|
1101
|
+
let cs = c.color_space(sc);
|
|
1102
|
+
let color_space_resource = Self::cs_to_content_cs(self, sc, chunk_container, cs);
|
|
1103
|
+
set_solid_fn(&mut self.content, color_space_resource, c);
|
|
1104
|
+
}
|
|
1105
|
+
InnerPaint::LinearGradient(lg) => {
|
|
1106
|
+
let (gradient_props, transform) = lg.clone().gradient_properties(bounds);
|
|
1107
|
+
write_gradient(gradient_props, sc, chunk_container, transform, self);
|
|
1108
|
+
}
|
|
1109
|
+
InnerPaint::RadialGradient(rg) => {
|
|
1110
|
+
let (gradient_props, transform) = rg.clone().gradient_properties(bounds);
|
|
1111
|
+
write_gradient(gradient_props, sc, chunk_container, transform, self);
|
|
1112
|
+
}
|
|
1113
|
+
InnerPaint::SweepGradient(sg) => {
|
|
1114
|
+
let (gradient_props, transform) = sg.clone().gradient_properties(bounds);
|
|
1115
|
+
write_gradient(gradient_props, sc, chunk_container, transform, self);
|
|
1116
|
+
}
|
|
1117
|
+
InnerPaint::Pattern(pat) => {
|
|
1118
|
+
let mut pat = Arc::unwrap_or_clone(pat.clone());
|
|
1119
|
+
pat.transform = pattern_transform(pat.transform);
|
|
1120
|
+
|
|
1121
|
+
let tiling_pattern = TilingPattern::new(
|
|
1122
|
+
pat.stream,
|
|
1123
|
+
pat.transform,
|
|
1124
|
+
opacity,
|
|
1125
|
+
pat.width,
|
|
1126
|
+
pat.height,
|
|
1127
|
+
sc,
|
|
1128
|
+
chunk_container,
|
|
1129
|
+
);
|
|
1130
|
+
|
|
1131
|
+
let color_space = self.rd_builder.register_resource::<resource::Pattern>(
|
|
1132
|
+
sc.register_resourceable(chunk_container, tiling_pattern),
|
|
1133
|
+
);
|
|
1134
|
+
set_pattern_fn(&mut self.content, color_space);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
fn cs_to_content_cs(
|
|
1140
|
+
content_builder: &mut ContentBuilder,
|
|
1141
|
+
sc: &mut SerializeContext,
|
|
1142
|
+
chunk_container: &mut ChunkContainer,
|
|
1143
|
+
cs: ColorSpace,
|
|
1144
|
+
) -> ContentColorSpace {
|
|
1145
|
+
match sc.register_colorspace(chunk_container, cs) {
|
|
1146
|
+
MaybeDeviceColorSpace::ColorSpace(s) => {
|
|
1147
|
+
ContentColorSpace::Named(content_builder.rd_builder.register_resource(s))
|
|
1148
|
+
}
|
|
1149
|
+
MaybeDeviceColorSpace::DeviceGray => ContentColorSpace::Device,
|
|
1150
|
+
MaybeDeviceColorSpace::DeviceRgb => ContentColorSpace::Device,
|
|
1151
|
+
MaybeDeviceColorSpace::DeviceCMYK => ContentColorSpace::Device,
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
fn content_set_fill_properties(
|
|
1156
|
+
&mut self,
|
|
1157
|
+
bounds: Rect,
|
|
1158
|
+
fill: &Fill,
|
|
1159
|
+
serializer_context: &mut SerializeContext,
|
|
1160
|
+
chunk_container: &mut ChunkContainer,
|
|
1161
|
+
) {
|
|
1162
|
+
fn set_pattern_fn(content: &mut Content, color_space: String) {
|
|
1163
|
+
content.set_fill_color_space(pdf_writer::types::ColorSpaceOperand::Pattern);
|
|
1164
|
+
content.set_fill_pattern(None, color_space.to_pdf_name());
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
fn set_solid_fn(content: &mut Content, color_space: ContentColorSpace, color: &Color) {
|
|
1168
|
+
match color_space {
|
|
1169
|
+
ContentColorSpace::Device => match color {
|
|
1170
|
+
Color::Regular(crate::color::RegularColor::Rgb(r)) => {
|
|
1171
|
+
let comps = r.to_pdf_color();
|
|
1172
|
+
content.set_fill_rgb(comps[0], comps[1], comps[2]);
|
|
1173
|
+
}
|
|
1174
|
+
Color::Regular(crate::color::RegularColor::Luma(l)) => {
|
|
1175
|
+
content.set_fill_gray(l.to_pdf_color());
|
|
1176
|
+
}
|
|
1177
|
+
Color::Regular(crate::color::RegularColor::Cmyk(c)) => {
|
|
1178
|
+
let comps = c.to_pdf_color();
|
|
1179
|
+
content.set_fill_cmyk(comps[0], comps[1], comps[2], comps[3]);
|
|
1180
|
+
}
|
|
1181
|
+
Color::Special(_) => {
|
|
1182
|
+
panic!("Device color space cannot be used with special colors")
|
|
1183
|
+
}
|
|
1184
|
+
},
|
|
1185
|
+
ContentColorSpace::Named(n) => {
|
|
1186
|
+
content.set_fill_color_space(n.to_pdf_name());
|
|
1187
|
+
content.set_fill_color(color.to_pdf_color());
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
self.content_set_fill_stroke_properties(
|
|
1193
|
+
bounds,
|
|
1194
|
+
&fill.paint,
|
|
1195
|
+
fill.opacity,
|
|
1196
|
+
serializer_context,
|
|
1197
|
+
chunk_container,
|
|
1198
|
+
set_pattern_fn,
|
|
1199
|
+
set_solid_fn,
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
fn content_set_stroke_properties(
|
|
1204
|
+
&mut self,
|
|
1205
|
+
bounds: Rect,
|
|
1206
|
+
stroke: &Stroke,
|
|
1207
|
+
serializer_context: &mut SerializeContext,
|
|
1208
|
+
chunk_container: &mut ChunkContainer,
|
|
1209
|
+
) {
|
|
1210
|
+
fn set_pattern_fn(content: &mut Content, color_space: String) {
|
|
1211
|
+
content.set_stroke_color_space(pdf_writer::types::ColorSpaceOperand::Pattern);
|
|
1212
|
+
content.set_stroke_pattern(None, color_space.to_pdf_name());
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
fn set_solid_fn(content: &mut Content, color_space: ContentColorSpace, color: &Color) {
|
|
1216
|
+
match color_space {
|
|
1217
|
+
ContentColorSpace::Device => match color {
|
|
1218
|
+
Color::Regular(crate::color::RegularColor::Rgb(r)) => {
|
|
1219
|
+
let comps = r.to_pdf_color();
|
|
1220
|
+
content.set_stroke_rgb(comps[0], comps[1], comps[2]);
|
|
1221
|
+
}
|
|
1222
|
+
Color::Regular(crate::color::RegularColor::Luma(l)) => {
|
|
1223
|
+
content.set_stroke_gray(l.to_pdf_color());
|
|
1224
|
+
}
|
|
1225
|
+
Color::Regular(crate::color::RegularColor::Cmyk(c)) => {
|
|
1226
|
+
let comps = c.to_pdf_color();
|
|
1227
|
+
content.set_stroke_cmyk(comps[0], comps[1], comps[2], comps[3]);
|
|
1228
|
+
}
|
|
1229
|
+
Color::Special(_) => {
|
|
1230
|
+
panic!("Device color space cannot be used with special colors")
|
|
1231
|
+
}
|
|
1232
|
+
},
|
|
1233
|
+
ContentColorSpace::Named(n) => {
|
|
1234
|
+
content.set_stroke_color_space(n.to_pdf_name());
|
|
1235
|
+
content.set_stroke_color(color.to_pdf_color());
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
self.content_set_fill_stroke_properties(
|
|
1241
|
+
bounds,
|
|
1242
|
+
&stroke.paint,
|
|
1243
|
+
stroke.opacity,
|
|
1244
|
+
serializer_context,
|
|
1245
|
+
chunk_container,
|
|
1246
|
+
set_pattern_fn,
|
|
1247
|
+
set_solid_fn,
|
|
1248
|
+
);
|
|
1249
|
+
|
|
1250
|
+
// Only write if they don't correspond to the default values as defined in the
|
|
1251
|
+
// PDF specification.
|
|
1252
|
+
if stroke.width != 1.0 {
|
|
1253
|
+
self.content.set_line_width(stroke.width);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
if stroke.miter_limit != 10.0 {
|
|
1257
|
+
self.content.set_miter_limit(stroke.miter_limit);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
if stroke.line_cap != LineCap::Butt {
|
|
1261
|
+
self.content.set_line_cap(stroke.line_cap.to_pdf_line_cap());
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
if stroke.line_join != LineJoin::Miter {
|
|
1265
|
+
self.content
|
|
1266
|
+
.set_line_join(stroke.line_join.to_pdf_line_join());
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
if let Some(stroke_dash) = &stroke.dash {
|
|
1270
|
+
self.content
|
|
1271
|
+
.set_dash_pattern(stroke_dash.array.iter().copied(), stroke_dash.offset);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
fn content_draw_path(&mut self, path_data: impl Iterator<Item = PathSegment>) {
|
|
1276
|
+
// Taken from resvg
|
|
1277
|
+
fn calc(n1: f32, n2: f32) -> f32 {
|
|
1278
|
+
(n1 + n2 * 2.0) / 3.0
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
let mut p_prev = None;
|
|
1282
|
+
|
|
1283
|
+
for operation in path_data {
|
|
1284
|
+
match operation {
|
|
1285
|
+
PathSegment::MoveTo(p) => {
|
|
1286
|
+
self.content.move_to(p.x, p.y);
|
|
1287
|
+
p_prev = Some(p);
|
|
1288
|
+
}
|
|
1289
|
+
PathSegment::LineTo(p) => {
|
|
1290
|
+
self.content.line_to(p.x, p.y);
|
|
1291
|
+
p_prev = Some(p);
|
|
1292
|
+
}
|
|
1293
|
+
PathSegment::QuadTo(p1, p2) => {
|
|
1294
|
+
// Since PDF doesn't support quad curves, we need to convert them into
|
|
1295
|
+
// cubic.
|
|
1296
|
+
let prev = p_prev.unwrap();
|
|
1297
|
+
self.content.cubic_to(
|
|
1298
|
+
calc(prev.x, p1.x),
|
|
1299
|
+
calc(prev.y, p1.y),
|
|
1300
|
+
calc(p2.x, p1.x),
|
|
1301
|
+
calc(p2.y, p1.y),
|
|
1302
|
+
p2.x,
|
|
1303
|
+
p2.y,
|
|
1304
|
+
);
|
|
1305
|
+
p_prev = Some(p2);
|
|
1306
|
+
}
|
|
1307
|
+
PathSegment::CubicTo(p1, p2, p3) => {
|
|
1308
|
+
self.content.cubic_to(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
|
1309
|
+
p_prev = Some(p3);
|
|
1310
|
+
}
|
|
1311
|
+
PathSegment::Close => {
|
|
1312
|
+
self.content.close_path();
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Note that this isn't a 100% accurate calculation, it can overestimate (and in a few cases
|
|
1320
|
+
// even underestimate), but it should be good enough for the majority of the cases.
|
|
1321
|
+
// TODO: Improve this so that `zalgo_text` test case shows up fully in the reference image.
|
|
1322
|
+
fn get_glyphs_bbox(glyphs: &[impl Glyph], x: f32, y: f32, size: f32, font: Font) -> Rect {
|
|
1323
|
+
let font_bbox = font.bbox();
|
|
1324
|
+
let (mut bl, mut bt, mut br, mut bb) = font_bbox
|
|
1325
|
+
.transform(Transform::from_scale(
|
|
1326
|
+
size / font.units_per_em(),
|
|
1327
|
+
-size / font.units_per_em(),
|
|
1328
|
+
))
|
|
1329
|
+
.and_then(|b| b.transform(Transform::from_translate(x, y)))
|
|
1330
|
+
.map(|b| (b.left(), b.top(), b.right(), b.bottom()))
|
|
1331
|
+
.unwrap_or((x, y, x + 1.0, y + 1.0));
|
|
1332
|
+
|
|
1333
|
+
let mut x = x;
|
|
1334
|
+
let mut y = y;
|
|
1335
|
+
|
|
1336
|
+
for glyph in glyphs {
|
|
1337
|
+
let xo = glyph.x_offset(size);
|
|
1338
|
+
let xa = glyph.x_advance(size);
|
|
1339
|
+
let yo = glyph.y_offset(size);
|
|
1340
|
+
let ya = glyph.y_advance(size);
|
|
1341
|
+
|
|
1342
|
+
x += xa;
|
|
1343
|
+
y -= ya;
|
|
1344
|
+
|
|
1345
|
+
bl = bl.min(x + xo);
|
|
1346
|
+
br = br.max(x + xo);
|
|
1347
|
+
bt = bt.min(y - yo);
|
|
1348
|
+
bb = bb.max(y - yo);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
Rect::from_ltrb(bl, bt, br, bb).unwrap()
|
|
1352
|
+
}
|