1//! Layers written as the sheets of a PDF: glyphs as text in subset fonts, which viewers
2//! select, search and copy, shapes and ink as vector paths, pictures and icons as images.
3
4use super::{
5 GlyphRun, Layer, PathStyle, Primitive, RasterImage, RenderError, Stroke, icon, srgb_byte,
6};
7use parley::fontique::Blob;
8use pdf_writer::{
9 Content, Filter, Finish, Name, Pdf, Rect, Ref, Str, TextStr,
10 types::{
11 BlendMode, CidFontType, FontFlags, LineCapStyle, LineJoinStyle, SystemInfo,
12 TextRenderingMode, UnicodeCmap,
13 },
14};
15use std::collections::{BTreeMap, HashMap, btree_map::Entry};
16use swash::{
17 FontRef, StringId,
18 scale::{Render, ScaleContext, Source, StrikeWith, image::Content as Pixels},
19 tag_from_bytes,
20 zeno::{Command, PathData},
21};
22
23/// One sheet of paper.
24pub struct Sheet<'a> {
25 /// Width and height in points.
26 pub size: [f32; 2],
27 /// Layers whose device units are points from the sheet's top left.
28 pub layers: Vec<Layer<'a>>,
29}
30
31/// `sheets` as a PDF document titled `title`.
32pub fn pdf(title: &str, sheets: &[Sheet<'_>]) -> Result<Vec<u8>, RenderError> {
33 let mut document = Document {
34 pdf: Pdf::new(),
35 next: 1,
36 fonts: Vec::new(),
37 images: HashMap::new(),
38 states: HashMap::new(),
39 scale: ScaleContext::new(),
40 };
41 let catalog = document.reserve();
42 let tree = document.reserve();
43 let pages: Vec<_> = sheets
44 .iter()
45 .map(|sheet| {
46 let page = document.reserve();
47 let content = document.reserve();
48 let used = document.sheet(sheet, content)?;
49 Ok((page, used))
50 })
51 .collect::<Result<_, RenderError>>()?;
52 for ((page, used), sheet) in pages.iter().zip(sheets) {
53 let mut writer = document.pdf.page(*page);
54 writer
55 .parent(tree)
56 .media_box(Rect::new(0.0, 0.0, sheet.size[0], sheet.size[1]))
57 .contents(used.content);
58 let mut resources = writer.resources();
59 let mut fonts = resources.fonts();
60 for font in &used.fonts {
61 let reference = document.fonts[*font].reference;
62 fonts.pair(Name(format!("F{font}").as_bytes()), reference);
63 }
64 fonts.finish();
65 let mut images = resources.x_objects();
66 for image in &used.images {
67 images.pair(Name(format!("I{}", image.get()).as_bytes()), *image);
68 }
69 images.finish();
70 let mut states = resources.ext_g_states();
71 for state in &used.states {
72 states.pair(Name(format!("G{}", state.get()).as_bytes()), *state);
73 }
74 }
75 document
76 .pdf
77 .pages(tree)
78 .kids(pages.iter().map(|(page, _)| *page))
79 .count(pages.len() as i32);
80 document.pdf.catalog(catalog).pages(tree);
81 let info = document.reserve();
82 document
83 .pdf
84 .document_info(info)
85 .title(TextStr(title))
86 .producer(TextStr("Snowbound"));
87 let fonts = std::mem::take(&mut document.fonts);
88 for font in fonts.into_iter().filter(|font| font.outlined) {
89 document.write_font(font)?;
90 }
91 Ok(document.pdf.finish())
92}
93
94struct Document {
95 pdf: Pdf,
96 next: i32,
97 fonts: Vec<Font>,
98 /// Image XObjects by what they show.
99 images: HashMap<ImageKey, Ref>,
100 /// Graphics states by fill and stroke opacity bits and whether they multiply.
101 states: HashMap<(u32, bool), Ref>,
102 scale: ScaleContext,
103}
104
105#[derive(Hash, PartialEq, Eq)]
106enum ImageKey {
107 Raster(u64),
108 Icon {
109 sources: usize,
110 size: u32,
111 tint: [u32; 3],
112 palette: [Option<[u32; 3]>; 3],
113 },
114 Glyph {
115 font: u64,
116 index: u32,
117 glyph: u32,
118 size: u32,
119 color: [u32; 3],
120 },
121}
122
123/// A face the document shows glyphs of.
124struct Font {
125 reference: Ref,
126 data: Blob<u8>,
127 index: u32,
128 /// Whether its glyphs are outlines the PDF embeds; others draw as pictures.
129 outlined: bool,
130 glyphs: subsetter::GlyphRemapper,
131 /// The text each subset glyph shows where it first appeared.
132 text: BTreeMap<u16, String>,
133}
134
135/// What a sheet's content refers to.
136struct Used {
137 content: Ref,
138 fonts: Vec<usize>,
139 images: Vec<Ref>,
140 states: Vec<Ref>,
141}
142
143impl Used {
144 fn add<T: PartialEq>(list: &mut Vec<T>, item: T) {
145 if !list.contains(&item) {
146 list.push(item);
147 }
148 }
149}
150
151fn compress(data: &[u8]) -> Vec<u8> {
152 miniz_oxide::deflate::compress_to_vec_zlib(data, 6)
153}
154
155/// sRGB components of a linear colour.
156fn encoded(color: [f32; 4]) -> [f32; 3] {
157 [color[0], color[1], color[2]].map(|value| f32::from(srgb_byte(value.clamp(0.0, 1.0))) / 255.0)
158}
159
160/// Bézier handle length for a quarter circle of radius one.
161const KAPPA: f32 = 0.552_284_8;
162
163/// Appends an ellipse's quarter arcs around `rect`'s corners of radii `radius`, as a
164/// closed path.
165fn rounded(content: &mut Content, [left, top, right, bottom]: [f32; 4], radius: [f32; 2]) {
166 let rx = radius[0].clamp(0.0, (right - left) / 2.0);
167 let ry = radius[1].clamp(0.0, (bottom - top) / 2.0);
168 if rx <= 0.0 || ry <= 0.0 {
169 content.rect(left, top, right - left, bottom - top);
170 return;
171 }
172 let [kx, ky] = [rx * KAPPA, ry * KAPPA];
173 content.move_to(left + rx, top);
174 content.line_to(right - rx, top);
175 content.cubic_to(right - rx + kx, top, right, top + ry - ky, right, top + ry);
176 content.line_to(right, bottom - ry);
177 content.cubic_to(
178 right,
179 bottom - ry + ky,
180 right - rx + kx,
181 bottom,
182 right - rx,
183 bottom,
184 );
185 content.line_to(left + rx, bottom);
186 content.cubic_to(
187 left + rx - kx,
188 bottom,
189 left,
190 bottom - ry + ky,
191 left,
192 bottom - ry,
193 );
194 content.line_to(left, top + ry);
195 content.cubic_to(left, top + ry - ky, left + rx - kx, top, left + rx, top);
196 content.close_path();
197}
198
199fn circle(content: &mut Content, [x, y]: [f32; 2], radius: f32) {
200 rounded(
201 content,
202 [x - radius, y - radius, x + radius, y + radius],
203 [radius; 2],
204 );
205}
206
207/// The text each of `count` glyphs shows: a cluster's text goes to its first glyph, and a
208/// ligature's later clusters to the glyph before them.
209fn glyph_text(
210 text: &str,
211 clusters: &mut dyn Iterator<Item = (std::ops::Range<usize>, usize)>,
212 count: usize,
213) -> Vec<String> {
214 let mut texts = vec![String::new(); count];
215 let mut at = 0_usize;
216 for (range, glyphs) in clusters {
217 let shown = text.get(range).unwrap_or_default();
218 if glyphs == 0 {
219 if let Some(previous) = at.checked_sub(1).and_then(|at| texts.get_mut(at)) {
220 previous.push_str(shown);
221 }
222 continue;
223 }
224 if let Some(first) = texts.get_mut(at) {
225 first.push_str(shown);
226 }
227 at += glyphs;
228 }
229 texts
230}
231
232impl Document {
233 fn reserve(&mut self) -> Ref {
234 let reference = Ref::new(self.next);
235 self.next += 1;
236 reference
237 }
238
239 fn sheet(&mut self, sheet: &Sheet<'_>, content_ref: Ref) -> Result<Used, RenderError> {
240 let mut used = Used {
241 content: content_ref,
242 fonts: Vec::new(),
243 images: Vec::new(),
244 states: Vec::new(),
245 };
246 let mut content = Content::new();
247 // Layers measure down from the top left, as the renderer's do.
248 content.transform([1.0, 0.0, 0.0, -1.0, 0.0, sheet.size[1]]);
249 for layer in &sheet.layers {
250 content.save_state();
251 if let Some([left, top, right, bottom]) = layer.clip {
252 content
253 .rect(left, top, right - left, bottom - top)
254 .clip_nonzero()
255 .end_path();
256 }
257 // Lines of text outside a clipping layer are left out, so that no sheet's text
258 // layer holds text it does not show.
259 let rows = layer
260 .clip
261 .map(|clip| [clip[1], clip[3]].map(|y| (y - layer.origin[1]) / layer.scale));
262 content.transform([
263 layer.scale,
264 0.0,
265 0.0,
266 layer.scale,
267 layer.origin[0],
268 layer.origin[1],
269 ]);
270 for primitive in layer.primitives {
271 self.primitive(&mut content, &mut used, primitive, rows)?;
272 }
273 content.restore_state();
274 }
275 let data = compress(&content.finish());
276 self.pdf
277 .stream(content_ref, &data)
278 .filter(Filter::FlateDecode);
279 Ok(used)
280 }
281
282 /// Sets the fill, or with `stroke` the stroke, to `color` with its opacity.
283 fn paint(&mut self, content: &mut Content, used: &mut Used, color: [f32; 4], stroke: bool) {
284 let [red, green, blue] = encoded(color);
285 if stroke {
286 content.set_stroke_rgb(red, green, blue);
287 } else {
288 content.set_fill_rgb(red, green, blue);
289 }
290 if color[3] < 1.0 {
291 self.opacity(content, used, color[3], false);
292 }
293 }
294
295 /// Paints at `opacity`, multiplying what lies beneath if `multiply`.
296 fn opacity(&mut self, content: &mut Content, used: &mut Used, opacity: f32, multiply: bool) {
297 let opacity = opacity.clamp(0.0, 1.0);
298 let key = (opacity.to_bits(), multiply);
299 let state = match self.states.get(&key) {
300 Some(state) => *state,
301 None => {
302 let state = self.reserve();
303 let mut writer = self.pdf.ext_graphics(state);
304 writer.non_stroking_alpha(opacity).stroking_alpha(opacity);
305 if multiply {
306 writer.blend_mode(BlendMode::Multiply);
307 }
308 writer.finish();
309 self.states.insert(key, state);
310 state
311 }
312 };
313 Used::add(&mut used.states, state);
314 content.set_parameters(Name(format!("G{}", state.get()).as_bytes()));
315 }
316
317 fn primitive(
318 &mut self,
319 content: &mut Content,
320 used: &mut Used,
321 primitive: &Primitive<'_>,
322 rows: Option<[f32; 2]>,
323 ) -> Result<(), RenderError> {
324 match primitive {
325 Primitive::Text {
326 text,
327 origin,
328 clip,
329 ink,
330 } => {
331 content.save_state();
332 if let Some([left, top, right, bottom]) = *clip {
333 content
334 .rect(left, top, right - left, bottom - top)
335 .clip_nonzero()
336 .end_path();
337 }
338 text.runs(&mut |run| self.run(content, used, run, *origin, *ink, rows))?;
339 content.restore_state();
340 }
341 Primitive::Icon {
342 sources,
343 origin,
344 size,
345 tint,
346 palette,
347 } => {
348 // Icons are small art; eight pixels a point keeps them sharp on paper.
349 let side = (size * 8.0).ceil().clamp(16.0, 512.0) as u32;
350 let key = ImageKey::Icon {
351 sources: sources.as_ptr() as usize,
352 size: side,
353 tint: [tint[0], tint[1], tint[2]].map(f32::to_bits),
354 palette: palette.bits(),
355 };
356 let image = match self.images.get(&key) {
357 Some(image) => *image,
358 None => {
359 let raster =
360 icon::rasterize(sources, side, [tint[0], tint[1], tint[2]], palette);
361 let image = self.image([side; 2], &raster.data, false);
362 self.images.insert(key, image);
363 image
364 }
365 };
366 content.save_state();
367 if tint[3] < 1.0 {
368 self.opacity(content, used, tint[3], false);
369 }
370 self.place(
371 content,
372 used,
373 image,
374 [origin[0], origin[1], origin[0] + size, origin[1] + size],
375 );
376 content.restore_state();
377 }
378 Primitive::Path {
379 data,
380 origin,
381 style,
382 colors,
383 } => {
384 let fill = matches!(style, PathStyle::Fill);
385 let (PathStyle::Fill | PathStyle::Stroke(_)) = style else {
386 return Ok(());
387 };
388 content.save_state();
389 content.transform([1.0, 0.0, 0.0, 1.0, origin[0], origin[1]]);
390 let average = std::array::from_fn(|at| (colors[0][at] + colors[1][at]) / 2.0);
391 self.paint(content, used, average, !fill);
392 let mut last = [0.0_f32; 2];
393 for command in data.commands() {
394 match command {
395 Command::MoveTo(to) => {
396 content.move_to(to.x, to.y);
397 last = [to.x, to.y];
398 }
399 Command::LineTo(to) => {
400 content.line_to(to.x, to.y);
401 last = [to.x, to.y];
402 }
403 Command::CurveTo(one, two, to) => {
404 content.cubic_to(one.x, one.y, two.x, two.y, to.x, to.y);
405 last = [to.x, to.y];
406 }
407 Command::QuadTo(control, to) => {
408 let near =
409 |from: f32, control: f32| from + (control - from) * 2.0 / 3.0;
410 content.cubic_to(
411 near(last[0], control.x),
412 near(last[1], control.y),
413 near(to.x, control.x),
414 near(to.y, control.y),
415 to.x,
416 to.y,
417 );
418 last = [to.x, to.y];
419 }
420 Command::Close => {
421 content.close_path();
422 }
423 }
424 }
425 match style {
426 PathStyle::Stroke(width) => {
427 content
428 .set_line_width(*width)
429 .set_line_cap(LineCapStyle::RoundCap)
430 .set_line_join(LineJoinStyle::RoundJoin)
431 .stroke();
432 }
433 _ => {
434 content.fill_nonzero();
435 }
436 }
437 content.restore_state();
438 }
439 Primitive::Rect { rect, color } => {
440 content.save_state();
441 self.paint(content, used, *color, false);
442 content
443 .rect(rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1])
444 .fill_nonzero();
445 content.restore_state();
446 }
447 Primitive::RoundedRect {
448 rect,
449 radius,
450 stroke,
451 color,
452 } => {
453 content.save_state();
454 self.paint(content, used, *color, stroke.is_some());
455 rounded(content, *rect, *radius);
456 match stroke {
457 None => {
458 content.fill_nonzero();
459 }
460 Some(Stroke::Solid(width)) => {
461 content.set_line_width(*width).stroke();
462 }
463 Some(Stroke::Dashed(width)) => {
464 content
465 .set_line_width(*width)
466 .set_dash_pattern([width * 2.0, width * 2.0], 0.0)
467 .stroke();
468 }
469 }
470 content.restore_state();
471 }
472 Primitive::Gradient {
473 rect,
474 radius,
475 colors,
476 } => {
477 content.save_state();
478 let average = std::array::from_fn(|at| (colors[0][at] + colors[1][at]) / 2.0);
479 self.paint(content, used, average, false);
480 rounded(content, *rect, *radius);
481 content.fill_nonzero();
482 content.restore_state();
483 }
484 // Soft shadows are for the screen.
485 Primitive::Shadow { .. } => {}
486 Primitive::Image { image, rect } => {
487 let key = ImageKey::Raster(image.id());
488 let reference = match self.images.get(&key) {
489 Some(reference) => *reference,
490 None => {
491 let reference = self.raster(image);
492 self.images.insert(key, reference);
493 reference
494 }
495 };
496 self.place(content, used, reference, *rect);
497 }
498 Primitive::Segment {
499 from,
500 to,
501 width,
502 round,
503 color,
504 } => {
505 content.save_state();
506 self.paint(content, used, *color, true);
507 content
508 .set_line_width(*width)
509 .set_line_cap(if *round {
510 LineCapStyle::RoundCap
511 } else {
512 LineCapStyle::ProjectingSquareCap
513 })
514 .move_to(from[0], from[1])
515 .line_to(to[0], to[1])
516 .stroke();
517 content.restore_state();
518 }
519 Primitive::Taper {
520 from,
521 to,
522 widths,
523 round: false,
524 color,
525 } => {
526 content.save_state();
527 self.paint(content, used, *color, false);
528 // Square ends reach half their width past each point, as a square tip does.
529 let [from_radius, to_radius] = widths.map(|width| width / 2.0);
530 let [dx, dy] = [to[0] - from[0], to[1] - from[1]];
531 let length = dx.hypot(dy);
532 let [ux, uy] = if length > 0.0 {
533 [dx / length, dy / length]
534 } else {
535 [1.0, 0.0]
536 };
537 let corner = |center: &[f32; 2], radius: f32, ahead: f32, side: f32| {
538 [
539 center[0] + radius * (ux * ahead - uy * side),
540 center[1] + radius * (uy * ahead + ux * side),
541 ]
542 };
543 let corners = [
544 corner(from, from_radius, -1.0, 1.0),
545 corner(to, to_radius, 1.0, 1.0),
546 corner(to, to_radius, 1.0, -1.0),
547 corner(from, from_radius, -1.0, -1.0),
548 ];
549 content.move_to(corners[0][0], corners[0][1]);
550 for corner in &corners[1..] {
551 content.line_to(corner[0], corner[1]);
552 }
553 content.close_path().fill_nonzero();
554 content.restore_state();
555 }
556 Primitive::Taper {
557 from,
558 to,
559 widths,
560 color,
561 ..
562 } => {
563 content.save_state();
564 self.paint(content, used, *color, false);
565 let [from_radius, to_radius] = widths.map(|width| width / 2.0);
566 // Each part fills on its own, as their windings differ.
567 circle(content, *from, from_radius);
568 content.fill_nonzero();
569 circle(content, *to, to_radius);
570 content.fill_nonzero();
571 let [dx, dy] = [to[0] - from[0], to[1] - from[1]];
572 let length = dx.hypot(dy);
573 if length > (from_radius - to_radius).abs() {
574 // The lines touching both ends' circles.
575 let [ux, uy] = [dx / length, dy / length];
576 let sine = (from_radius - to_radius) / length;
577 let cosine = (1.0 - sine * sine).sqrt();
578 let touch = |center: &[f32; 2], radius: f32, side: f32| {
579 [
580 center[0] + radius * (-uy * side * cosine + ux * sine),
581 center[1] + radius * (ux * side * cosine + uy * sine),
582 ]
583 };
584 let corners = [
585 touch(from, from_radius, 1.0),
586 touch(to, to_radius, 1.0),
587 touch(to, to_radius, -1.0),
588 touch(from, from_radius, -1.0),
589 ];
590 content.move_to(corners[0][0], corners[0][1]);
591 for corner in &corners[1..] {
592 content.line_to(corner[0], corner[1]);
593 }
594 content.close_path().fill_nonzero();
595 }
596 content.restore_state();
597 }
598 Primitive::Highlight {
599 from,
600 to,
601 width,
602 color,
603 } => {
604 content.save_state();
605 self.opacity(content, used, color[3], true);
606 let [red, green, blue] = encoded(*color);
607 content
608 .set_stroke_rgb(red, green, blue)
609 .set_line_width(*width)
610 .set_line_cap(LineCapStyle::ProjectingSquareCap)
611 .move_to(from[0], from[1])
612 .line_to(to[0], to[1])
613 .stroke();
614 content.restore_state();
615 }
616 }
617 Ok(())
618 }
619
620 /// Draws image XObject `image` filling `rect`.
621 fn place(&mut self, content: &mut Content, used: &mut Used, image: Ref, rect: [f32; 4]) {
622 Used::add(&mut used.images, image);
623 content.save_state();
624 // Image space runs up from the bottom left.
625 content.transform([
626 rect[2] - rect[0],
627 0.0,
628 0.0,
629 rect[1] - rect[3],
630 rect[0],
631 rect[3],
632 ]);
633 content.x_object(Name(format!("I{}", image.get()).as_bytes()));
634 content.restore_state();
635 }
636
637 /// A picture as an image XObject: opaque photographs as JPEG, others losslessly.
638 fn raster(&mut self, image: &RasterImage) -> Ref {
639 // Pixels are premultiplied in linear light.
640 let straight: Vec<u8> = image
641 .pixels()
642 .as_chunks::<4>()
643 .0
644 .iter()
645 .flat_map(|pixel| {
646 let alpha = pixel[3];
647 if alpha == 255 || alpha == 0 {
648 return [pixel[0], pixel[1], pixel[2], alpha];
649 }
650 let [red, green, blue] = [pixel[0], pixel[1], pixel[2]].map(|byte| {
651 let linear = crate::linear(f32::from(byte) / 255.0);
652 srgb_byte((linear * 255.0 / f32::from(alpha)).min(1.0))
653 });
654 [red, green, blue, alpha]
655 })
656 .collect();
657 let size = image.size();
658 let opaque = straight
659 .as_chunks::<4>()
660 .0
661 .iter()
662 .all(|pixel| pixel[3] == 255);
663 if opaque && size[0] * size[1] >= 64 * 64 {
664 let rgb: Vec<u8> = straight
665 .as_chunks::<4>()
666 .0
667 .iter()
668 .flat_map(|pixel| [pixel[0], pixel[1], pixel[2]])
669 .collect();
670 let mut jpeg = Vec::new();
671 let encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut jpeg, 92);
672 if image::ImageEncoder::write_image(
673 encoder,
674 &rgb,
675 size[0],
676 size[1],
677 image::ExtendedColorType::Rgb8,
678 )
679 .is_ok()
680 {
681 let reference = self.reserve();
682 let mut writer = self.pdf.image_xobject(reference, &jpeg);
683 writer.filter(Filter::DctDecode);
684 writer
685 .width(size[0] as i32)
686 .height(size[1] as i32)
687 .color_space_name(Name(b"DeviceRGB"))
688 .bits_per_component(8)
689 .interpolate(true);
690 return reference;
691 }
692 }
693 self.image(size, &straight, true)
694 }
695
696 /// Straight sRGB RGBA pixels as a losslessly compressed image XObject.
697 fn image(&mut self, [width, height]: [u32; 2], rgba: &[u8], interpolate: bool) -> Ref {
698 let rgb: Vec<u8> = rgba
699 .as_chunks::<4>()
700 .0
701 .iter()
702 .flat_map(|pixel| [pixel[0], pixel[1], pixel[2]])
703 .collect();
704 let mask = rgba
705 .as_chunks::<4>()
706 .0
707 .iter()
708 .any(|pixel| pixel[3] != 255)
709 .then(|| {
710 let alpha: Vec<u8> = rgba
711 .as_chunks::<4>()
712 .0
713 .iter()
714 .map(|pixel| pixel[3])
715 .collect();
716 let mask = self.reserve();
717 let data = compress(&alpha);
718 let mut writer = self.pdf.image_xobject(mask, &data);
719 writer.filter(Filter::FlateDecode);
720 writer
721 .width(width as i32)
722 .height(height as i32)
723 .color_space_name(Name(b"DeviceGray"))
724 .bits_per_component(8)
725 .interpolate(interpolate);
726 mask
727 });
728 let reference = self.reserve();
729 let data = compress(&rgb);
730 let mut writer = self.pdf.image_xobject(reference, &data);
731 writer.filter(Filter::FlateDecode);
732 writer
733 .width(width as i32)
734 .height(height as i32)
735 .color_space_name(Name(b"DeviceRGB"))
736 .bits_per_component(8)
737 .interpolate(interpolate);
738 if let Some(mask) = mask {
739 writer.s_mask(mask);
740 }
741 reference
742 }
743
744 /// The document's face for a run's font, added on first use.
745 fn font(&mut self, data: &Blob<u8>, index: u32) -> Result<usize, RenderError> {
746 if let Some(found) = self
747 .fonts
748 .iter()
749 .position(|font| font.data.id() == data.id() && font.index == index)
750 {
751 return Ok(found);
752 }
753 let face = FontRef::from_index(data.as_ref(), index as usize)
754 .ok_or(RenderError::UnreadableFont)?;
755 let outlined = face.table(tag_from_bytes(b"glyf")).is_some()
756 || face.table(tag_from_bytes(b"CFF ")).is_some();
757 let reference = self.reserve();
758 self.fonts.push(Font {
759 reference,
760 data: data.clone(),
761 index,
762 outlined,
763 glyphs: subsetter::GlyphRemapper::new(),
764 text: BTreeMap::new(),
765 });
766 Ok(self.fonts.len() - 1)
767 }
768
769 fn run(
770 &mut self,
771 content: &mut Content,
772 used: &mut Used,
773 run: GlyphRun<'_>,
774 origin: [f32; 2],
775 ink: [f32; 4],
776 rows: Option<[f32; 2]>,
777 ) -> Result<(), RenderError> {
778 let [top, bottom] = [
779 origin[1] + run.line[0],
780 origin[1] + run.line[0] + run.line[1],
781 ];
782 if rows.is_some_and(|[first, last]| bottom <= first || top >= last) {
783 return Ok(());
784 }
785 let font = self.font(run.font, run.index)?;
786 let color = run.color.unwrap_or(ink);
787 let glyphs: Vec<_> = run.glyphs.collect();
788 let texts = glyph_text(run.text, run.clusters, glyphs.len());
789 content.save_state();
790 if self.fonts[font].outlined {
791 Used::add(&mut used.fonts, font);
792 self.paint(content, used, color, false);
793 let face = FontRef::from_index(run.font.as_ref(), run.index as usize)
794 .ok_or(RenderError::UnreadableFont)?;
795 let units = f32::from(face.metrics(&[]).units_per_em.max(1));
796 let advances = face.glyph_metrics(&[]);
797 let name = format!("F{font}");
798 content
799 .begin_text()
800 .set_font(Name(name.as_bytes()), run.size);
801 if run.embolden {
802 self.paint(content, used, color, true);
803 content
804 .set_text_rendering_mode(TextRenderingMode::FillStroke)
805 .set_line_width(run.size / 24.0);
806 }
807 let shear = run.skew.map_or(0.0, |degrees| degrees.to_radians().tan());
808 let mut shown: Vec<(u16, f32)> = Vec::new();
809 let mut pen: Option<[f32; 2]> = None;
810 let flush = |content: &mut Content, shown: &mut Vec<(u16, f32)>| {
811 if shown.is_empty() {
812 return;
813 }
814 let mut positioned = content.show_positioned();
815 let mut items = positioned.items();
816 for (glyph, adjust) in shown.drain(..) {
817 if adjust != 0.0 {
818 items.adjust(adjust);
819 }
820 items.show(Str(&glyph.to_be_bytes()));
821 }
822 };
823 for (glyph, text) in glyphs.iter().zip(texts) {
824 let Ok(id) = u16::try_from(glyph.id) else {
825 continue;
826 };
827 let face = &mut self.fonts[font];
828 let cid = face.glyphs.remap(id);
829 let [x, y] = [origin[0] + glyph.x, origin[1] + glyph.y];
830 let conflict = !text.is_empty()
831 && match face.text.entry(cid) {
832 Entry::Vacant(entry) => {
833 entry.insert(text.clone());
834 false
835 }
836 Entry::Occupied(entry) => *entry.get() != text,
837 };
838 let advance = advances.advance_width(id) / units * run.size;
839 match pen {
840 Some([at, line]) if !conflict && (line - y).abs() < 0.001 => {
841 shown.push((cid, (at - x) * 1000.0 / run.size));
842 }
843 _ => {
844 flush(content, &mut shown);
845 content.set_text_matrix([1.0, 0.0, shear, -1.0, x, y]);
846 if conflict {
847 // A glyph shared by different text says what it shows here.
848 content
849 .begin_marked_content_with_properties(Name(b"Span"))
850 .properties()
851 .actual_text(TextStr(&text));
852 content.show(Str(&cid.to_be_bytes()));
853 content.end_marked_content();
854 pen = None;
855 continue;
856 }
857 shown.push((cid, 0.0));
858 }
859 }
860 pen = Some([x + advance, y]);
861 }
862 flush(content, &mut shown);
863 content.end_text();
864 } else {
865 for glyph in &glyphs {
866 self.glyph_picture(content, used, &run, *glyph, origin, color);
867 }
868 }
869 for decoration in run.decorations {
870 self.paint(content, used, decoration.color.unwrap_or(color), false);
871 content
872 .rect(
873 origin[0] + decoration.x,
874 origin[1] + decoration.y,
875 decoration.width,
876 decoration.thickness,
877 )
878 .fill_nonzero();
879 }
880 content.restore_state();
881 Ok(())
882 }
883
884 /// A glyph of a face without outlines the PDF can embed, such as a colour emoji, drawn
885 /// as a picture four pixels a point.
886 fn glyph_picture(
887 &mut self,
888 content: &mut Content,
889 used: &mut Used,
890 run: &GlyphRun<'_>,
891 glyph: super::Glyph,
892 origin: [f32; 2],
893 color: [f32; 4],
894 ) {
895 const DENSITY: f32 = 4.0;
896 let Ok(id) = u16::try_from(glyph.id) else {
897 return;
898 };
899 let Some(face) = FontRef::from_index(run.font.as_ref(), run.index as usize) else {
900 return;
901 };
902 let size = run.size * DENSITY;
903 let mut scaler = self
904 .scale
905 .builder(face)
906 .size(size)
907 .normalized_coords(run.coords.iter().copied())
908 .build();
909 let Some(rendered) = Render::new(&[
910 Source::ColorOutline(0),
911 Source::ColorBitmap(StrikeWith::BestFit),
912 Source::Outline,
913 ])
914 .render(&mut scaler, id) else {
915 return;
916 };
917 let placement = rendered.placement;
918 if placement.width == 0 || placement.height == 0 {
919 return;
920 }
921 let key = ImageKey::Glyph {
922 font: run.font.id(),
923 index: run.index,
924 glyph: glyph.id,
925 size: size.to_bits(),
926 color: [color[0], color[1], color[2]].map(f32::to_bits),
927 };
928 let image = match self.images.get(&key) {
929 Some(image) => *image,
930 None => {
931 let [red, green, blue] = encoded(color).map(|value| (value * 255.0).round() as u8);
932 let rgba: Vec<u8> = match rendered.content {
933 Pixels::Mask => rendered
934 .data
935 .iter()
936 .flat_map(|coverage| [red, green, blue, *coverage])
937 .collect(),
938 Pixels::Color | Pixels::SubpixelMask => rendered.data.clone(),
939 };
940 let image = self.image([placement.width, placement.height], &rgba, true);
941 self.images.insert(key, image);
942 image
943 }
944 };
945 let [x, y] = [
946 origin[0] + glyph.x + placement.left as f32 / DENSITY,
947 origin[1] + glyph.y - placement.top as f32 / DENSITY,
948 ];
949 self.place(
950 content,
951 used,
952 image,
953 [
954 x,
955 y,
956 x + placement.width as f32 / DENSITY,
957 y + placement.height as f32 / DENSITY,
958 ],
959 );
960 }
961
962 /// Embeds `font`'s subset as a CID-keyed Type 0 font with a ToUnicode map.
963 fn write_font(&mut self, font: Font) -> Result<(), RenderError> {
964 let data = font.data.as_ref();
965 let face =
966 FontRef::from_index(data, font.index as usize).ok_or(RenderError::UnreadableFont)?;
967 let subset = subsetter::subset(data, font.index, &font.glyphs)
968 .map_err(|_| RenderError::UnreadableFont)?;
969 let cff = face.table(tag_from_bytes(b"CFF ")).is_some();
970 let metrics = face.metrics(&[]);
971 let scale = 1000.0 / f32::from(metrics.units_per_em.max(1));
972 let advances = face.glyph_metrics(&[]);
973 let widths: Vec<f32> = font
974 .glyphs
975 .remapped_gids()
976 .map(|old| advances.advance_width(old) * scale)
977 .collect();
978 let postscript: String = face
979 .localized_strings()
980 .find_by_id(StringId::PostScript, None)
981 .map(|name| name.chars().filter(char::is_ascii_alphanumeric).collect())
982 .filter(|name: &String| !name.is_empty())
983 .unwrap_or_else(|| "Font".to_owned());
984 // A subset's name starts with six capitals naming the subset.
985 let mut hash = font.reference.get() as u32;
986 let tag: String = (0..6)
987 .map(|_| {
988 hash = hash.wrapping_mul(1_103_515_245).wrapping_add(12_345);
989 char::from(b'A' + (hash >> 16) as u8 % 26)
990 })
991 .collect();
992 let base = format!("{tag}+{postscript}");
993 let [cid, descriptor, file, map] = [(); 4].map(|_| self.reserve());
994 let info = SystemInfo {
995 registry: Str(b"Adobe"),
996 ordering: Str(b"Identity"),
997 supplement: 0,
998 };
999 self.pdf
1000 .type0_font(font.reference)
1001 .base_font(Name(base.as_bytes()))
1002 .encoding_predefined(Name(b"Identity-H"))
1003 .descendant_font(cid)
1004 .to_unicode(map);
1005 let mut writer = self.pdf.cid_font(cid);
1006 writer
1007 .subtype(if cff {
1008 CidFontType::Type0
1009 } else {
1010 CidFontType::Type2
1011 })
1012 .base_font(Name(base.as_bytes()))
1013 .system_info(info)
1014 .font_descriptor(descriptor)
1015 .default_width(0.0);
1016 if !cff {
1017 writer.cid_to_gid_map_predefined(Name(b"Identity"));
1018 }
1019 writer.widths().consecutive(0, widths);
1020 writer.finish();
1021 let mut writer = self.pdf.font_descriptor(descriptor);
1022 writer
1023 .name(Name(base.as_bytes()))
1024 .flags(FontFlags::SYMBOLIC)
1025 .bbox(Rect::new(
1026 0.0,
1027 -metrics.descent * scale,
1028 metrics.max_width * scale,
1029 metrics.ascent * scale,
1030 ))
1031 .italic_angle(0.0)
1032 .ascent(metrics.ascent * scale)
1033 .descent(-metrics.descent * scale)
1034 .cap_height(metrics.cap_height * scale)
1035 .stem_v(80.0);
1036 if cff {
1037 writer.font_file3(file);
1038 } else {
1039 writer.font_file2(file);
1040 }
1041 writer.finish();
1042 let compressed = compress(&subset);
1043 let mut stream = self.pdf.stream(file, &compressed);
1044 stream.filter(Filter::FlateDecode);
1045 if cff {
1046 stream.pair(Name(b"Subtype"), Name(b"OpenType"));
1047 }
1048 stream.finish();
1049 let mut cmap = UnicodeCmap::new(Name(b"Custom"), info);
1050 for (glyph, text) in &font.text {
1051 cmap.pair_with_multiple(*glyph, text.chars());
1052 }
1053 let cmap = compress(&cmap.finish());
1054 self.pdf.stream(map, &cmap).filter(Filter::FlateDecode);
1055 Ok(())
1056 }
1057}