Skip to main content

ravenrs_report_pdf/
lib.rs

1//! PDF export for RavenRS Reporting: draws a [`Document`] as vector pages, with
2//! subsets of the fonts it was measured with embedded, so the file looks the
3//! same everywhere and its text can be selected and searched.
4//!
5//! ```
6//! use ravenrs_report::{Band, Control, Report, ReportEngine};
7//! use serde_json::json;
8//!
9//! let report = Report::new("Sales").detail(Band::new(18.0).control(Control::field("[Customer]").at(0.0, 0.0, 300.0, 18.0)));
10//! let document = ReportEngine::new().render(&report, &json!([{ "Customer": "Ana" }]));
11//! let pdf = ravenrs_report_pdf::to_pdf(&document)?;
12//! assert!(pdf.starts_with(b"%PDF-"));
13//! # Ok::<(), ravenrs_report_pdf::PdfError>(())
14//! ```
15
16use std::collections::{BTreeSet, HashMap};
17use std::fmt;
18use std::rc::Rc;
19
20use miniz_oxide::deflate::compress_to_vec_zlib;
21use pdf_writer::types::{
22    ActionType, AnnotationType, CidFontType, FontFlags, PageMode, SystemInfo, UnicodeCmap,
23};
24use pdf_writer::writers::Resources;
25use pdf_writer::{Content, Filter, Finish, Name, Pdf, Rect as PdfRect, Ref, Str, TextStr};
26use ravenrs_core::{Color, Rect};
27use ravenrs_report::vector::{self, PathSegment};
28use ravenrs_report::{Document, DocumentImage, ImageFormat, Item, Page, TextBlock};
29use ravenrs_text::{TextAttrs, TextSystem};
30use skrifa::instance::{LocationRef, Size};
31use skrifa::{GlyphId, MetadataProvider};
32use subsetter::GlyphRemapper;
33
34/// Why a document could not be written as PDF.
35#[derive(Clone, Debug, PartialEq, Eq)]
36#[non_exhaustive]
37pub enum PdfError {
38    /// A font could not be read or reduced to the glyphs in use.
39    Font(String),
40}
41
42impl fmt::Display for PdfError {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            Self::Font(reason) => write!(f, "could not embed a font: {reason}"),
46        }
47    }
48}
49
50impl std::error::Error for PdfError {}
51
52/// Writes `document` as a PDF 1.7 file.
53pub fn to_pdf(document: &Document) -> Result<Vec<u8>, PdfError> {
54    let mut writer = Writer::new()?;
55    let contents: Vec<(Vec<u8>, &Page)> =
56        document.pages.iter().map(|page| (writer.page_content(page), page)).collect();
57    writer.finish(document, contents)
58}
59
60/// Text is shaped at this size, so positions come out in PDF's text space units.
61const UNITS: f32 = 1000.0;
62
63/// A shaped line of text, in thousandths of the font size.
64struct Shaped {
65    glyphs: Vec<ShapedGlyph>,
66}
67
68struct ShapedGlyph {
69    /// The glyph's index in the subset, which is also its CID.
70    cid: u16,
71    x: f32,
72}
73
74/// An embedded font and the glyphs used from it so far.
75struct Font {
76    data: &'static [u8],
77    bold: bool,
78    remapper: GlyphRemapper,
79    /// Original glyph of each subset glyph.
80    originals: Vec<u16>,
81    /// Width of each subset glyph, in thousandths of the font size.
82    widths: Vec<f32>,
83    /// The text each subset glyph stands for, when known.
84    unicode: Vec<Option<String>>,
85    /// Width of a glyph in the font, by original glyph index.
86    advances: Vec<f32>,
87}
88
89impl Font {
90    fn new(bold: bool) -> Result<Self, PdfError> {
91        let data = ravenrs_text::inter_font(bold);
92        let font = skrifa::FontRef::new(data).map_err(|e| PdfError::Font(e.to_string()))?;
93        let metrics = font.glyph_metrics(Size::new(UNITS), LocationRef::default());
94        let advances: Vec<f32> = (0..metrics.glyph_count())
95            .map(|g| metrics.advance_width(GlyphId::new(g)).unwrap_or(0.0))
96            .collect();
97        let notdef = advance(&advances, 0);
98        Ok(Self {
99            data,
100            bold,
101            remapper: GlyphRemapper::new(),
102            originals: vec![0],
103            widths: vec![notdef],
104            unicode: vec![None],
105            advances,
106        })
107    }
108
109    /// The CID of an original glyph, adding it to the subset the first time.
110    fn cid(&mut self, glyph: u16, text: &str) -> u16 {
111        let cid = self.remapper.remap(glyph);
112        if usize::from(cid) == self.originals.len() {
113            self.originals.push(glyph);
114            self.widths.push(advance(&self.advances, glyph));
115            self.unicode.push(None);
116        }
117        let slot = &mut self.unicode[usize::from(cid)];
118        if slot.is_none() && !text.is_empty() {
119            *slot = Some(text.to_owned());
120        }
121        cid
122    }
123}
124
125fn advance(advances: &[f32], glyph: u16) -> f32 {
126    advances.get(usize::from(glyph)).copied().unwrap_or(0.0)
127}
128
129struct Writer {
130    text: TextSystem,
131    fonts: [Font; 2],
132    shaped: HashMap<(bool, String), Rc<Shaped>>,
133    /// Opacities in use, as 0–255; each becomes a graphics state.
134    alphas: BTreeSet<u8>,
135    /// Images in use; a logo on every page is stored once. Position is the name's number.
136    images: Vec<DocumentImage>,
137    image_index: HashMap<u64, usize>,
138}
139
140impl Writer {
141    fn new() -> Result<Self, PdfError> {
142        Ok(Self {
143            text: TextSystem::new(),
144            fonts: [Font::new(false)?, Font::new(true)?],
145            shaped: HashMap::new(),
146            alphas: BTreeSet::new(),
147            images: Vec::new(),
148            image_index: HashMap::new(),
149        })
150    }
151
152    fn shape(&mut self, text: &str, bold: bool) -> Rc<Shaped> {
153        let key = (bold, text.to_owned());
154        if let Some(shaped) = self.shaped.get(&key) {
155            return shaped.clone();
156        }
157        let attrs = TextAttrs {
158            size: UNITS,
159            line_height: UNITS,
160            weight: if bold { 600.0 } else { 400.0 },
161            ..TextAttrs::default()
162        };
163        let mut layout = self.text.layout(text, &attrs);
164        layout.measure(None);
165        let font = &mut self.fonts[usize::from(bold)];
166        let glyphs = layout
167            .glyphs()
168            .into_iter()
169            .map(|g| {
170                let id = u16::try_from(g.id).unwrap_or(0);
171                ShapedGlyph { cid: font.cid(id, text.get(g.text).unwrap_or_default()), x: g.x }
172            })
173            .collect();
174        let shaped = Rc::new(Shaped { glyphs });
175        self.shaped.insert(key, shaped.clone());
176        shaped
177    }
178
179    /// The drawing commands of a page, compressed.
180    fn page_content(&mut self, page: &Page) -> Vec<u8> {
181        let mut content = Content::new();
182        let h = page.height;
183        for item in &page.items {
184            match item {
185                Item::Rect { rect, fill, stroke, radius } => {
186                    self.draw(&mut content, *fill, *stroke, |c| {
187                        write_path(c, &vector::rectangle(*rect, *radius), h)
188                    });
189                }
190                Item::Ellipse { rect, fill, stroke } => {
191                    self.draw(&mut content, *fill, *stroke, |c| write_path(c, &vector::ellipse(*rect), h));
192                }
193                Item::Line { from, to, width, color } => {
194                    let alpha = self.begin_alpha(&mut content, *color);
195                    content.set_stroke_rgb(color.r, color.g, color.b);
196                    content.set_line_width(*width);
197                    content.move_to(from.x, h - from.y);
198                    content.line_to(to.x, h - to.y);
199                    content.stroke();
200                    end_alpha(&mut content, alpha);
201                }
202                Item::Text(block) => self.draw_text(&mut content, block, h),
203                Item::Image { rect, clip, image } => {
204                    let index = self.image_number(image);
205                    content.save_state();
206                    content.rect(clip.x(), h - clip.max_y(), clip.width(), clip.height());
207                    content.clip_nonzero();
208                    content.end_path();
209                    content.transform([rect.width(), 0.0, 0.0, rect.height(), rect.x(), h - rect.max_y()]);
210                    content.x_object(Name(image_name(index).as_bytes()));
211                    content.restore_state();
212                }
213                Item::Vector { shapes, .. } => {
214                    for shape in shapes {
215                        self.draw(&mut content, shape.fill, shape.stroke, |c| {
216                            write_path(c, &shape.path, h);
217                        });
218                    }
219                }
220                // Links and bookmarks print nothing; they are written with the page.
221                _ => {}
222            }
223        }
224        compress_to_vec_zlib(&content.finish(), 6)
225    }
226
227    fn image_number(&mut self, image: &DocumentImage) -> usize {
228        let images = &mut self.images;
229        *self.image_index.entry(image.pixels().id()).or_insert_with(|| {
230            images.push(image.clone());
231            images.len() - 1
232        })
233    }
234
235    fn draw(
236        &mut self,
237        content: &mut Content,
238        fill: Option<Color>,
239        stroke: Option<(f32, Color)>,
240        path: impl Fn(&mut Content),
241    ) {
242        if let Some(fill) = fill.filter(|c| c.a > 0.0) {
243            let alpha = self.begin_alpha(content, fill);
244            content.set_fill_rgb(fill.r, fill.g, fill.b);
245            path(content);
246            content.fill_nonzero();
247            end_alpha(content, alpha);
248        }
249        if let Some((width, color)) = stroke.filter(|(w, c)| *w > 0.0 && c.a > 0.0) {
250            let alpha = self.begin_alpha(content, color);
251            content.set_stroke_rgb(color.r, color.g, color.b);
252            content.set_line_width(width);
253            path(content);
254            content.stroke();
255            end_alpha(content, alpha);
256        }
257    }
258
259    /// Opens a graphics state for a translucent color; returns whether it did.
260    fn begin_alpha(&mut self, content: &mut Content, color: Color) -> bool {
261        let alpha = (color.a.clamp(0.0, 1.0) * 255.0).round() as u8;
262        if alpha == 255 {
263            return false;
264        }
265        self.alphas.insert(alpha);
266        content.save_state();
267        content.set_parameters(Name(alpha_name(alpha).as_bytes()));
268        true
269    }
270
271    fn draw_text(&mut self, content: &mut Content, block: &TextBlock, h: f32) {
272        // Viewers clip text to its box; clipping only when needed keeps the file small.
273        let c = block.clip;
274        let overflows = block.lines.iter().any(|l| {
275            l.x < c.x() - 0.5
276                || l.x + l.width > c.max_x() + 0.5
277                || l.top < c.y() - 0.5
278                || l.baseline + block.size * 0.3 > c.max_y() + 0.5
279        });
280        let alpha = self.begin_alpha(content, block.color);
281        if overflows {
282            content.save_state();
283            content.rect(c.x(), h - c.max_y(), c.width(), c.height());
284            content.clip_nonzero();
285            content.end_path();
286        }
287        content.set_fill_rgb(block.color.r, block.color.g, block.color.b);
288        content.begin_text();
289        content.set_font(Name(font_name(block.bold).as_bytes()), block.size);
290        let mut encoded = Vec::new();
291        for line in &block.lines {
292            let shaped = self.shape(&line.text, block.bold);
293            if shaped.glyphs.is_empty() {
294                continue;
295            }
296            content.set_text_matrix([1.0, 0.0, 0.0, 1.0, line.x, h - line.baseline]);
297            let widths = &self.fonts[usize::from(block.bold)].widths;
298            let mut show = content.show_positioned();
299            let mut items = show.items();
300            let mut pen = 0.0;
301            for glyph in &shaped.glyphs {
302                // Kerning and shaping move a glyph away from where the font's width would put it.
303                if (glyph.x - pen).abs() > 0.01 {
304                    if !encoded.is_empty() {
305                        items.show(Str(&encoded));
306                        encoded.clear();
307                    }
308                    items.adjust(pen - glyph.x);
309                    pen = glyph.x;
310                }
311                encoded.extend_from_slice(&glyph.cid.to_be_bytes());
312                pen += widths[usize::from(glyph.cid)];
313            }
314            if !encoded.is_empty() {
315                items.show(Str(&encoded));
316                encoded.clear();
317            }
318            items.finish();
319            show.finish();
320        }
321        content.end_text();
322        if overflows {
323            content.restore_state();
324        }
325        end_alpha(content, alpha);
326    }
327
328    fn finish(self, document: &Document, contents: Vec<(Vec<u8>, &Page)>) -> Result<Vec<u8>, PdfError> {
329        let mut next = 1;
330        let mut alloc = || {
331            let id = Ref::new(next);
332            next += 1;
333            id
334        };
335        let (catalog, tree, resources, info) = (alloc(), alloc(), alloc(), alloc());
336        let page_ids: Vec<(Ref, Ref)> = contents.iter().map(|_| (alloc(), alloc())).collect();
337        let mut pdf = Pdf::new();
338        let outline = write_outline(&mut pdf, &contents, &page_ids, &mut alloc);
339        let mut root = pdf.catalog(catalog);
340        root.pages(tree);
341        if let Some(outline) = outline {
342            root.outlines(outline);
343            root.page_mode(PageMode::UseOutlines);
344        }
345        root.finish();
346        let mut about = pdf.document_info(info);
347        about.title(TextStr(&document.title));
348        about.producer(TextStr("RavenRS Reporting"));
349        about.finish();
350
351        let font_ids: Vec<Ref> =
352            self.fonts.iter().map(|font| write_font(&mut pdf, font, &mut alloc)).collect::<Result<_, _>>()?;
353
354        let alpha_ids: Vec<(u8, Ref)> = self.alphas.iter().map(|a| (*a, alloc())).collect();
355        for (alpha, id) in &alpha_ids {
356            let value = f32::from(*alpha) / 255.0;
357            pdf.ext_graphics(*id).non_stroking_alpha(value).stroking_alpha(value);
358        }
359        let image_ids: Vec<Ref> =
360            self.images.iter().map(|image| write_image(&mut pdf, image, &mut alloc)).collect();
361        let mut res = pdf.indirect(resources).start::<Resources>();
362        let mut fonts = res.fonts();
363        for (bold, id) in [false, true].into_iter().zip(&font_ids) {
364            fonts.pair(Name(font_name(bold).as_bytes()), *id);
365        }
366        fonts.finish();
367        let mut states = res.ext_g_states();
368        for (alpha, id) in &alpha_ids {
369            states.pair(Name(alpha_name(*alpha).as_bytes()), *id);
370        }
371        states.finish();
372        let mut x_objects = res.x_objects();
373        for (index, id) in image_ids.iter().enumerate() {
374            x_objects.pair(Name(image_name(index).as_bytes()), *id);
375        }
376        x_objects.finish();
377        res.finish();
378
379        let mut pages = pdf.pages(tree);
380        pages.kids(page_ids.iter().map(|(page, _)| *page));
381        pages.count(i32::try_from(page_ids.len()).unwrap_or(i32::MAX));
382        pages.pair(Name(b"Resources"), resources);
383        pages.finish();
384        for ((data, page), (id, content_id)) in contents.iter().zip(&page_ids) {
385            let mut out = pdf.page(*id);
386            out.parent(tree);
387            out.media_box(PdfRect::new(0.0, 0.0, page.width, page.height));
388            out.contents(*content_id);
389            let links: Vec<(&Rect, &str)> = page
390                .items
391                .iter()
392                .filter_map(|item| match item {
393                    Item::Link { rect, url } => Some((rect, url.as_str())),
394                    _ => None,
395                })
396                .collect();
397            let annotation_ids: Vec<Ref> = links.iter().map(|_| alloc()).collect();
398            if !annotation_ids.is_empty() {
399                out.annotations(annotation_ids.iter().copied());
400            }
401            out.finish();
402            pdf.stream(*content_id, data).filter(Filter::FlateDecode);
403            for ((rect, url), annotation_id) in links.iter().zip(&annotation_ids) {
404                let mut annotation = pdf.annotation(*annotation_id);
405                annotation.subtype(AnnotationType::Link);
406                annotation.rect(PdfRect::new(
407                    rect.x(),
408                    page.height - rect.max_y(),
409                    rect.max_x(),
410                    page.height - rect.y(),
411                ));
412                annotation.border(0.0, 0.0, 0.0, None);
413                annotation.action().action_type(ActionType::Uri).uri(Str(url.as_bytes()));
414            }
415        }
416        Ok(pdf.finish())
417    }
418}
419
420fn end_alpha(content: &mut Content, opened: bool) {
421    if opened {
422        content.restore_state();
423    }
424}
425
426fn font_name(bold: bool) -> &'static str {
427    if bold { "F1" } else { "F0" }
428}
429
430fn alpha_name(alpha: u8) -> String {
431    format!("A{alpha}")
432}
433
434fn image_name(index: usize) -> String {
435    format!("I{index}")
436}
437
438/// Embeds an image. A JPEG goes in as the original file when PDF can show it
439/// directly, so a photo keeps its quality and size; anything else as pixels,
440/// with transparency as a separate mask.
441fn write_image(pdf: &mut Pdf, image: &DocumentImage, alloc: &mut impl FnMut() -> Ref) -> Ref {
442    let id = alloc();
443    let pixels = image.pixels();
444    let (width, height) = (pixels.width() as i32, pixels.height() as i32);
445    if image.format() == ImageFormat::Jpeg && matches!(image.channels(), 1 | 3) {
446        let mut out = pdf.image_xobject(id, image.encoded());
447        out.filter(Filter::DctDecode);
448        out.width(width);
449        out.height(height);
450        if image.channels() == 1 {
451            out.color_space().device_gray();
452        } else {
453            out.color_space().device_rgb();
454        }
455        out.bits_per_component(8);
456        return id;
457    }
458    let (rgba, _) = pixels.pixels().as_chunks::<4>();
459    let rgb: Vec<u8> = rgba.iter().flat_map(|p| [p[0], p[1], p[2]]).collect();
460    let mask = rgba.iter().any(|p| p[3] != 255).then(|| {
461        let alpha: Vec<u8> = rgba.iter().map(|p| p[3]).collect();
462        let mask_id = alloc();
463        let compressed = compress_to_vec_zlib(&alpha, 6);
464        let mut out = pdf.image_xobject(mask_id, &compressed);
465        out.filter(Filter::FlateDecode);
466        out.width(width);
467        out.height(height);
468        out.color_space().device_gray();
469        out.bits_per_component(8);
470        out.finish();
471        mask_id
472    });
473    let compressed = compress_to_vec_zlib(&rgb, 6);
474    let mut out = pdf.image_xobject(id, &compressed);
475    out.filter(Filter::FlateDecode);
476    out.width(width);
477    out.height(height);
478    out.color_space().device_rgb();
479    out.bits_per_component(8);
480    if let Some(mask) = mask {
481        out.s_mask(mask);
482    }
483    id
484}
485
486/// A bookmark in the outline tree.
487struct OutlineNode<'d> {
488    id: Ref,
489    title: &'d str,
490    page: Ref,
491    top: f32,
492    parent: Option<usize>,
493    children: Vec<usize>,
494    descendants: i32,
495}
496
497/// Writes the outline from the pages' bookmarks; `None` when there are none.
498fn write_outline(
499    pdf: &mut Pdf,
500    contents: &[(Vec<u8>, &Page)],
501    page_ids: &[(Ref, Ref)],
502    alloc: &mut impl FnMut() -> Ref,
503) -> Option<Ref> {
504    let mut nodes: Vec<OutlineNode<'_>> = Vec::new();
505    let mut roots = Vec::new();
506    // The open path: the level and node of each ancestor of what comes next.
507    let mut path: Vec<(u32, usize)> = Vec::new();
508    for ((_, page), (page_id, _)) in contents.iter().zip(page_ids) {
509        for item in &page.items {
510            let Item::Bookmark { title, level, y } = item else { continue };
511            // The parent is the latest entry at a lesser level, so a level that skips one still fits.
512            while path.last().is_some_and(|(open, _)| open >= level) {
513                path.pop();
514            }
515            let index = nodes.len();
516            let parent = path.last().map(|(_, node)| *node);
517            nodes.push(OutlineNode {
518                id: alloc(),
519                title,
520                page: *page_id,
521                top: page.height - y,
522                parent,
523                children: Vec::new(),
524                descendants: 0,
525            });
526            match parent {
527                Some(parent) => nodes[parent].children.push(index),
528                None => roots.push(index),
529            }
530            path.push((*level, index));
531        }
532    }
533    if nodes.is_empty() {
534        return None;
535    }
536    // Children come after their parents, so going backwards adds them up.
537    for index in (0..nodes.len()).rev() {
538        if let Some(parent) = nodes[index].parent {
539            nodes[parent].descendants += nodes[index].descendants + 1;
540        }
541    }
542    let root = alloc();
543    let mut outline = pdf.outline(root);
544    outline.first(nodes[roots[0]].id);
545    outline.last(nodes[roots[roots.len() - 1]].id);
546    outline.count(nodes.len() as i32);
547    outline.finish();
548    for (index, node) in nodes.iter().enumerate() {
549        let siblings = node.parent.map_or(&roots, |parent| &nodes[parent].children);
550        let at = siblings.iter().position(|sibling| *sibling == index).unwrap_or(0);
551        let mut item = pdf.outline_item(node.id);
552        item.title(TextStr(node.title));
553        item.parent(node.parent.map_or(root, |parent| nodes[parent].id));
554        if at > 0 {
555            item.prev(nodes[siblings[at - 1]].id);
556        }
557        if let Some(next) = siblings.get(at + 1) {
558            item.next(nodes[*next].id);
559        }
560        if let (Some(first), Some(last)) = (node.children.first(), node.children.last()) {
561            item.first(nodes[*first].id);
562            item.last(nodes[*last].id);
563            item.count(node.descendants);
564        }
565        item.dest().page(node.page).xyz(0.0, node.top, None);
566    }
567    Some(root)
568}
569
570/// Embeds a font's subset; returns the reference pages use.
571fn write_font(pdf: &mut Pdf, font: &Font, alloc: &mut impl FnMut() -> Ref) -> Result<Ref, PdfError> {
572    let (type0, cid, descriptor, file, cmap) = (alloc(), alloc(), alloc(), alloc(), alloc());
573    let name =
574        format!("{}+Inter-{}", subset_tag(&font.originals), if font.bold { "SemiBold" } else { "Regular" });
575    let name = Name(name.as_bytes());
576
577    let subset =
578        subsetter::subset(font.data, 0, &font.remapper).map_err(|e| PdfError::Font(e.to_string()))?;
579    let compressed = compress_to_vec_zlib(&subset, 6);
580    pdf.stream(file, &compressed).filter(Filter::FlateDecode).pair(Name(b"Length1"), subset.len() as i32);
581
582    let reader = skrifa::FontRef::new(font.data).map_err(|e| PdfError::Font(e.to_string()))?;
583    let metrics = reader.metrics(Size::new(UNITS), LocationRef::default());
584    let bbox = metrics.bounds.map_or(PdfRect::new(0.0, -300.0, 1000.0, 1000.0), |b| {
585        PdfRect::new(b.x_min, b.y_min, b.x_max, b.y_max)
586    });
587    let mut desc = pdf.font_descriptor(descriptor);
588    desc.name(name);
589    desc.flags(FontFlags::NON_SYMBOLIC);
590    desc.bbox(bbox);
591    desc.italic_angle(0.0);
592    desc.ascent(metrics.ascent);
593    desc.descent(metrics.descent);
594    desc.cap_height(metrics.cap_height.unwrap_or(metrics.ascent));
595    desc.stem_v(if font.bold { 120.0 } else { 80.0 });
596    desc.font_file2(file);
597    desc.finish();
598
599    let system = SystemInfo { registry: Str(b"Adobe"), ordering: Str(b"Identity"), supplement: 0 };
600    let mut cid_font = pdf.cid_font(cid);
601    cid_font.subtype(CidFontType::Type2);
602    cid_font.base_font(name);
603    cid_font.system_info(system);
604    cid_font.font_descriptor(descriptor);
605    cid_font.cid_to_gid_map_predefined(Name(b"Identity"));
606    let mut widths = cid_font.widths();
607    widths.consecutive(0, font.widths.iter().copied());
608    widths.finish();
609    cid_font.finish();
610
611    let mut to_unicode = UnicodeCmap::<u16>::new(Name(b"Custom"), system);
612    for (cid, text) in font.unicode.iter().enumerate() {
613        if let (Some(text), Ok(cid)) = (text, u16::try_from(cid)) {
614            to_unicode.pair_with_multiple(cid, text.chars());
615        }
616    }
617    let unicode = compress_to_vec_zlib(&to_unicode.finish(), 6);
618    pdf.cmap(cmap, &unicode).filter(Filter::FlateDecode);
619
620    let mut out = pdf.type0_font(type0);
621    out.base_font(name);
622    out.encoding_predefined(Name(b"Identity-H"));
623    out.descendant_font(cid);
624    out.to_unicode(cmap);
625    out.finish();
626    Ok(type0)
627}
628
629/// The six capital letters PDF requires in front of a subset font's name.
630fn subset_tag(glyphs: &[u16]) -> String {
631    let mut hash = glyphs.iter().fold(2_166_136_261_u32, |h, g| (h ^ u32::from(*g)).wrapping_mul(16_777_619));
632    (0..6)
633        .map(|_| {
634            let letter = char::from(b'A' + (hash % 26) as u8);
635            hash /= 26;
636            letter
637        })
638        .collect()
639}
640
641/// A shape's outline, with `y` measured from the top of a page of height `h`.
642fn write_path(content: &mut Content, path: &[PathSegment], h: f32) {
643    for segment in path {
644        match segment {
645            PathSegment::Move(p) => {
646                content.move_to(p.x, h - p.y);
647            }
648            PathSegment::Line(p) => {
649                content.line_to(p.x, h - p.y);
650            }
651            PathSegment::Cubic(a, b, c) => {
652                content.cubic_to(a.x, h - a.y, b.x, h - b.y, c.x, h - c.y);
653            }
654            PathSegment::Close => {
655                content.close_path();
656            }
657            _ => {}
658        }
659    }
660}