blitz-pdf 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +300 -10
  2. package/THIRD_PARTY_NOTICES.md +4 -2
  3. package/dist/browser.d.ts +1 -1
  4. package/dist/browser.d.ts.map +1 -1
  5. package/dist/document-options.d.ts +36 -0
  6. package/dist/document-options.d.ts.map +1 -0
  7. package/dist/document-options.js +236 -0
  8. package/dist/document-options.js.map +1 -0
  9. package/dist/node.d.ts +1 -1
  10. package/dist/node.d.ts.map +1 -1
  11. package/dist/options.d.ts +35 -1
  12. package/dist/options.d.ts.map +1 -1
  13. package/dist/options.js +21 -14
  14. package/dist/options.js.map +1 -1
  15. package/dist/types.d.ts +71 -1
  16. package/dist/types.d.ts.map +1 -1
  17. package/dist/validation.d.ts +2 -0
  18. package/dist/validation.d.ts.map +1 -0
  19. package/dist/validation.js +7 -0
  20. package/dist/validation.js.map +1 -0
  21. package/examples/zugferd.mjs +41 -0
  22. package/licenses/RUST_DEPENDENCIES.txt +27 -0
  23. package/package.json +3 -2
  24. package/src/browser.ts +1 -1
  25. package/src/document-options.ts +218 -0
  26. package/src/node.ts +1 -1
  27. package/src/options.ts +18 -13
  28. package/src/types.ts +76 -1
  29. package/src/validation.ts +6 -0
  30. package/vendor/README.md +27 -2
  31. package/vendor/krilla/.cargo-ok +1 -0
  32. package/vendor/krilla/.cargo_vcs_info.json +6 -0
  33. package/vendor/krilla/Cargo.lock +1357 -0
  34. package/vendor/krilla/Cargo.toml +165 -0
  35. package/vendor/krilla/LICENSE_APACHE +202 -0
  36. package/vendor/krilla/LICENSE_MIT +21 -0
  37. package/vendor/krilla/README.md +156 -0
  38. package/vendor/krilla/examples/basic.rs +117 -0
  39. package/vendor/krilla/examples/empty_document.rs +25 -0
  40. package/vendor/krilla/examples/parley.rs +174 -0
  41. package/vendor/krilla/examples/simple_text.rs +118 -0
  42. package/vendor/krilla/examples/stream_builder.rs +93 -0
  43. package/vendor/krilla/icc/LICENSE +116 -0
  44. package/vendor/krilla/icc/README.md +1 -0
  45. package/vendor/krilla/icc/sGrey-v2-magic.icc +0 -0
  46. package/vendor/krilla/icc/sGrey-v4.icc +0 -0
  47. package/vendor/krilla/icc/sRGB-v2-magic.icc +0 -0
  48. package/vendor/krilla/icc/sRGB-v4.icc +0 -0
  49. package/vendor/krilla/src/chunk_container.rs +503 -0
  50. package/vendor/krilla/src/configure/PDF_A1.md +227 -0
  51. package/vendor/krilla/src/configure/PDF_A2.md +269 -0
  52. package/vendor/krilla/src/configure/PDF_A3.md +31 -0
  53. package/vendor/krilla/src/configure/PDF_A4.md +211 -0
  54. package/vendor/krilla/src/configure/PDF_UA1.md +202 -0
  55. package/vendor/krilla/src/configure/README.md +23 -0
  56. package/vendor/krilla/src/configure/mod.rs +197 -0
  57. package/vendor/krilla/src/configure/validate.rs +1244 -0
  58. package/vendor/krilla/src/configure/version.rs +134 -0
  59. package/vendor/krilla/src/content.rs +1352 -0
  60. package/vendor/krilla/src/data.rs +49 -0
  61. package/vendor/krilla/src/document.rs +156 -0
  62. package/vendor/krilla/src/error.rs +170 -0
  63. package/vendor/krilla/src/geom.rs +341 -0
  64. package/vendor/krilla/src/graphics/blend.rs +46 -0
  65. package/vendor/krilla/src/graphics/color.rs +552 -0
  66. package/vendor/krilla/src/graphics/graphic.rs +30 -0
  67. package/vendor/krilla/src/graphics/graphics_state.rs +251 -0
  68. package/vendor/krilla/src/graphics/icc.rs +218 -0
  69. package/vendor/krilla/src/graphics/image.rs +835 -0
  70. package/vendor/krilla/src/graphics/mask.rs +128 -0
  71. package/vendor/krilla/src/graphics/mod.rs +14 -0
  72. package/vendor/krilla/src/graphics/paint.rs +434 -0
  73. package/vendor/krilla/src/graphics/separation.rs +86 -0
  74. package/vendor/krilla/src/graphics/shading_function.rs +865 -0
  75. package/vendor/krilla/src/graphics/shading_pattern.rs +60 -0
  76. package/vendor/krilla/src/graphics/tiling_pattern.rs +119 -0
  77. package/vendor/krilla/src/graphics/xobject.rs +150 -0
  78. package/vendor/krilla/src/interactive/action.rs +68 -0
  79. package/vendor/krilla/src/interactive/annotation.rs +283 -0
  80. package/vendor/krilla/src/interactive/destination.rs +289 -0
  81. package/vendor/krilla/src/interactive/mod.rs +3 -0
  82. package/vendor/krilla/src/interchange/embed.rs +240 -0
  83. package/vendor/krilla/src/interchange/metadata.rs +564 -0
  84. package/vendor/krilla/src/interchange/mod.rs +4 -0
  85. package/vendor/krilla/src/interchange/outline.rs +263 -0
  86. package/vendor/krilla/src/interchange/tagging/fmt.rs +733 -0
  87. package/vendor/krilla/src/interchange/tagging/generate.toml +474 -0
  88. package/vendor/krilla/src/interchange/tagging/generated.rs +4754 -0
  89. package/vendor/krilla/src/interchange/tagging/mod.rs +1324 -0
  90. package/vendor/krilla/src/interchange/tagging/tag.rs +686 -0
  91. package/vendor/krilla/src/lib.rs +179 -0
  92. package/vendor/krilla/src/num.rs +26 -0
  93. package/vendor/krilla/src/page.rs +612 -0
  94. package/vendor/krilla/src/pdf.rs +313 -0
  95. package/vendor/krilla/src/resource.rs +436 -0
  96. package/vendor/krilla/src/serialize.rs +1101 -0
  97. package/vendor/krilla/src/stream.rs +368 -0
  98. package/vendor/krilla/src/surface.rs +664 -0
  99. package/vendor/krilla/src/text/cid.rs +488 -0
  100. package/vendor/krilla/src/text/font.rs +351 -0
  101. package/vendor/krilla/src/text/glyph/bitmap.rs +81 -0
  102. package/vendor/krilla/src/text/glyph/colr.rs +581 -0
  103. package/vendor/krilla/src/text/glyph/mod.rs +205 -0
  104. package/vendor/krilla/src/text/glyph/outline.rs +72 -0
  105. package/vendor/krilla/src/text/glyph/svg.rs +34 -0
  106. package/vendor/krilla/src/text/group.rs +463 -0
  107. package/vendor/krilla/src/text/mod.rs +232 -0
  108. package/vendor/krilla/src/text/shape.rs +102 -0
  109. package/vendor/krilla/src/text/type3.rs +514 -0
  110. package/vendor/krilla/src/util.rs +286 -0
  111. package/vendor/stylo/properties/longhands.toml +0 -1
  112. package/vendor/stylo/stylesheets/rule_parser.rs +1 -1
  113. package/wasm/blitz_pdf.js +10 -5
  114. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,102 @@
