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 .chunks_exact(4)
643 .flat_map(|pixel| {
644 let alpha = pixel[3];
645 if alpha == 255 || alpha == 0 {
646 return [pixel[0], pixel[1], pixel[2], alpha];
647 }
648 let [red, green, blue] = [pixel[0], pixel[1], pixel[2]].map(|byte| {
649 let linear = crate::linear(f32::from(byte) / 255.0);
650 srgb_byte((linear * 255.0 / f32::from(alpha)).min(1.0))
651 });
652 [red, green, blue, alpha]
653 })
654 .collect();
655 let size = image.size();
656 let opaque = straight.chunks_exact(4).all(|pixel| pixel[3] == 255);
657 if opaque && size[0] * size[1] >= 64 * 64 {
658 let rgb: Vec<u8> = straight
659 .chunks_exact(4)
660 .flat_map(|pixel| [pixel[0], pixel[1], pixel[2]])
661 .collect();
662 let mut jpeg = Vec::new();
663 let encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut jpeg, 92);
664 if image::ImageEncoder::write_image(
665 encoder,
666 &rgb,
667 size[0],
668 size[1],
669 image::ExtendedColorType::Rgb8,
670 )
671 .is_ok()
672 {
673 let reference = self.reserve();
674 let mut writer = self.pdf.image_xobject(reference, &jpeg);
675 writer.filter(Filter::DctDecode);
676 writer
677 .width(size[0] as i32)
678 .height(size[1] as i32)
679 .color_space_name(Name(b"DeviceRGB"))
680 .bits_per_component(8)
681 .interpolate(true);
682 return reference;
683 }
684 }
685 self.image(size, &straight, true)
686 }
687
688 /// Straight sRGB RGBA pixels as a losslessly compressed image XObject.
689 fn image(&mut self, [width, height]: [u32; 2], rgba: &[u8], interpolate: bool) -> Ref {
690 let rgb: Vec<u8> = rgba
691 .chunks_exact(4)
692 .flat_map(|pixel| [pixel[0], pixel[1], pixel[2]])
693 .collect();
694 let mask = rgba.chunks_exact(4).any(|pixel| pixel[3] != 255).then(|| {
695 let alpha: Vec<u8> = rgba.chunks_exact(4).map(|pixel| pixel[3]).collect();
696 let mask = self.reserve();
697 let data = compress(&alpha);
698 let mut writer = self.pdf.image_xobject(mask, &data);
699 writer.filter(Filter::FlateDecode);
700 writer
701 .width(width as i32)
702 .height(height as i32)
703 .color_space_name(Name(b"DeviceGray"))
704 .bits_per_component(8)
705 .interpolate(interpolate);
706 mask
707 });
708 let reference = self.reserve();
709 let data = compress(&rgb);
710 let mut writer = self.pdf.image_xobject(reference, &data);
711 writer.filter(Filter::FlateDecode);
712 writer
713 .width(width as i32)
714 .height(height as i32)
715 .color_space_name(Name(b"DeviceRGB"))
716 .bits_per_component(8)
717 .interpolate(interpolate);
718 if let Some(mask) = mask {
719 writer.s_mask(mask);
720 }
721 reference
722 }
723
724 /// The document's face for a run's font, added on first use.
725 fn font(&mut self, data: &Blob<u8>, index: u32) -> Result<usize, RenderError> {
726 if let Some(found) = self
727 .fonts
728 .iter()
729 .position(|font| font.data.id() == data.id() && font.index == index)
730 {
731 return Ok(found);
732 }
733 let face = FontRef::from_index(data.as_ref(), index as usize)
734 .ok_or(RenderError::UnreadableFont)?;
735 let outlined = face.table(tag_from_bytes(b"glyf")).is_some()
736 || face.table(tag_from_bytes(b"CFF ")).is_some();
737 let reference = self.reserve();
738 self.fonts.push(Font {
739 reference,
740 data: data.clone(),
741 index,
742 outlined,
743 glyphs: subsetter::GlyphRemapper::new(),
744 text: BTreeMap::new(),
745 });
746 Ok(self.fonts.len() - 1)
747 }
748
749 fn run(
750 &mut self,
751 content: &mut Content,
752 used: &mut Used,
753 run: GlyphRun<'_>,
754 origin: [f32; 2],
755 ink: [f32; 4],
756 rows: Option<[f32; 2]>,
757 ) -> Result<(), RenderError> {
758 let [top, bottom] = [
759 origin[1] + run.line[0],
760 origin[1] + run.line[0] + run.line[1],
761 ];
762 if rows.is_some_and(|[first, last]| bottom <= first || top >= last) {
763 return Ok(());
764 }
765 let font = self.font(run.font, run.index)?;
766 let color = run.color.unwrap_or(ink);
767 let glyphs: Vec<_> = run.glyphs.collect();
768 let texts = glyph_text(run.text, run.clusters, glyphs.len());
769 content.save_state();
770 if self.fonts[font].outlined {
771 Used::add(&mut used.fonts, font);
772 self.paint(content, used, color, false);
773 let face = FontRef::from_index(run.font.as_ref(), run.index as usize)
774 .ok_or(RenderError::UnreadableFont)?;
775 let units = f32::from(face.metrics(&[]).units_per_em.max(1));
776 let advances = face.glyph_metrics(&[]);
777 let name = format!("F{font}");
778 content
779 .begin_text()
780 .set_font(Name(name.as_bytes()), run.size);
781 if run.embolden {
782 self.paint(content, used, color, true);
783 content
784 .set_text_rendering_mode(TextRenderingMode::FillStroke)
785 .set_line_width(run.size / 24.0);
786 }
787 let shear = run.skew.map_or(0.0, |degrees| degrees.to_radians().tan());
788 let mut shown: Vec<(u16, f32)> = Vec::new();
789 let mut pen: Option<[f32; 2]> = None;
790 let flush = |content: &mut Content, shown: &mut Vec<(u16, f32)>| {
791 if shown.is_empty() {
792 return;
793 }
794 let mut positioned = content.show_positioned();
795 let mut items = positioned.items();
796 for (glyph, adjust) in shown.drain(..) {
797 if adjust != 0.0 {
798 items.adjust(adjust);
799 }
800 items.show(Str(&glyph.to_be_bytes()));
801 }
802 };
803 for (glyph, text) in glyphs.iter().zip(texts) {
804 let Ok(id) = u16::try_from(glyph.id) else {
805 continue;
806 };
807 let face = &mut self.fonts[font];
808 let cid = face.glyphs.remap(id);
809 let [x, y] = [origin[0] + glyph.x, origin[1] + glyph.y];
810 let conflict = !text.is_empty()
811 && match face.text.entry(cid) {
812 Entry::Vacant(entry) => {
813 entry.insert(text.clone());
814 false
815 }
816 Entry::Occupied(entry) => *entry.get() != text,
817 };
818 let advance = advances.advance_width(id) / units * run.size;
819 match pen {
820 Some([at, line]) if !conflict && (line - y).abs() < 0.001 => {
821 shown.push((cid, (at - x) * 1000.0 / run.size));
822 }
823 _ => {
824 flush(content, &mut shown);
825 content.set_text_matrix([1.0, 0.0, shear, -1.0, x, y]);
826 if conflict {
827 // A glyph shared by different text says what it shows here.
828 content
829 .begin_marked_content_with_properties(Name(b"Span"))
830 .properties()
831 .actual_text(TextStr(&text));
832 content.show(Str(&cid.to_be_bytes()));
833 content.end_marked_content();
834 pen = None;
835 continue;
836 }
837 shown.push((cid, 0.0));
838 }
839 }
840 pen = Some([x + advance, y]);
841 }
842 flush(content, &mut shown);
843 content.end_text();
844 } else {
845 for glyph in &glyphs {
846 self.glyph_picture(content, used, &run, *glyph, origin, color);
847 }
848 }
849 for decoration in run.decorations {
850 self.paint(content, used, decoration.color.unwrap_or(color), false);
851 content
852 .rect(
853 origin[0] + decoration.x,
854 origin[1] + decoration.y,
855 decoration.width,
856 decoration.thickness,
857 )
858 .fill_nonzero();
859 }
860 content.restore_state();
861 Ok(())
862 }
863
864 /// A glyph of a face without outlines the PDF can embed, such as a colour emoji, drawn
865 /// as a picture four pixels a point.
866 fn glyph_picture(
867 &mut self,
868 content: &mut Content,
869 used: &mut Used,
870 run: &GlyphRun<'_>,
871 glyph: super::Glyph,
872 origin: [f32; 2],
873 color: [f32; 4],
874 ) {
875 const DENSITY: f32 = 4.0;
876 let Ok(id) = u16::try_from(glyph.id) else {
877 return;
878 };
879 let Some(face) = FontRef::from_index(run.font.as_ref(), run.index as usize) else {
880 return;
881 };
882 let size = run.size * DENSITY;
883 let mut scaler = self
884 .scale
885 .builder(face)
886 .size(size)
887 .normalized_coords(run.coords.iter().copied())
888 .build();
889 let Some(rendered) = Render::new(&[
890 Source::ColorOutline(0),
891 Source::ColorBitmap(StrikeWith::BestFit),
892 Source::Outline,
893 ])
894 .render(&mut scaler, id) else {
895 return;
896 };
897 let placement = rendered.placement;
898 if placement.width == 0 || placement.height == 0 {
899 return;
900 }
901 let key = ImageKey::Glyph {
902 font: run.font.id(),
903 index: run.index,
904 glyph: glyph.id,
905 size: size.to_bits(),
906 color: [color[0], color[1], color[2]].map(f32::to_bits),
907 };
908 let image = match self.images.get(&key) {
909 Some(image) => *image,
910 None => {
911 let [red, green, blue] = encoded(color).map(|value| (value * 255.0).round() as u8);
912 let rgba: Vec<u8> = match rendered.content {
913 Pixels::Mask => rendered
914 .data
915 .iter()
916 .flat_map(|coverage| [red, green, blue, *coverage])
917 .collect(),
918 Pixels::Color | Pixels::SubpixelMask => rendered.data.clone(),
919 };
920 let image = self.image([placement.width, placement.height], &rgba, true);
921 self.images.insert(key, image);
922 image
923 }
924 };
925 let [x, y] = [
926 origin[0] + glyph.x + placement.left as f32 / DENSITY,
927 origin[1] + glyph.y - placement.top as f32 / DENSITY,
928 ];
929 self.place(
930 content,
931 used,
932 image,
933 [
934 x,
935 y,
936 x + placement.width as f32 / DENSITY,
937 y + placement.height as f32 / DENSITY,
938 ],
939 );
940 }
941
942 /// Embeds `font`'s subset as a CID-keyed Type 0 font with a ToUnicode map.
943 fn write_font(&mut self, font: Font) -> Result<(), RenderError> {
944 let data = font.data.as_ref();
945 let face =
946 FontRef::from_index(data, font.index as usize).ok_or(RenderError::UnreadableFont)?;
947 let subset = subsetter::subset(data, font.index, &font.glyphs)
948 .map_err(|_| RenderError::UnreadableFont)?;
949 let cff = face.table(tag_from_bytes(b"CFF ")).is_some();
950 let metrics = face.metrics(&[]);
951 let scale = 1000.0 / f32::from(metrics.units_per_em.max(1));
952 let advances = face.glyph_metrics(&[]);
953 let widths: Vec<f32> = font
954 .glyphs
955 .remapped_gids()
956 .map(|old| advances.advance_width(old) * scale)
957 .collect();
958 let postscript: String = face
959 .localized_strings()
960 .find_by_id(StringId::PostScript, None)
961 .map(|name| name.chars().filter(char::is_ascii_alphanumeric).collect())
962 .filter(|name: &String| !name.is_empty())
963 .unwrap_or_else(|| "Font".to_owned());
964 // A subset's name starts with six capitals naming the subset.
965 let mut hash = font.reference.get() as u32;
966 let tag: String = (0..6)
967 .map(|_| {
968 hash = hash.wrapping_mul(1_103_515_245).wrapping_add(12_345);
969 char::from(b'A' + (hash >> 16) as u8 % 26)
970 })
971 .collect();
972 let base = format!("{tag}+{postscript}");
973 let [cid, descriptor, file, map] = [(); 4].map(|_| self.reserve());
974 let info = SystemInfo {
975 registry: Str(b"Adobe"),
976 ordering: Str(b"Identity"),
977 supplement: 0,
978 };
979 self.pdf
980 .type0_font(font.reference)
981 .base_font(Name(base.as_bytes()))
982 .encoding_predefined(Name(b"Identity-H"))
983 .descendant_font(cid)
984 .to_unicode(map);
985 let mut writer = self.pdf.cid_font(cid);
986 writer
987 .subtype(if cff {
988 CidFontType::Type0
989 } else {
990 CidFontType::Type2
991 })
992 .base_font(Name(base.as_bytes()))
993 .system_info(info)
994 .font_descriptor(descriptor)
995 .default_width(0.0);
996 if !cff {
997 writer.cid_to_gid_map_predefined(Name(b"Identity"));
998 }
999 writer.widths().consecutive(0, widths);
1000 writer.finish();
1001 let mut writer = self.pdf.font_descriptor(descriptor);
1002 writer
1003 .name(Name(base.as_bytes()))
1004 .flags(FontFlags::SYMBOLIC)
1005 .bbox(Rect::new(
1006 0.0,
1007 -metrics.descent * scale,
1008 metrics.max_width * scale,
1009 metrics.ascent * scale,
1010 ))
1011 .italic_angle(0.0)
1012 .ascent(metrics.ascent * scale)
1013 .descent(-metrics.descent * scale)
1014 .cap_height(metrics.cap_height * scale)
1015 .stem_v(80.0);
1016 if cff {
1017 writer.font_file3(file);
1018 } else {
1019 writer.font_file2(file);
1020 }
1021 writer.finish();
1022 let compressed = compress(&subset);
1023 let mut stream = self.pdf.stream(file, &compressed);
1024 stream.filter(Filter::FlateDecode);
1025 if cff {
1026 stream.pair(Name(b"Subtype"), Name(b"OpenType"));
1027 }
1028 stream.finish();
1029 let mut cmap = UnicodeCmap::new(Name(b"Custom"), info);
1030 for (glyph, text) in &font.text {
1031 cmap.pair_with_multiple(*glyph, text.chars());
1032 }
1033 let cmap = compress(&cmap.finish());
1034 self.pdf.stream(map, &cmap).filter(Filter::FlateDecode);
1035 Ok(())
1036 }
1037}