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