blitz-pdf 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +300 -10
- 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,463 @@
|
|
|
1
|
+
use std::cell::{RefCell, RefMut};
|
|
2
|
+
use std::ops::Range;
|
|
3
|
+
use std::rc::Rc;
|
|
4
|
+
|
|
5
|
+
use crate::color::rgb;
|
|
6
|
+
use crate::text::type3::ColoredGlyph;
|
|
7
|
+
use crate::text::Glyph;
|
|
8
|
+
use crate::text::{FontContainer, FontIdentifier};
|
|
9
|
+
|
|
10
|
+
pub(crate) enum GlyphSpan<'a, T>
|
|
11
|
+
where
|
|
12
|
+
T: Glyph,
|
|
13
|
+
{
|
|
14
|
+
Unspanned(&'a [T]),
|
|
15
|
+
Spanned(&'a [T], &'a str),
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl<T> GlyphSpan<'_, T>
|
|
19
|
+
where
|
|
20
|
+
T: Glyph,
|
|
21
|
+
{
|
|
22
|
+
pub(crate) fn glyphs(&self) -> &[T] {
|
|
23
|
+
match self {
|
|
24
|
+
GlyphSpan::Unspanned(glyphs) => glyphs,
|
|
25
|
+
GlyphSpan::Spanned(glyphs, _) => glyphs,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pub(crate) fn actual_text(&self) -> Option<&str> {
|
|
30
|
+
match self {
|
|
31
|
+
GlyphSpan::Unspanned(_) => None,
|
|
32
|
+
GlyphSpan::Spanned(_, text) => Some(text),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// In PDF, correspondences between glyphs and Unicode codepoints are expressed
|
|
38
|
+
/// via a CMAP. In a CMAP, you can assign a sequence of Unicode codepoints to each
|
|
39
|
+
/// glyph. There are two issues with this approach:
|
|
40
|
+
/// - How to deal with the fact that the same glyph might be assigned two different codepoints
|
|
41
|
+
/// in different contexts (i.e. space and NZWJ).
|
|
42
|
+
/// - How to deal with complex shaping scenarios, where there is not a one-to-one or
|
|
43
|
+
/// one-to-many correspondence between glyphs and codepoints, but instead a many-to-one
|
|
44
|
+
/// or many-to-many mapping.
|
|
45
|
+
///
|
|
46
|
+
/// The answer to this is the `ActualText` feature of PDF, which allows to define some custom
|
|
47
|
+
/// actual text for a number of glyphs, which overrides anything else. Unfortunately, this
|
|
48
|
+
/// is seemingly only supported in Acrobat and Chrome, but it's the only proper way of addressing
|
|
49
|
+
/// this issue.
|
|
50
|
+
///
|
|
51
|
+
/// This is the task of the `TextSpanner`. Given a sequence of glyphs, it segments the
|
|
52
|
+
/// sequence into subruns of glyphs that either do need to be wrapped in an actual text
|
|
53
|
+
/// attribute, or not.
|
|
54
|
+
pub(crate) struct GlyphSpanner<'a, T>
|
|
55
|
+
where
|
|
56
|
+
T: Glyph,
|
|
57
|
+
{
|
|
58
|
+
slice: &'a [T],
|
|
59
|
+
context_color: rgb::Color,
|
|
60
|
+
reversed: bool,
|
|
61
|
+
forbid_invalid_codepoints: bool,
|
|
62
|
+
font_container: Rc<RefCell<FontContainer>>,
|
|
63
|
+
text: &'a str,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
impl<'a, T> GlyphSpanner<'a, T>
|
|
67
|
+
where
|
|
68
|
+
T: Glyph,
|
|
69
|
+
{
|
|
70
|
+
pub(crate) fn new(
|
|
71
|
+
slice: &'a [T],
|
|
72
|
+
text: &'a str,
|
|
73
|
+
reversed: bool,
|
|
74
|
+
forbid_invalid_codepoints: bool,
|
|
75
|
+
context_color: rgb::Color,
|
|
76
|
+
font_container: Rc<RefCell<FontContainer>>,
|
|
77
|
+
) -> Self {
|
|
78
|
+
Self {
|
|
79
|
+
slice,
|
|
80
|
+
context_color,
|
|
81
|
+
reversed,
|
|
82
|
+
forbid_invalid_codepoints,
|
|
83
|
+
text,
|
|
84
|
+
font_container,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
impl<'a, T> Iterator for GlyphSpanner<'a, T>
|
|
90
|
+
where
|
|
91
|
+
T: Glyph,
|
|
92
|
+
{
|
|
93
|
+
type Item = GlyphSpan<'a, T>;
|
|
94
|
+
|
|
95
|
+
#[inline]
|
|
96
|
+
fn next(&mut self) -> Option<Self::Item> {
|
|
97
|
+
fn func<U>(
|
|
98
|
+
g: &U,
|
|
99
|
+
context_color: rgb::Color,
|
|
100
|
+
previous_range: Option<Range<usize>>,
|
|
101
|
+
forbid_invalid_codepoints: bool,
|
|
102
|
+
mut font_container: RefMut<FontContainer>,
|
|
103
|
+
text: &str,
|
|
104
|
+
) -> (Range<usize>, bool)
|
|
105
|
+
where
|
|
106
|
+
U: Glyph,
|
|
107
|
+
{
|
|
108
|
+
let (identifier, pdf_glyph) =
|
|
109
|
+
font_container.add_glyph(ColoredGlyph::new(g.glyph_id(), context_color));
|
|
110
|
+
let pdf_font = font_container
|
|
111
|
+
.get_from_identifier_mut(identifier.clone())
|
|
112
|
+
.unwrap();
|
|
113
|
+
|
|
114
|
+
let range = g.text_range().clone();
|
|
115
|
+
let text = &text[range.clone()];
|
|
116
|
+
let codepoints = pdf_font.get_codepoints(pdf_glyph);
|
|
117
|
+
// Check if the glyph has already been assigned codepoints that don't match the
|
|
118
|
+
// one we are seeing right now.
|
|
119
|
+
let incompatible_codepoint = codepoints.is_some_and(|c| c != text);
|
|
120
|
+
|
|
121
|
+
// Only set the codepoint if there isn't a previous, different mapping.
|
|
122
|
+
//
|
|
123
|
+
// If we could set it, we only want to insert a codepoint if we are not already
|
|
124
|
+
// building a spanned run (which is the case if the previous range is the same).
|
|
125
|
+
// If we are building a spanned run, it means that the glyphs are part of the same
|
|
126
|
+
// cluster, in which case only the first glyph should be assigned the codepoint,
|
|
127
|
+
// while all other glyphs in the same cluster should not be assigned anything.
|
|
128
|
+
// Otherwise, when copying text from the PDF, we will get the same codepoint multiple
|
|
129
|
+
// times in viewers that don't support `ActualText`.
|
|
130
|
+
//
|
|
131
|
+
// However, in case we are for example exporting to PDF/UA, every glyph is required
|
|
132
|
+
// to have a valid codepoint mapping. So in this case, we still add the codepoints
|
|
133
|
+
// to each glyph in the cluster, this will result in worse copy-pasting in viewers
|
|
134
|
+
// that don't support `ActualText`.
|
|
135
|
+
if !incompatible_codepoint
|
|
136
|
+
&& (previous_range != Some(range.clone()) || forbid_invalid_codepoints)
|
|
137
|
+
&& codepoints != Some(text)
|
|
138
|
+
{
|
|
139
|
+
pdf_font.set_codepoints(pdf_glyph, text.to_string(), g.location());
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
(range, incompatible_codepoint)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let mut use_span = None;
|
|
146
|
+
let mut count = 1;
|
|
147
|
+
|
|
148
|
+
let mut iter = self.slice.iter();
|
|
149
|
+
|
|
150
|
+
// Get the range of the first glyph, as well as whether it's
|
|
151
|
+
// incompatible.
|
|
152
|
+
let (first_range, first_incompatible) = func(
|
|
153
|
+
iter.next()?,
|
|
154
|
+
self.context_color,
|
|
155
|
+
None,
|
|
156
|
+
self.forbid_invalid_codepoints,
|
|
157
|
+
self.font_container.borrow_mut(),
|
|
158
|
+
self.text,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
let mut prev_range = first_range.clone();
|
|
162
|
+
|
|
163
|
+
for next in iter {
|
|
164
|
+
let (next_range, next_incompatible) = func(
|
|
165
|
+
next,
|
|
166
|
+
self.context_color,
|
|
167
|
+
Some(prev_range.clone()),
|
|
168
|
+
self.forbid_invalid_codepoints,
|
|
169
|
+
self.font_container.borrow_mut(),
|
|
170
|
+
self.text,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
match use_span {
|
|
174
|
+
// In this case, we just started and we are looking at the first two glyphs.
|
|
175
|
+
// This decides whether the current run will be spanned, or not.
|
|
176
|
+
None => {
|
|
177
|
+
if prev_range == next_range {
|
|
178
|
+
// The two glyphs are in the same range, so we definitely want this run
|
|
179
|
+
// to be spanned, and also want to include both glyphs in that run.
|
|
180
|
+
use_span = Some(true);
|
|
181
|
+
} else {
|
|
182
|
+
// Else, whether we use a span depends on whether the first glyph
|
|
183
|
+
// is incompatible.
|
|
184
|
+
use_span = Some(first_incompatible);
|
|
185
|
+
|
|
186
|
+
// If either the first glyph or the second glyph are incompatible, they
|
|
187
|
+
// need to be in separate runs, since they are not part of the same cluster.
|
|
188
|
+
if first_incompatible || next_incompatible {
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// If none are incompatible, then `use_span` is false, and we can also
|
|
193
|
+
// include the next glyph in that unspanned run.
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// We are currently building a spanned range, and all glyphs
|
|
197
|
+
// are part of the same cluster.
|
|
198
|
+
Some(true) => {
|
|
199
|
+
// If the next glyph is not part of the same cluster, terminate the current
|
|
200
|
+
// span and don't include the next one.
|
|
201
|
+
if prev_range != next_range {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// We are currently building an unspanned range, meaning the
|
|
206
|
+
// glyphs are not part of the same cluster.
|
|
207
|
+
Some(false) => {
|
|
208
|
+
// If the previous and next glyph are part of the same range this means
|
|
209
|
+
// that they are part of the same cluster. This means that the previous
|
|
210
|
+
// AND the next glyph should be part of the upcoming spanned range, not
|
|
211
|
+
// the current one. To exclude the next glyph, we need to do
|
|
212
|
+
// `count -= 1` before terminating.
|
|
213
|
+
if prev_range == next_range {
|
|
214
|
+
count -= 1;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// If the next one is incompatible, terminate the
|
|
219
|
+
// current run, since the next one needs to be spanned.
|
|
220
|
+
if next_incompatible {
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
prev_range = next_range;
|
|
227
|
+
count += 1;
|
|
228
|
+
|
|
229
|
+
// PDF 1.7 § 14.8.2.3.3 says that `/ReversedChars` show strings shall not contain
|
|
230
|
+
// interior space glyphs. Thus, we check whether we have a glyph that is not the first
|
|
231
|
+
// one and which has a space as its text mapping, and if so, stop after it. That a space
|
|
232
|
+
// is in the same cluster as another codepoint should be exceedingly rare, so we do a
|
|
233
|
+
// literal comparison against `" "` instead of a containment check and prefer to
|
|
234
|
+
// continue otherwise.
|
|
235
|
+
if self.reversed && count > 1 && &self.text[prev_range.clone()] == " " {
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// If we only had one glyph to begin with (and never entered the for loop), then
|
|
241
|
+
// it should be spanned if its codepoint is incompatible.
|
|
242
|
+
if count == 1 {
|
|
243
|
+
use_span = Some(first_incompatible);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let (head, tail) = self.slice.split_at(count);
|
|
247
|
+
self.slice = tail;
|
|
248
|
+
|
|
249
|
+
let fragment = match use_span.unwrap_or(false) {
|
|
250
|
+
true => GlyphSpan::Spanned(head, &self.text[first_range]),
|
|
251
|
+
false => GlyphSpan::Unspanned(head),
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
Some(fragment)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
pub(crate) struct GlyphGroup<'a, T>
|
|
259
|
+
where
|
|
260
|
+
T: Glyph,
|
|
261
|
+
{
|
|
262
|
+
pub(crate) font_identifier: FontIdentifier,
|
|
263
|
+
pub(crate) glyphs: &'a [T],
|
|
264
|
+
// This will be stored in normalized form (i.e. at a font size of 1).
|
|
265
|
+
pub(crate) y_offset: f32,
|
|
266
|
+
// This will be stored in normalized form (i.e. at a font size of 1).
|
|
267
|
+
pub(crate) y_advance: f32,
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
impl<'a, T> GlyphGroup<'a, T>
|
|
271
|
+
where
|
|
272
|
+
T: Glyph,
|
|
273
|
+
{
|
|
274
|
+
pub fn from_props(glyphs: &'a [T], props: GlyphProps) -> Self {
|
|
275
|
+
GlyphGroup {
|
|
276
|
+
font_identifier: props.font_identifier,
|
|
277
|
+
glyphs,
|
|
278
|
+
y_offset: props.y_offset,
|
|
279
|
+
y_advance: props.y_advance,
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// The GlyphGrouper further segments glyph runs (that already have been segmented
|
|
285
|
+
// by `TextSpanner` into subruns that can be encoded as one consecutive run in PDF.
|
|
286
|
+
// This is necessary because:
|
|
287
|
+
// - The user provides a font for the whole glyph run, but in PDF, the font might
|
|
288
|
+
// have to be switched if the glyph maps to a different Type3 font.
|
|
289
|
+
// - The glyph contains a y_offset/y_advance, which cannot be expressed as an adjustment
|
|
290
|
+
// and requires us to start a new run with a transformation matrix that takes this
|
|
291
|
+
// adjustment into account.
|
|
292
|
+
pub(crate) struct GlyphGrouper<'a, T>
|
|
293
|
+
where
|
|
294
|
+
T: Glyph,
|
|
295
|
+
{
|
|
296
|
+
font_container: Rc<RefCell<FontContainer>>,
|
|
297
|
+
context_color: rgb::Color,
|
|
298
|
+
slice: &'a [T],
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
impl<'a, T> GlyphGrouper<'a, T>
|
|
302
|
+
where
|
|
303
|
+
T: Glyph,
|
|
304
|
+
{
|
|
305
|
+
pub fn new(
|
|
306
|
+
font_container: Rc<RefCell<FontContainer>>,
|
|
307
|
+
context_color: rgb::Color,
|
|
308
|
+
slice: &'a [T],
|
|
309
|
+
) -> Self {
|
|
310
|
+
Self {
|
|
311
|
+
font_container,
|
|
312
|
+
context_color,
|
|
313
|
+
slice,
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
impl<'a, T> Iterator for GlyphGrouper<'a, T>
|
|
319
|
+
where
|
|
320
|
+
T: Glyph,
|
|
321
|
+
{
|
|
322
|
+
type Item = GlyphGroup<'a, T>;
|
|
323
|
+
|
|
324
|
+
#[inline]
|
|
325
|
+
fn next(&mut self) -> Option<Self::Item> {
|
|
326
|
+
// Guarantees: All glyphs in `head` have the font identifier that is given in
|
|
327
|
+
// `props`, the same size and the same y offset.
|
|
328
|
+
let (head, tail, props) = {
|
|
329
|
+
let mut count = 1;
|
|
330
|
+
|
|
331
|
+
let mut iter = self.slice.iter();
|
|
332
|
+
let first = get_glyph_props(
|
|
333
|
+
iter.next()?,
|
|
334
|
+
self.context_color,
|
|
335
|
+
&mut self.font_container.borrow_mut(),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
for next in iter {
|
|
339
|
+
let temp_glyph = get_glyph_props(
|
|
340
|
+
next,
|
|
341
|
+
self.context_color,
|
|
342
|
+
&mut self.font_container.borrow_mut(),
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// If either of those is different, we need to start a new subrun.
|
|
346
|
+
if first.font_identifier != temp_glyph.font_identifier
|
|
347
|
+
|| first.y_offset != temp_glyph.y_offset
|
|
348
|
+
|| first.y_advance != 0.0
|
|
349
|
+
|| temp_glyph.y_advance != 0.0
|
|
350
|
+
{
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
count += 1;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let (head, tail) = self.slice.split_at(count);
|
|
358
|
+
(head, tail, first)
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
self.slice = tail;
|
|
362
|
+
|
|
363
|
+
let glyph_group = GlyphGroup::from_props(head, props);
|
|
364
|
+
|
|
365
|
+
Some(glyph_group)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
pub(crate) struct GlyphProps {
|
|
370
|
+
font_identifier: FontIdentifier,
|
|
371
|
+
y_offset: f32,
|
|
372
|
+
y_advance: f32,
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
pub(crate) fn get_glyph_props<U>(
|
|
376
|
+
g: &U,
|
|
377
|
+
context_color: rgb::Color,
|
|
378
|
+
font_container: &mut FontContainer,
|
|
379
|
+
) -> GlyphProps
|
|
380
|
+
where
|
|
381
|
+
U: Glyph,
|
|
382
|
+
{
|
|
383
|
+
// Safe because we've already added all glyphs in the text spanner.
|
|
384
|
+
let font_identifier = font_container
|
|
385
|
+
.font_identifier(ColoredGlyph::new(g.glyph_id(), context_color))
|
|
386
|
+
.unwrap();
|
|
387
|
+
|
|
388
|
+
GlyphProps {
|
|
389
|
+
font_identifier,
|
|
390
|
+
y_offset: g.y_offset(1.0),
|
|
391
|
+
y_advance: g.y_advance(1.0),
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
pub fn use_text_spanner(
|
|
396
|
+
glyphs: &[impl Glyph],
|
|
397
|
+
text: &str,
|
|
398
|
+
context_color: rgb::Color,
|
|
399
|
+
font_container: &mut FontContainer,
|
|
400
|
+
) -> bool {
|
|
401
|
+
if glyphs.is_empty() {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
let mut do_text_span = false;
|
|
406
|
+
// We _might_ need to group glyphs either if they are mapped to different PDF fonts (which
|
|
407
|
+
// can only happen for Type3 fonts, not CID fonts), or if a glyph has a different y/x offset.
|
|
408
|
+
// Note that it's of course possible that we always use the same Type3 font, but we need to
|
|
409
|
+
// be conservative here.
|
|
410
|
+
|
|
411
|
+
let mut check_single = |glyph, do_text_span: &mut bool| {
|
|
412
|
+
// As soon as we know that the glyph run requires a text span, we do not insert any codepoints
|
|
413
|
+
// anymore, because otherwise we might unnecessarily pollute the cmap with entries that
|
|
414
|
+
// wouldn't be necessary. The text spanner will then iterate over the all glyphs again
|
|
415
|
+
// and thus take care of inserting cmap entries for any remaining glyphs we ignore now.
|
|
416
|
+
// The only reason we keep going and don't early abort is in order to fully
|
|
417
|
+
// check the `do_glyph_grouping` property.
|
|
418
|
+
if !*do_text_span {
|
|
419
|
+
check_text_span_prop(glyph, text, context_color, font_container, do_text_span);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
for glyphs in glyphs.windows(2) {
|
|
424
|
+
let prev = &glyphs[0];
|
|
425
|
+
let next = &glyphs[1];
|
|
426
|
+
|
|
427
|
+
// If two glyphs are part of the same cluster, we definitely need a text span.
|
|
428
|
+
do_text_span |= prev.text_range() == next.text_range();
|
|
429
|
+
|
|
430
|
+
check_single(prev, &mut do_text_span);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Since windows checks groups of two, we need to manually check the last glyph
|
|
434
|
+
check_single(glyphs.last().unwrap(), &mut do_text_span);
|
|
435
|
+
|
|
436
|
+
do_text_span
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
fn check_text_span_prop(
|
|
440
|
+
glyph: &impl Glyph,
|
|
441
|
+
text: &str,
|
|
442
|
+
context_color: rgb::Color,
|
|
443
|
+
font_container: &mut FontContainer,
|
|
444
|
+
do_text_span: &mut bool,
|
|
445
|
+
) {
|
|
446
|
+
let (identifier, pdf_glyph) =
|
|
447
|
+
font_container.add_glyph(ColoredGlyph::new(glyph.glyph_id(), context_color));
|
|
448
|
+
let pdf_font = font_container
|
|
449
|
+
.get_from_identifier_mut(identifier.clone())
|
|
450
|
+
.unwrap();
|
|
451
|
+
|
|
452
|
+
let range = glyph.text_range().clone();
|
|
453
|
+
let text = &text[range.clone()];
|
|
454
|
+
let codepoints = pdf_font.get_codepoints(pdf_glyph);
|
|
455
|
+
|
|
456
|
+
if codepoints.is_some_and(|t| t != text) {
|
|
457
|
+
// If there already exists an entry, then the codepoints are invalid, and we will have to
|
|
458
|
+
// use a text span.
|
|
459
|
+
*do_text_span = true;
|
|
460
|
+
} else if codepoints.is_none() {
|
|
461
|
+
pdf_font.set_codepoints(pdf_glyph, text.to_string(), glyph.location());
|
|
462
|
+
}
|
|
463
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
//! Text and font support.
|
|
2
|
+
//!
|
|
3
|
+
//! krilla has extensive support for OpenType fonts. It supports CFF-based as well
|
|
4
|
+
//! as TTF-based OpenType fonts. In addition to that, krilla also supports
|
|
5
|
+
//! all major tables used in color fonts, including the `SVG`, `COLR`, `sbix` and
|
|
6
|
+
//! `CBDT`/`EBDT` (only PNG) tables, something that, to the best of my knowledge, no other
|
|
7
|
+
//! Rust crates provides.
|
|
8
|
+
//!
|
|
9
|
+
//! Even better is the fact that you do not need to take care of choosing the right
|
|
10
|
+
//! table for drawing glyphs: All you need to do is to provide the [`Font`] object with
|
|
11
|
+
//! an appropriate index.
|
|
12
|
+
|
|
13
|
+
use std::collections::HashMap;
|
|
14
|
+
use std::fmt::Debug;
|
|
15
|
+
use std::hash::Hash;
|
|
16
|
+
|
|
17
|
+
use rustc_hash::FxHashMap;
|
|
18
|
+
|
|
19
|
+
use crate::text::cid::CIDFont;
|
|
20
|
+
use crate::text::type3::{ColoredGlyph, Type3Font, Type3FontMapper, Type3ID};
|
|
21
|
+
pub(crate) mod cid;
|
|
22
|
+
pub(crate) mod font;
|
|
23
|
+
pub(crate) mod glyph;
|
|
24
|
+
pub(crate) mod group;
|
|
25
|
+
#[cfg(feature = "simple-text")]
|
|
26
|
+
pub(crate) mod shape;
|
|
27
|
+
pub(crate) mod type3;
|
|
28
|
+
|
|
29
|
+
pub use font::*;
|
|
30
|
+
pub use glyph::*;
|
|
31
|
+
#[cfg(feature = "simple-text")]
|
|
32
|
+
pub use shape::TextDirection;
|
|
33
|
+
|
|
34
|
+
pub(crate) const PDF_UNITS_PER_EM: f32 = 1000.0;
|
|
35
|
+
|
|
36
|
+
/// A unique CID identifier.
|
|
37
|
+
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
|
38
|
+
pub(crate) struct CIDIdentifier(pub Font);
|
|
39
|
+
|
|
40
|
+
/// A unique Type3 font identifier. Type3 fonts can only hold 256 glyphs, which
|
|
41
|
+
/// means that we might have to create more than one Type3 font. This is why we
|
|
42
|
+
/// additionally store an index that indicates which specific Type3Font we are
|
|
43
|
+
/// referring to.
|
|
44
|
+
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
|
45
|
+
pub(crate) struct Type3Identifier(pub Font, pub Type3ID);
|
|
46
|
+
|
|
47
|
+
/// A font identifier for a PDF font.
|
|
48
|
+
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
|
49
|
+
pub(crate) enum FontIdentifier {
|
|
50
|
+
Cid(CIDIdentifier),
|
|
51
|
+
Type3(Type3Identifier),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// A container that holds all PDF fonts belonging to an OTF font.
|
|
55
|
+
pub(crate) struct FontContainer {
|
|
56
|
+
font: Font,
|
|
57
|
+
type3_mapper: Type3FontMapper,
|
|
58
|
+
cid_font: CIDFont,
|
|
59
|
+
cid_cache: FxHashMap<u32, (FontIdentifier, PDFGlyph)>,
|
|
60
|
+
type3_cache: HashMap<ColoredGlyph, (FontIdentifier, PDFGlyph)>,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
impl FontContainer {
|
|
64
|
+
pub(crate) fn new(font: Font) -> Self {
|
|
65
|
+
Self {
|
|
66
|
+
font: font.clone(),
|
|
67
|
+
type3_mapper: Type3FontMapper::new(font.clone()),
|
|
68
|
+
cid_font: CIDFont::new(font.clone()),
|
|
69
|
+
cid_cache: Default::default(),
|
|
70
|
+
type3_cache: Default::default(),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
pub(crate) fn type3_mapper(&self) -> &Type3FontMapper {
|
|
75
|
+
&self.type3_mapper
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pub(crate) fn cid_font(&self) -> &CIDFont {
|
|
79
|
+
&self.cid_font
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#[inline]
|
|
83
|
+
pub(crate) fn font_identifier(&self, glyph: ColoredGlyph) -> Option<FontIdentifier> {
|
|
84
|
+
let (id, _) = self
|
|
85
|
+
.cid_cache
|
|
86
|
+
.get(&glyph.glyph_id.to_u32())
|
|
87
|
+
.or_else(|| self.type3_cache.get(&glyph.to_owned()))?;
|
|
88
|
+
Some(id.clone())
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#[inline]
|
|
92
|
+
pub(crate) fn get_from_identifier_mut(
|
|
93
|
+
&mut self,
|
|
94
|
+
font_identifier: FontIdentifier,
|
|
95
|
+
) -> Option<&mut dyn PdfFont> {
|
|
96
|
+
if self.cid_font.identifier() == font_identifier {
|
|
97
|
+
Some(&mut self.cid_font)
|
|
98
|
+
} else {
|
|
99
|
+
// If the identifier doesn't match either of CID or Type3, this will
|
|
100
|
+
// return `None`.
|
|
101
|
+
Some(self.type3_mapper.font_mut_from_id(font_identifier)?)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[inline]
|
|
106
|
+
pub(crate) fn get_from_identifier(
|
|
107
|
+
&self,
|
|
108
|
+
font_identifier: FontIdentifier,
|
|
109
|
+
) -> Option<&dyn PdfFont> {
|
|
110
|
+
if self.cid_font.identifier() == font_identifier {
|
|
111
|
+
Some(&self.cid_font)
|
|
112
|
+
} else {
|
|
113
|
+
// If the identifier doesn't match either of CID or Type3, this will
|
|
114
|
+
// return `None`.
|
|
115
|
+
Some(self.type3_mapper.font_from_id(font_identifier)?)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#[inline]
|
|
120
|
+
pub(crate) fn add_glyph(&mut self, glyph: ColoredGlyph) -> (FontIdentifier, PDFGlyph) {
|
|
121
|
+
if let Some(e) = self
|
|
122
|
+
.cid_cache
|
|
123
|
+
.get(&glyph.glyph_id.to_u32())
|
|
124
|
+
.or_else(|| self.type3_cache.get(&glyph.to_owned()))
|
|
125
|
+
{
|
|
126
|
+
// We already know whether this glyph uses a CID or Type3 glyph.
|
|
127
|
+
e.clone()
|
|
128
|
+
} else if should_outline(&self.font, glyph.glyph_id) {
|
|
129
|
+
let cid = self.cid_font.add_glyph(glyph.glyph_id);
|
|
130
|
+
let res = (self.cid_font.identifier(), PDFGlyph::Cid(cid));
|
|
131
|
+
self.cid_cache.insert(glyph.glyph_id.to_u32(), res.clone());
|
|
132
|
+
res
|
|
133
|
+
} else {
|
|
134
|
+
let (identifier, gid) = self.type3_mapper.add_glyph(glyph.to_owned());
|
|
135
|
+
let res = (identifier, PDFGlyph::Type3(gid));
|
|
136
|
+
self.type3_cache.insert(glyph.to_owned(), res.clone());
|
|
137
|
+
res
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
pub(crate) trait PdfFont {
|
|
143
|
+
fn units_per_em(&self) -> f32;
|
|
144
|
+
fn font(&self) -> Font;
|
|
145
|
+
fn get_codepoints(&self, pdf_glyph: PDFGlyph) -> Option<&str>;
|
|
146
|
+
fn set_codepoints(
|
|
147
|
+
&mut self,
|
|
148
|
+
pdf_glyph: PDFGlyph,
|
|
149
|
+
text: String,
|
|
150
|
+
location: Option<crate::surface::Location>,
|
|
151
|
+
);
|
|
152
|
+
fn get_gid(&self, glyph: ColoredGlyph) -> Option<PDFGlyph>;
|
|
153
|
+
fn force_fill(&self) -> bool;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
impl PdfFont for Type3Font {
|
|
157
|
+
fn units_per_em(&self) -> f32 {
|
|
158
|
+
self.unit_per_em()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fn font(&self) -> Font {
|
|
162
|
+
Type3Font::font(self)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
#[track_caller]
|
|
166
|
+
fn get_codepoints(&self, pdf_glyph: PDFGlyph) -> Option<&str> {
|
|
167
|
+
match pdf_glyph {
|
|
168
|
+
PDFGlyph::Type3(t3) => self.get_codepoints(t3),
|
|
169
|
+
PDFGlyph::Cid(_) => panic!("attempted to pass cid to type 3 font"),
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#[track_caller]
|
|
174
|
+
fn set_codepoints(
|
|
175
|
+
&mut self,
|
|
176
|
+
pdf_glyph: PDFGlyph,
|
|
177
|
+
text: String,
|
|
178
|
+
location: Option<crate::surface::Location>,
|
|
179
|
+
) {
|
|
180
|
+
match pdf_glyph {
|
|
181
|
+
PDFGlyph::Type3(t3) => self.set_codepoints(t3, text, location),
|
|
182
|
+
PDFGlyph::Cid(_) => panic!("attempted to pass cid to type 3 font"),
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
fn get_gid(&self, glyph: ColoredGlyph) -> Option<PDFGlyph> {
|
|
187
|
+
self.get_gid(&glyph.to_owned()).map(PDFGlyph::Type3)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fn force_fill(&self) -> bool {
|
|
191
|
+
true
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
impl PdfFont for CIDFont {
|
|
196
|
+
fn units_per_em(&self) -> f32 {
|
|
197
|
+
self.units_per_em()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
fn font(&self) -> Font {
|
|
201
|
+
CIDFont::font(self)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#[track_caller]
|
|
205
|
+
fn get_codepoints(&self, pdf_glyph: PDFGlyph) -> Option<&str> {
|
|
206
|
+
match pdf_glyph {
|
|
207
|
+
PDFGlyph::Type3(_) => panic!("attempted to pass type 3 glyph to cid font"),
|
|
208
|
+
PDFGlyph::Cid(cid) => self.get_codepoints(cid),
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#[track_caller]
|
|
213
|
+
fn set_codepoints(
|
|
214
|
+
&mut self,
|
|
215
|
+
pdf_glyph: PDFGlyph,
|
|
216
|
+
text: String,
|
|
217
|
+
location: Option<crate::surface::Location>,
|
|
218
|
+
) {
|
|
219
|
+
match pdf_glyph {
|
|
220
|
+
PDFGlyph::Type3(_) => panic!("attempted to pass type 3 glyph to cid font"),
|
|
221
|
+
PDFGlyph::Cid(cid) => self.set_codepoints(cid, text, location),
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
fn get_gid(&self, glyph: ColoredGlyph) -> Option<PDFGlyph> {
|
|
226
|
+
self.get_cid(glyph.glyph_id).map(PDFGlyph::Cid)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
fn force_fill(&self) -> bool {
|
|
230
|
+
false
|
|
231
|
+
}
|
|
232
|
+
}
|