1
+ use crate::text::Font;
2
+ use crate::text::{GlyphId, KrillaGlyph};
3
+ use rustybuzz::ttf_parser::Tag;
4
+ use rustybuzz::{Direction, UnicodeBuffer};
5
+
6
+ /// Naively shape some text with a single font.
7
+ pub(crate) fn naive_shape(text: &str, font: Font, direction: TextDirection) -> Vec<KrillaGlyph> {
8
+ let data = font.font_data();
9
+ let mut rb_font = rustybuzz::Face::from_slice(data.as_ref(), font.index()).unwrap();
10
+
11
+ for variation in font.variation_coordinates() {
12
+ rb_font.set_variation(Tag::from_bytes(variation.0.get()), variation.1.get());
13
+ }
14
+
15
+ let mut buffer = UnicodeBuffer::new();
16
+ buffer.push_str(text);
17
+ buffer.guess_segment_properties();
18
+
19
+ match direction {
20
+ TextDirection::LeftToRight => buffer.set_direction(Direction::LeftToRight),
21
+ TextDirection::RightToLeft => buffer.set_direction(Direction::RightToLeft),
22
+ TextDirection::TopToBottom => buffer.set_direction(Direction::TopToBottom),
23
+ TextDirection::BottomToTop => buffer.set_direction(Direction::BottomToTop),
24
+ TextDirection::Auto => {}
25
+ }
26
+
27
+ let dir = buffer.direction();
28
+
29
+ let output = rustybuzz::shape(&rb_font, &[], buffer);
30
+
31
+ let positions = output.glyph_positions();
32
+ let infos = output.glyph_infos();
33
+
34
+ let mut glyphs = vec![];
35
+
36
+ for i in 0..output.len() {
37
+ let pos = positions[i];
38
+ let start_info = infos[i];
39
+
40
+ let start = start_info.cluster as usize;
41
+
42
+ let end = if dir == Direction::LeftToRight || dir == Direction::TopToBottom {
43
+ let mut e = i.checked_add(1);
44
+ loop {
45
+ if let Some(index) = e {
46
+ if let Some(end_info) = infos.get(index) {
47
+ if end_info.cluster == start_info.cluster {
48
+ e = index.checked_add(1);
49
+ continue;
50
+ }
51
+ }
52
+ }
53
+
54
+ break;
55
+ }
56
+
57
+ e
58
+ } else {
59
+ let mut e = i.checked_sub(1);
60
+ while let Some(index) = e {
61
+ if let Some(end_info) = infos.get(index) {
62
+ if end_info.cluster == start_info.cluster {
63
+ e = index.checked_sub(1);
64
+ } else {
65
+ break;
66
+ }
67
+ }
68
+ }
69
+
70
+ e
71
+ }
72
+ .and_then(|last| infos.get(last))
73
+ .map_or(text.len(), |info| info.cluster as usize);
74
+
75
+ glyphs.push(KrillaGlyph::new(
76
+ GlyphId::new(start_info.glyph_id),
77
+ pos.x_advance as f32 / font.units_per_em(),
78
+ pos.x_offset as f32 / font.units_per_em(),
79
+ pos.y_offset as f32 / font.units_per_em(),
80
+ pos.y_advance as f32 / font.units_per_em(),
81
+ start..end,
82
+ None,
83
+ ));
84
+ }
85
+
86
+ glyphs
87
+ }
88
+
89
+ /// The direction of a text.
90
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
91
+ pub enum TextDirection {
92
+ /// Determine the direction automatically.
93
+ Auto,
94
+ /// Left to right.
95
+ LeftToRight,
96
+ /// Right to left.
97
+ RightToLeft,
98
+ /// Top to bottom.
99
+ TopToBottom,
100
+ /// Bottom to top.
101
+ BottomToTop,
102
+ }
@@ -0,0 +1,514 @@
1
+ use std::collections::HashSet;
2
+ use std::hash::Hash;
3
+ use std::ops::DerefMut;
4
+
5
+ use pdf_writer::types::{FontFlags, UnicodeCmap};
6
+ use pdf_writer::writers::WMode;
7
+ use pdf_writer::{Finish, Name, Ref, Str};
8
+ use rustc_hash::FxHashMap;
9
+
10
+ use super::{FontIdentifier, Type3Identifier};
11
+ use crate::chunk_container::ChunkContainer;
12
+ use crate::color::rgb;
13
+ use crate::configure::PdfVersion;
14
+ use crate::geom::Path;
15
+ use crate::geom::{Rect, Transform};
16
+ use crate::graphics::paint::Fill;
17
+ use crate::graphics::xobject::XObject;
18
+ use crate::resource::ResourceDictionaryBuilder;
19
+ use crate::serialize::SerializeContext;
20
+ use crate::stream::{FilterStreamBuilder, StreamBuilder};
21
+ use crate::surface::Location;
22
+ use crate::text::cid::write_cmap_entry;
23
+ use crate::text::cid::{CMAP_NAME, IDENTITY_H, SYSTEM_INFO};
24
+ use crate::text::outline::glyph_path;
25
+ use crate::text::GlyphId;
26
+ use crate::text::{self, Font};
27
+ use crate::util::NameExt;
28
+
29
+ pub(crate) type Gid = u8;
30
+
31
+ #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
32
+ pub(crate) struct ColoredGlyph {
33
+ pub(crate) glyph_id: GlyphId,
34
+ // Some formats like COLR and SVG allow drawing the same glyph
35
+ // with a different context color, so we need to store that as well.
36
+ pub(crate) context_color: rgb::Color,
37
+ }
38
+
39
+ impl ColoredGlyph {
40
+ pub(crate) fn new(glyph_id: GlyphId, context_color: rgb::Color) -> ColoredGlyph {
41
+ Self {
42
+ glyph_id,
43
+ context_color,
44
+ }
45
+ }
46
+ }
47
+
48
+ #[derive(Debug)]
49
+ pub(crate) struct Type3Font {
50
+ font: Font,
51
+ glyphs: Vec<ColoredGlyph>,
52
+ widths: Vec<f32>,
53
+ cmap_entries: FxHashMap<Gid, (String, Option<Location>)>,
54
+ glyph_set: HashSet<ColoredGlyph>,
55
+ index: usize,
56
+ }
57
+
58
+ impl Type3Font {
59
+ pub(crate) fn new(font: Font, index: usize) -> Self {
60
+ Self {
61
+ font,
62
+ glyphs: Vec::new(),
63
+ cmap_entries: FxHashMap::default(),
64
+ widths: Vec::new(),
65
+ glyph_set: HashSet::new(),
66
+ index,
67
+ }
68
+ }
69
+
70
+ // Unlike CID fonts, the units per em of type 3 fonts does not have to be 1000.
71
+ pub(crate) fn unit_per_em(&self) -> f32 {
72
+ self.font.units_per_em()
73
+ }
74
+
75
+ pub(crate) fn is_full(&self) -> bool {
76
+ self.count() == 256
77
+ }
78
+
79
+ pub(crate) fn count(&self) -> u16 {
80
+ u16::try_from(self.glyphs.len()).unwrap()
81
+ }
82
+
83
+ #[inline]
84
+ pub(crate) fn covers(&self, glyph: &ColoredGlyph) -> bool {
85
+ self.glyph_set.contains(glyph)
86
+ }
87
+
88
+ #[inline]
89
+ pub(crate) fn get_gid(&self, glyph: &ColoredGlyph) -> Option<u8> {
90
+ self.glyphs
91
+ .iter()
92
+ .position(|g| g == glyph)
93
+ .and_then(|n| u8::try_from(n).ok())
94
+ }
95
+
96
+ #[inline]
97
+ pub(crate) fn add_glyph(&mut self, glyph: ColoredGlyph) -> u8 {
98
+ if let Some(pos) = self.get_gid(&glyph) {
99
+ pos
100
+ } else {
101
+ assert!(self.glyphs.len() < 256);
102
+
103
+ self.glyph_set.insert(glyph);
104
+ self.glyphs.push(glyph);
105
+ self.widths
106
+ .push(self.font.advance_width(glyph.glyph_id).unwrap_or(0.0));
107
+ u8::try_from(self.glyphs.len() - 1).unwrap()
108
+ }
109
+ }
110
+
111
+ #[inline]
112
+ pub(crate) fn get_codepoints(&self, gid: Gid) -> Option<&str> {
113
+ self.cmap_entries.get(&gid).map(|s| s.0.as_str())
114
+ }
115
+
116
+ #[inline]
117
+ pub(crate) fn set_codepoints(&mut self, gid: Gid, text: String, location: Option<Location>) {
118
+ self.cmap_entries.insert(gid, (text, location));
119
+ }
120
+
121
+ #[inline]
122
+ pub(crate) fn font(&self) -> Font {
123
+ self.font.clone()
124
+ }
125
+
126
+ #[inline]
127
+ pub(crate) fn identifier(&self) -> FontIdentifier {
128
+ FontIdentifier::Type3(Type3Identifier(self.font.clone(), self.index))
129
+ }
130
+
131
+ pub(crate) fn serialize(
132
+ &self,
133
+ sc: &mut SerializeContext,
134
+ chunk_container: &mut ChunkContainer,
135
+ root_ref: Ref,
136
+ ) {
137
+ let mut stream_chunk = sc.new_chunk();
138
+
139
+ let mut rd_builder = ResourceDictionaryBuilder::new();
140
+ let mut font_bbox = Rect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap();
141
+
142
+ let glyph_streams = self
143
+ .glyphs
144
+ .iter()
145
+ .enumerate()
146
+ .map(|(index, glyph)| {
147
+ let mut stream_surface = StreamBuilder::new(sc, chunk_container);
148
+ let mut surface = stream_surface.surface();
149
+
150
+ // In case this returns `None`, the surface is guaranteed to be empty.
151
+ let drawn_color_glyph = text::draw_color_glyph(
152
+ self.font.clone(),
153
+ glyph.context_color,
154
+ glyph.glyph_id,
155
+ Transform::default(),
156
+ &mut surface,
157
+ );
158
+
159
+ if drawn_color_glyph.is_none() {
160
+ // If this code path is reached, either we are dealing with a glyph that has no outline
161
+ // (like .notdef), or it means we tried to create a Type3
162
+ // font from a font that does not have any (valid) color/bitmap/svg table.
163
+ // Therefore, as a last resort we draw the outline glyph as a black glyph.
164
+ // PDF does have the concept of shape glyphs which take the color or where
165
+ // they are drawn from.
166
+ // The problem with those is that they seemingly have very bad viewer support. For
167
+ // example, filled glyphs with gradients become broken in some viewers,
168
+ // and stroking does not work at all consistently. Because of this,
169
+ // we don't bother trying to implement it for now.
170
+
171
+ // If this is the case (i.e. no color glyph was drawn, either because no table
172
+ // exists or an error occurred, the surface is guaranteed to be empty.
173
+ // So we can just safely draw the outline glyph instead without having to
174
+ // worry about the surface being "dirty".
175
+ if let Some(path) = glyph_path(self.font.clone(), glyph.glyph_id) {
176
+ surface.set_fill(Some(Fill::default()));
177
+ surface.set_stroke(None);
178
+ surface.draw_path(&Path(path));
179
+ }
180
+ };
181
+
182
+ surface.finish();
183
+ let stream = stream_surface.finish();
184
+ let mut content = sc.new_content();
185
+
186
+ // I considered writing into the stream directly instead of creating an XObject
187
+ // and showing that, but it seems like many viewers don't like that, and emojis
188
+ // look messed up. Using XObjects seems like the best choice here.
189
+ content.start_color_glyph(self.widths[index]);
190
+ let x_object = XObject::new(stream, false, false, None);
191
+ if !x_object.is_empty() {
192
+ font_bbox.expand(&x_object.bbox());
193
+ let x_name = rd_builder
194
+ .register_resource(sc.register_resourceable(chunk_container, x_object));
195
+ content.x_object(x_name.to_pdf_name());
196
+ }
197
+
198
+ let stream = content.finish();
199
+
200
+ let font_stream = FilterStreamBuilder::new_from_content_stream(
201
+ stream.as_slice(),
202
+ &sc.serialize_settings(),
203
+ )
204
+ .finish(&sc.serialize_settings());
205
+
206
+ let stream_ref = sc.new_ref();
207
+ let mut stream = stream_chunk.stream(stream_ref, font_stream.encoded_data());
208
+ font_stream.write_filters(stream.deref_mut());
209
+
210
+ stream_ref
211
+ })
212
+ .collect::<Vec<Ref>>();
213
+
214
+ let resource_dictionary = rd_builder.finish();
215
+ let chunk = &mut chunk_container.non_stream.fonts;
216
+
217
+ let descriptor_ref = if sc.serialize_settings().pdf_version() >= PdfVersion::Pdf15 {
218
+ Some(sc.new_ref())
219
+ } else {
220
+ None
221
+ };
222
+ let cmap_ref = sc.new_ref();
223
+
224
+ let mut flags = FontFlags::empty();
225
+ flags.set(
226
+ FontFlags::SERIF,
227
+ self.font
228
+ .postscript_name()
229
+ .is_some_and(|n| n.contains("Serif")),
230
+ );
231
+ flags.set(FontFlags::FIXED_PITCH, self.font.is_monospaced());
232
+ flags.set(FontFlags::ITALIC, self.font.italic_angle() != 0.0);
233
+ flags.insert(FontFlags::SYMBOLIC);
234
+ flags.insert(FontFlags::SMALL_CAP);
235
+
236
+ let italic_angle = self.font.italic_angle();
237
+ let ascender = font_bbox.bottom();
238
+ let descender = font_bbox.top();
239
+
240
+ let mut gids = self
241
+ .glyph_set
242
+ .iter()
243
+ .map(|g| g.glyph_id)
244
+ .collect::<Vec<_>>();
245
+ gids.sort();
246
+ let base_font = base_font_name(&self.font);
247
+
248
+ if let Some(descriptor_ref) = descriptor_ref {
249
+ // Write the font descriptor (contains metrics about the font).
250
+ let mut font_descriptor = chunk.font_descriptor(descriptor_ref);
251
+ font_descriptor
252
+ .name(Name(base_font.as_bytes()))
253
+ .flags(flags)
254
+ .bbox(font_bbox.to_pdf_rect())
255
+ .italic_angle(italic_angle)
256
+ .ascent(ascender)
257
+ .descent(descender)
258
+ // Adobe recommends these for tagged PDF for 1.5+ (descriptors for Type3 fonts
259
+ // are only written for 1.5+ in the first place, so no additional checks needed)
260
+ // so we write them as well.
261
+ // Unfortunately we have no way of determining the actual family name, so we just
262
+ // take the next best thing
263
+ .family(Str(base_font.as_bytes()))
264
+ .stretch(
265
+ match skrifa::attribute::Stretch::new(self.font().stretch()) {
266
+ skrifa::attribute::Stretch::ULTRA_CONDENSED => {
267
+ pdf_writer::types::FontStretch::UltraCondensed
268
+ }
269
+ skrifa::attribute::Stretch::EXTRA_CONDENSED => {
270
+ pdf_writer::types::FontStretch::ExtraCondensed
271
+ }
272
+ skrifa::attribute::Stretch::CONDENSED => {
273
+ pdf_writer::types::FontStretch::Condensed
274
+ }
275
+ skrifa::attribute::Stretch::SEMI_CONDENSED => {
276
+ pdf_writer::types::FontStretch::SemiCondensed
277
+ }
278
+ skrifa::attribute::Stretch::NORMAL => {
279
+ pdf_writer::types::FontStretch::Normal
280
+ }
281
+ skrifa::attribute::Stretch::SEMI_EXPANDED => {
282
+ pdf_writer::types::FontStretch::SemiExpanded
283
+ }
284
+ skrifa::attribute::Stretch::EXPANDED => {
285
+ pdf_writer::types::FontStretch::Expanded
286
+ }
287
+ skrifa::attribute::Stretch::EXTRA_EXPANDED => {
288
+ pdf_writer::types::FontStretch::ExtraExpanded
289
+ }
290
+ skrifa::attribute::Stretch::ULTRA_EXPANDED => {
291
+ pdf_writer::types::FontStretch::UltraExpanded
292
+ }
293
+ // Fallback
294
+ _ => pdf_writer::types::FontStretch::Normal,
295
+ },
296
+ )
297
+ .weight(match self.font.weight() as i32 {
298
+ 150..250 => 200,
299
+ 250..350 => 300,
300
+ 350..450 => 400,
301
+ 450..550 => 500,
302
+ 550..650 => 600,
303
+ 650..750 => 700,
304
+ 750..850 => 800,
305
+ other => {
306
+ if other < 150 {
307
+ 100
308
+ } else {
309
+ 900
310
+ }
311
+ }
312
+ });
313
+
314
+ font_descriptor.finish();
315
+ }
316
+
317
+ let mut type3_font = chunk.type3_font(root_ref);
318
+ resource_dictionary.to_pdf_resources(
319
+ &mut type3_font,
320
+ sc,
321
+ &mut chunk_container.non_stream.resource_dictionaries,
322
+ );
323
+
324
+ // See https://github.com/typst/typst/issues/5067 as to why we write this.
325
+ type3_font.name(Name(base_font.as_bytes()));
326
+ type3_font.bbox(font_bbox.to_pdf_rect());
327
+ type3_font.to_unicode(cmap_ref);
328
+ type3_font.matrix(
329
+ Transform::from_scale(
330
+ 1.0 / (self.font.units_per_em()),
331
+ 1.0 / (self.font.units_per_em()),
332
+ )
333
+ .to_pdf_transform(),
334
+ );
335
+ type3_font.first_char(0);
336
+ type3_font.last_char(u8::try_from(self.glyphs.len() - 1).unwrap());
337
+ type3_font.widths(self.widths.iter().copied());
338
+ if let Some(descriptor_ref) = descriptor_ref {
339
+ type3_font.font_descriptor(descriptor_ref);
340
+ }
341
+
342
+ let mut char_procs = type3_font.char_procs();
343
+ for (gid, ref_) in glyph_streams.iter().enumerate() {
344
+ char_procs.pair(format!("g{gid}").to_pdf_name(), *ref_);
345
+ }
346
+ char_procs.finish();
347
+
348
+ let names = (0..self.glyphs.len() as u16)
349
+ .map(|gid| format!("g{gid}"))
350
+ .collect::<Vec<_>>();
351
+
352
+ type3_font
353
+ .encoding_custom()
354
+ .differences()
355
+ .consecutive(0, names.iter().map(|n| n.to_pdf_name()));
356
+
357
+ type3_font.finish();
358
+
359
+ let cmap = {
360
+ let mut cmap = UnicodeCmap::new(CMAP_NAME, SYSTEM_INFO);
361
+
362
+ for g in 0..self.glyphs.len() {
363
+ let g = u8::try_from(g).unwrap();
364
+ let entry = self.cmap_entries.get(&g);
365
+ write_cmap_entry(&self.font, entry, sc, &mut cmap, g);
366
+ }
367
+
368
+ cmap
369
+ };
370
+
371
+ let cmap_stream = cmap.finish();
372
+ let cmap_stream =
373
+ FilterStreamBuilder::new_from_content_stream(&cmap_stream, &sc.serialize_settings())
374
+ .finish(&sc.serialize_settings());
375
+ let mut cmap = stream_chunk.cmap(cmap_ref, cmap_stream.encoded_data());
376
+ cmap_stream.write_filters(cmap.deref_mut().deref_mut());
377
+ cmap.writing_mode(WMode::Horizontal);
378
+ cmap.finish();
379
+ chunk_container.streams.fonts.push(stream_chunk);
380
+ }
381
+ }
382
+
383
+ pub(crate) type Type3ID = usize;
384
+
385
+ // A font can have multiple Type3 fonts, if more than 256 glyphs are used.
386
+ // We use this struct to keep track of them.
387
+ #[derive(Debug)]
388
+ pub(crate) struct Type3FontMapper {
389
+ font: Font,
390
+ fonts: Vec<Type3Font>,
391
+ }
392
+
393
+ impl Type3FontMapper {
394
+ pub(crate) fn new(font: Font) -> Type3FontMapper {
395
+ Self {
396
+ font,
397
+ fonts: Vec::new(),
398
+ }
399
+ }
400
+ }
401
+
402
+ impl Type3FontMapper {
403
+ /// Given a requested glyph coverage, find the corresponding font identifier of the
404
+ /// font that contains it.
405
+ pub(crate) fn id_from_glyph(&self, glyph: &ColoredGlyph) -> Option<FontIdentifier> {
406
+ self.fonts
407
+ .iter()
408
+ .position(|f| f.covers(glyph))
409
+ .map(|p| self.fonts[p].identifier())
410
+ }
411
+
412
+ /// Given a font identifier, return the corresponding Type3 font if it is part of
413
+ /// that type 3 font mapper.
414
+ pub(crate) fn font_from_id(&self, identifier: FontIdentifier) -> Option<&Type3Font> {
415
+ let pos = self
416
+ .fonts
417
+ .iter()
418
+ .position(|f| f.identifier() == identifier)?;
419
+ self.fonts.get(pos)
420
+ }
421
+
422
+ pub(crate) fn font_mut_from_id(
423
+ &mut self,
424
+ identifier: FontIdentifier,
425
+ ) -> Option<&mut Type3Font> {
426
+ let pos = self
427
+ .fonts
428
+ .iter()
429
+ .position(|f| f.identifier() == identifier)?;
430
+ self.fonts.get_mut(pos)
431
+ }
432
+
433
+ pub(crate) fn is_empty(&self) -> bool {
434
+ self.fonts.is_empty()
435
+ }
436
+
437
+ pub(crate) fn fonts(&self) -> &[Type3Font] {
438
+ &self.fonts
439
+ }
440
+
441
+ pub(crate) fn add_glyph(&mut self, glyph: ColoredGlyph) -> (FontIdentifier, Gid) {
442
+ // If the glyph has already been added, return the font identifier of
443
+ // the type 3 font as well as the Type3 gid in that font.
444
+ if let Some(id) = self.id_from_glyph(&glyph) {
445
+ let gid = self
446
+ .font_from_id(id.clone())
447
+ .unwrap()
448
+ .get_gid(&glyph)
449
+ .unwrap();
450
+ return (id, gid);
451
+ }
452
+
453
+ if let Some(last_font) = self.fonts.last_mut() {
454
+ if last_font.is_full() {
455
+ // If the last Type3 font is full, create a new one.
456
+ let mut font = Type3Font::new(self.font.clone(), self.fonts.len());
457
+ let id = font.identifier();
458
+ let gid = font.add_glyph(glyph);
459
+ self.fonts.push(font);
460
+ (id, gid)
461
+ } else {
462
+ // Otherwise, insert it into the last Type3 font.
463
+ let id = last_font.identifier();
464
+ (id, last_font.add_glyph(glyph))
465
+ }
466
+ } else {
467
+ // If not Type3 font exists yet, create a new one.
468
+ let mut font = Type3Font::new(self.font.clone(), self.fonts.len());
469
+ let id = font.identifier();
470
+ let gid = font.add_glyph(glyph);
471
+ self.fonts.push(font);
472
+ (id, gid)
473
+ }
474
+ }
475
+ }
476
+
477
+ pub(crate) fn base_font_name(font: &Font) -> String {
478
+ const REST_LEN: usize = 1 + 1 + IDENTITY_H.len();
479
+ let postscript_name = font.postscript_name().unwrap_or("unknown");
480
+
481
+ let max_len = 127 - REST_LEN;
482
+
483
+ let trimmed = &postscript_name[..postscript_name.len().min(max_len)];
484
+
485
+ trimmed.to_string()
486
+ }
487
+
488
+ #[cfg(test)]
489
+ mod tests {
490
+ use crate::color::rgb;
491
+ use crate::text::type3::{ColoredGlyph, Type3FontMapper};
492
+ use crate::text::Font;
493
+ use crate::text::GlyphId;
494
+ use crate::util::test_utils::NOTO_COLOR_EMOJI_COLR;
495
+
496
+ #[test]
497
+ fn type3_more_than_256_glyphs() {
498
+ let font = Font::new(NOTO_COLOR_EMOJI_COLR.clone(), 0).unwrap();
499
+ let mut t3 = Type3FontMapper::new(font.clone());
500
+
501
+ for i in 2..258 {
502
+ t3.add_glyph(ColoredGlyph::new(GlyphId::new(i), rgb::Color::black()));
503
+ }
504
+
505
+ assert_eq!(t3.fonts.len(), 1);
506
+ assert_eq!(
507
+ t3.fonts[0].add_glyph(ColoredGlyph::new(GlyphId::new(20), rgb::Color::black())),
508
+ 18
509
+ );
510
+
511
+ t3.add_glyph(ColoredGlyph::new(GlyphId::new(512), rgb::Color::black()));
512
+ assert_eq!(t3.fonts.len(), 2);
513
+ }
514
+ }