vl_convert_pdf/
lib.rs

1use anyhow::{bail, Error as AnyError};
2use pdf_writer::{Content, Filter, Finish, Name, Pdf, Rect, Ref, Str, TextStr};
3
4use itertools::Itertools;
5use pdf_writer::types::{CidFontType, FontFlags, SystemInfo, UnicodeCmap};
6use siphasher::sip128::{Hasher128, SipHasher13};
7use std::collections::{BTreeMap, HashMap, HashSet};
8use std::fs;
9use std::hash::Hash;
10use ttf_parser::{GlyphId, Tag};
11use unicode_bidi::BidiInfo;
12use usvg::fontdb::{Database, Family, Query, Source, Stretch, Style, Weight};
13use usvg::{
14    Font, FontStretch, FontStyle, Node, NodeExt, NodeKind, Opacity, Paint, Text, TextAnchor,
15    TextToPath, Tree,
16};
17
18const CFF: Tag = Tag::from_bytes(b"CFF ");
19const SYSTEM_INFO: SystemInfo = SystemInfo {
20    registry: Str(b"Adobe"),
21    ordering: Str(b"Identity"),
22    supplement: 0,
23};
24const CMAP_NAME: Name = Name(b"Custom");
25
26/// Convert a usvg::Tree into the bytes for a standalone PDF document
27/// This function uses svg2pdf to perform non-text conversion and then overlays embedded
28/// text on top.
29pub fn svg_to_pdf(tree: &Tree, font_db: &Database, scale: f32) -> Result<Vec<u8>, AnyError> {
30    // Extract SVGs size. We'll use this as the size of the resulting PDF document
31    let width = tree.size.width();
32    let height = tree.size.height();
33
34    let font_chars = collect_font_to_chars_mapping(tree)?;
35
36    let mut ctx = PdfContext::new(width, height, scale);
37
38    // Create mapping from usvg::Font to FontMetrics, which contains the info needed to
39    // build the embedded PDF font. Sort by Debug representation of Font for deterministic
40    // ordering
41    let mut font_metrics = HashMap::new();
42    for (font, chars) in font_chars.iter().sorted_by_key(|(f, _)| format!("{f:?}")) {
43        font_metrics.insert(
44            font.clone(),
45            compute_font_metrics(&mut ctx, font, chars, font_db)?,
46        );
47    }
48
49    // Need to update svg_id to be last id before calling svg2pdf because it will allocate more ids
50    ctx.svg_id = ctx.alloc.bump();
51    construct_page(&mut ctx, &font_metrics);
52    write_svg(&mut ctx, tree);
53    write_fonts(&mut ctx, &font_metrics)?;
54    write_content(&mut ctx, tree, &font_metrics, font_db)?;
55    Ok(ctx.writer.finish())
56}
57
58/// Additional methods for [`Ref`].
59trait RefExt {
60    /// Bump the reference up by one and return the previous one.
61    fn bump(&mut self) -> Self;
62}
63
64impl RefExt for Ref {
65    fn bump(&mut self) -> Self {
66        let prev = *self;
67        *self = Self::new(prev.get() + 1);
68        prev
69    }
70}
71
72struct PdfContext {
73    writer: Pdf,
74    width: f32,
75    height: f32,
76    scale: f32,
77    alloc: Ref,
78    info_id: Ref,
79    catalog_id: Ref,
80    page_tree_id: Ref,
81    page_id: Ref,
82    content_id: Ref,
83    svg_id: Ref,
84    svg_name: Vec<u8>,
85    next_font_name_index: usize,
86}
87
88impl PdfContext {
89    fn new(width: f32, height: f32, scale: f32) -> Self {
90        let mut alloc = Ref::new(1);
91        let info_id = alloc.bump();
92        let catalog_id = alloc.bump();
93        let page_tree_id = alloc.bump();
94        let page_id = alloc.bump();
95        let content_id = alloc.bump();
96
97        // svg_id will be replaced later because it must be the last id before calling svg2pdf
98        let svg_id = Ref::new(1);
99
100        Self {
101            writer: Pdf::new(),
102            width,
103            height,
104            scale,
105            alloc,
106            info_id,
107            catalog_id,
108            page_tree_id,
109            page_id,
110            content_id,
111            svg_id,
112            svg_name: Vec::from(b"S1".as_slice()),
113            next_font_name_index: 1,
114        }
115    }
116
117    fn next_font_name(&mut self) -> String {
118        let name = format!("F{}", self.next_font_name_index);
119        self.next_font_name_index += 1;
120        name
121    }
122}
123
124/// Construct a single PDF page (with required parents)
125fn construct_page(ctx: &mut PdfContext, font_metrics: &HashMap<Font, FontMetrics>) {
126    let mut info = ctx.writer.document_info(ctx.info_id);
127    info.creator(TextStr("VlConvert"));
128    info.finish();
129
130    ctx.writer.catalog(ctx.catalog_id).pages(ctx.page_tree_id);
131    ctx.writer
132        .pages(ctx.page_tree_id)
133        .kids([ctx.page_id])
134        .count(1);
135
136    // Initialize page with size matching the SVG image
137    let mut page = ctx.writer.page(ctx.page_id);
138    page.media_box(Rect::new(
139        0.0,
140        0.0,
141        ctx.width * ctx.scale,
142        ctx.height * ctx.scale,
143    ));
144    page.parent(ctx.page_tree_id);
145    page.contents(ctx.content_id);
146
147    let mut resources = page.resources();
148    // SVG
149    resources
150        .x_objects()
151        .pair(Name(ctx.svg_name.as_slice()), ctx.svg_id);
152
153    // Fonts
154    let mut resource_fonts = resources.fonts();
155    for mapped_font in font_metrics.values().sorted_by_key(|f| f.font_ref) {
156        resource_fonts.pair(
157            Name(mapped_font.font_ref_name.as_slice()),
158            mapped_font.font_ref,
159        );
160    }
161    resource_fonts.finish();
162    resources.finish();
163
164    // Finish page configuration
165    page.finish();
166}
167
168/// Write the SVG to a PDF XObject using svg2pdf.
169/// Note that svg2pdf currently ignores Text nodes, which is why we handle text
170/// separately
171fn write_svg(ctx: &mut PdfContext, tree: &Tree) {
172    ctx.alloc = svg2pdf::convert_tree_into(
173        tree,
174        svg2pdf::Options::default(),
175        &mut ctx.writer,
176        ctx.svg_id,
177    );
178}
179
180/// Write fonts to PDF resources
181fn write_fonts(
182    ctx: &mut PdfContext,
183    font_metrics: &HashMap<Font, FontMetrics>,
184) -> Result<(), AnyError> {
185    for font_specs in font_metrics.values().sorted_by_key(|f| f.font_ref) {
186        let cid_ref = ctx.alloc.bump();
187        let descriptor_ref = ctx.alloc.bump();
188        let cmap_ref = ctx.alloc.bump();
189        let data_ref = ctx.alloc.bump();
190        let is_cff = font_specs.is_cff;
191
192        ctx.writer
193            .type0_font(font_specs.font_ref)
194            .base_font(Name(font_specs.base_font_type0.as_bytes()))
195            .encoding_predefined(Name(b"Identity-H"))
196            .descendant_font(cid_ref)
197            .to_unicode(cmap_ref);
198
199        // Write the CID font referencing the font descriptor.
200        let mut cid = ctx.writer.cid_font(cid_ref);
201        cid.subtype(CidFontType::Type2);
202        cid.subtype(if is_cff {
203            CidFontType::Type0
204        } else {
205            CidFontType::Type2
206        });
207        cid.base_font(Name(font_specs.base_font.as_bytes()));
208        cid.system_info(SYSTEM_INFO);
209        cid.font_descriptor(descriptor_ref);
210        cid.default_width(0.0);
211        if !is_cff {
212            cid.cid_to_gid_map_predefined(Name(b"Identity"));
213        }
214
215        // Write all non-zero glyph widths.
216        let mut width_writer = cid.widths();
217        for (i, w) in font_specs.widths.iter().enumerate().skip(1) {
218            if *w != 0.0 {
219                width_writer.same(i as u16, i as u16, *w);
220            }
221        }
222
223        width_writer.finish();
224        cid.finish();
225
226        // Write the font descriptor (contains metrics about the font).
227        let mut font_descriptor = ctx.writer.font_descriptor(descriptor_ref);
228        font_descriptor
229            .name(Name(font_specs.base_font.as_bytes()))
230            .flags(font_specs.flags)
231            .bbox(font_specs.bbox)
232            .italic_angle(font_specs.italic_angle)
233            .ascent(font_specs.ascender)
234            .descent(font_specs.descender)
235            .cap_height(font_specs.cap_height)
236            .stem_v(font_specs.stem_v);
237
238        if is_cff {
239            font_descriptor.font_file3(data_ref);
240        } else {
241            font_descriptor.font_file2(data_ref);
242        }
243        font_descriptor.finish();
244
245        // Write the /ToUnicode character map, which maps character ids back to
246        // unicode codepoints to enable copying out of the PDF.
247        let cmap = create_cmap(&font_specs.cid_set);
248        ctx.writer.cmap(cmap_ref, &cmap.finish());
249
250        let glyphs: Vec<_> = font_specs.glyph_set.keys().copied().collect();
251        let profile = subsetter::Profile::pdf(&glyphs);
252        let subsetted = subsetter::subset(&font_specs.font_data, font_specs.face_index, profile);
253        let subset_font_data = deflate(subsetted.as_deref().unwrap_or(&font_specs.font_data));
254
255        let mut stream = ctx.writer.stream(data_ref, &subset_font_data);
256        stream.filter(Filter::FlateDecode);
257        if is_cff {
258            stream.pair(Name(b"Subtype"), Name(b"CIDFontType0C"));
259        }
260        stream.finish();
261    }
262    Ok(())
263}
264
265fn write_content(
266    ctx: &mut PdfContext,
267    tree: &Tree,
268    font_mapping: &HashMap<Font, FontMetrics>,
269    font_db: &Database,
270) -> Result<(), AnyError> {
271    // Create a content stream with the SVG and text
272    let mut content = Content::new();
273
274    // Add reference to the SVG XObject
275    // It's re-scaled to the size of the document because convert_tree_into above
276    // scales it to 1.0 x 1.0
277    content
278        .save_state()
279        .transform([
280            ctx.width * ctx.scale,
281            0.0,
282            0.0,
283            ctx.height * ctx.scale,
284            0.0,
285            0.0,
286        ])
287        .x_object(Name(ctx.svg_name.as_slice()))
288        .restore_state();
289
290    // Add Text
291    content.save_state();
292
293    for node in tree.root.children() {
294        write_text(ctx, node, &mut content, font_db, font_mapping)?;
295    }
296
297    content.restore_state();
298
299    // Write the content stream
300    ctx.writer.stream(ctx.content_id, &content.finish());
301    Ok(())
302}
303
304fn write_text(
305    ctx: &PdfContext,
306    node: Node,
307    content: &mut Content,
308    font_db: &Database,
309    font_metrics: &HashMap<Font, FontMetrics>,
310) -> Result<(), AnyError> {
311    match *node.borrow() {
312        NodeKind::Text(ref text) if text.chunks.len() == 1 => {
313            let Some(text_width) = get_text_width(text, font_db) else {
314                bail!("Failed to calculate text bounding box")
315            };
316
317            let chunk = &text.chunks[0];
318            let x_offset = match chunk.anchor {
319                TextAnchor::Start => 0.0,
320                TextAnchor::Middle => -text_width / 2.0,
321                TextAnchor::End => -text_width,
322            };
323
324            // Compute chunk x/y
325            let chunk_x = chunk.x.unwrap_or(0.0) + x_offset as f32;
326            let chunk_y = -chunk.y.unwrap_or(0.0);
327
328            let tx = node.abs_transform();
329
330            content.save_state().transform([
331                tx.sx * ctx.scale,
332                tx.kx * ctx.scale,
333                tx.ky * ctx.scale,
334                tx.sy * ctx.scale,
335                tx.tx * ctx.scale,
336                (ctx.height - tx.ty) * ctx.scale,
337            ]);
338
339            // Start text
340            content.begin_text().next_line(chunk_x, chunk_y);
341
342            for span in &chunk.spans {
343                let font_size = span.font_size.get();
344
345                // Skip zero opacity text, and text without a fill
346                let span_opacity = span.fill.clone().unwrap_or_default().opacity;
347                if span.fill.is_none()
348                    || span_opacity == Opacity::ZERO
349                    || node_has_zero_opacity(&node)
350                {
351                    continue;
352                }
353
354                let Some(font_specs) = font_metrics.get(&span.font) else {
355                    bail!("Font metrics not found")
356                };
357
358                // Compute left-to-right ordering of characters
359                let mut span_text = chunk.text[span.start..span.end].to_string();
360                let bidi_info = BidiInfo::new(&span_text, None);
361                if bidi_info.paragraphs.len() == 1 {
362                    let para = &bidi_info.paragraphs[0];
363                    let line = para.range.clone();
364                    span_text = bidi_info.reorder_line(para, line).to_string();
365                }
366
367                // Encode 16-bit glyph index into two bytes
368                let mut encoded_text = Vec::new();
369                for ch in span_text.chars() {
370                    if let Some(g) = font_specs.char_set.get(&ch) {
371                        encoded_text.push((*g >> 8) as u8);
372                        encoded_text.push((*g & 0xff) as u8);
373                    }
374                }
375
376                // Extract fill color
377                let (fill_r, fill_g, fill_b) = match &span.fill {
378                    Some(fill) => {
379                        if let Paint::Color(color) = fill.paint {
380                            (
381                                color.red as f32 / 255.0,
382                                color.green as f32 / 255.0,
383                                color.blue as f32 / 255.0,
384                            )
385                        } else {
386                            // Use black for other pain modes
387                            (0.0, 0.0, 0.0)
388                        }
389                    }
390                    None => (0.0, 0.0, 0.0),
391                };
392
393                content
394                    .set_font(Name(font_specs.font_ref_name.as_slice()), font_size)
395                    .set_fill_rgb(fill_r, fill_g, fill_b)
396                    .show(Str(encoded_text.as_slice()));
397            }
398
399            content.end_text().restore_state();
400        }
401        NodeKind::Group(_) => {
402            for child in node.children() {
403                write_text(ctx, child, content, font_db, font_metrics)?;
404            }
405        }
406        _ => {}
407    }
408    Ok(())
409}
410
411/// Check if this node is a group node with zero opacity,
412/// or if it has an ancestor group node with zero opacity
413fn node_has_zero_opacity(node: &Node) -> bool {
414    if let NodeKind::Group(ref group) = *node.borrow() {
415        if group.opacity == Opacity::ZERO {
416            return true;
417        }
418    }
419    if let Some(parent) = &node.parent() {
420        node_has_zero_opacity(parent)
421    } else {
422        false
423    }
424}
425
426fn get_text_width(text: &Text, font_db: &Database) -> Option<f64> {
427    get_text_width_from_path(text.convert(font_db, Default::default())?)
428}
429
430fn get_text_width_from_path(node: Node) -> Option<f64> {
431    match *node.borrow() {
432        NodeKind::Group(_) => {
433            for child in node.children() {
434                if let Some(res) = get_text_width_from_path(child) {
435                    return Some(res);
436                }
437            }
438            None
439        }
440        NodeKind::Path(ref path) => {
441            // Use text_box width and bounding box height
442            path.text_bbox.map(|p| p.width() as f64)
443        }
444        _ => None,
445    }
446}
447
448/// Collect mapping from usvg::Font to Unicode characters in that font
449fn collect_font_to_chars_mapping(
450    tree: &Tree,
451) -> Result<HashMap<Font, HashSet<char>>, anyhow::Error> {
452    let mut fonts: HashMap<Font, HashSet<char>> = HashMap::new();
453    for node in tree.root.descendants() {
454        if let NodeKind::Text(ref text) = *node.borrow() {
455            match text.chunks.len() {
456                // Ignore zero chunk text
457                0 => {}
458                1 => {
459                    let chunk = &text.chunks[0];
460                    let chunk_text = chunk.text.as_str();
461                    for span in &chunk.spans {
462                        let span_text = &chunk_text[span.start..span.end];
463                        let font = &span.font;
464                        fonts
465                            .entry(font.clone())
466                            .or_default()
467                            .extend(span_text.chars());
468                    }
469                }
470                _ => bail!("multi-chunk text not supported"),
471            }
472        }
473    }
474    Ok(fonts)
475}
476
477struct FontMetrics {
478    font_ref: Ref,
479    font_ref_name: Vec<u8>,
480    font_data: Vec<u8>,
481    face_index: u32,
482    glyph_set: BTreeMap<u16, String>,
483    char_set: BTreeMap<char, u16>,
484    flags: FontFlags,
485    bbox: Rect,
486    widths: Vec<f32>,
487    italic_angle: f32,
488    ascender: f32,
489    descender: f32,
490    cap_height: f32,
491    stem_v: f32,
492    base_font: String,
493    is_cff: bool,
494    base_font_type0: String,
495    cid_set: BTreeMap<u16, String>,
496}
497
498/// Compute the font metrics and references required by PDF embedding for a usvg::Font
499fn compute_font_metrics(
500    ctx: &mut PdfContext,
501    font: &Font,
502    chars: &HashSet<char>,
503    font_db: &Database,
504) -> Result<FontMetrics, anyhow::Error> {
505    let families = font
506        .families
507        .iter()
508        .map(|family| match family.as_str() {
509            "serif" => Family::Serif,
510            "sans-serif" | "sans serif" => Family::SansSerif,
511            "monospace" => Family::Monospace,
512            "cursive" => Family::Cursive,
513            name => Family::Name(name),
514        })
515        .collect::<Vec<_>>();
516
517    let stretch = match font.stretch {
518        FontStretch::UltraCondensed => Stretch::UltraCondensed,
519        FontStretch::ExtraCondensed => Stretch::ExtraCondensed,
520        FontStretch::Condensed => Stretch::Condensed,
521        FontStretch::SemiCondensed => Stretch::SemiCondensed,
522        FontStretch::Normal => Stretch::Normal,
523        FontStretch::SemiExpanded => Stretch::SemiExpanded,
524        FontStretch::Expanded => Stretch::Expanded,
525        FontStretch::ExtraExpanded => Stretch::ExtraExpanded,
526        FontStretch::UltraExpanded => Stretch::UltraExpanded,
527    };
528
529    let style = match font.style {
530        FontStyle::Normal => Style::Normal,
531        FontStyle::Italic => Style::Italic,
532        FontStyle::Oblique => Style::Oblique,
533    };
534
535    let Some(font_id) = font_db.query(&Query {
536        families: &families,
537        weight: Weight(font.weight),
538        stretch,
539        style,
540    }) else {
541        bail!("Unable to find installed font matching {font:?}")
542    };
543
544    let Some(face) = font_db.face(font_id) else {
545        bail!("Unable to find installed font matching {font:?}")
546    };
547
548    let postscript_name = face.post_script_name.clone();
549
550    let font_data = match &face.source {
551        Source::Binary(d) => Vec::from(d.as_ref().as_ref()),
552        Source::File(f) => fs::read(f)?,
553        Source::SharedFile(_, d) => Vec::from(d.as_ref().as_ref()),
554    };
555
556    let ttf = ttf_parser::Face::parse(&font_data, face.index)?;
557
558    let is_cff = ttf.raw_face().table(CFF).is_some();
559
560    // Conversion function from ttf values in em to PDF's font units
561    let to_font_units = |v: f32| (v / ttf.units_per_em() as f32) * 1000.0;
562
563    // Font flags
564    let mut flags = FontFlags::empty();
565    flags.set(FontFlags::SERIF, postscript_name.contains("Serif"));
566    flags.set(FontFlags::FIXED_PITCH, ttf.is_monospaced());
567    flags.set(FontFlags::ITALIC, ttf.is_italic());
568    flags.insert(FontFlags::SYMBOLIC);
569    flags.insert(FontFlags::SMALL_CAP);
570
571    // bounding box
572    let global_bbox = ttf.global_bounding_box();
573    let bbox = Rect::new(
574        to_font_units(global_bbox.x_min.into()),
575        to_font_units(global_bbox.y_min.into()),
576        to_font_units(global_bbox.x_max.into()),
577        to_font_units(global_bbox.y_max.into()),
578    );
579
580    // Compute glyph set and chart set
581    let mut glyph_set: BTreeMap<u16, String> = BTreeMap::new();
582    let mut cid_set: BTreeMap<u16, String> = BTreeMap::new();
583    let mut char_set: BTreeMap<char, u16> = BTreeMap::new();
584    for ch in chars {
585        if let Some(g) = ttf.glyph_index(*ch) {
586            let cid = glyph_cid(&ttf, g.0);
587            glyph_set.entry(g.0).or_default().push(*ch);
588            cid_set.entry(cid).or_default().push(*ch);
589            char_set.insert(*ch, cid);
590        }
591    }
592
593    // Compute widths
594    let mut widths = vec![];
595    for gid in std::iter::once(0).chain(glyph_set.keys().copied()) {
596        let width = ttf.glyph_hor_advance(GlyphId(gid)).unwrap_or(0);
597        let units = to_font_units(width as f32);
598        let cid = glyph_cid(&ttf, gid);
599        if usize::from(cid) >= widths.len() {
600            widths.resize(usize::from(cid) + 1, 0.0);
601            widths[usize::from(cid)] = units;
602        }
603    }
604
605    // metrics
606    let italic_angle = ttf.italic_angle().unwrap_or(0.0);
607    let ascender = to_font_units(ttf.typographic_ascender().unwrap_or(ttf.ascender()).into());
608    let descender = to_font_units(
609        ttf.typographic_descender()
610            .unwrap_or(ttf.descender())
611            .into(),
612    );
613    let cap_height = to_font_units(ttf.capital_height().unwrap_or(ttf.ascender()).into());
614    let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0);
615
616    // Compute base_font name with subset tag
617    let subset_tag = subset_tag(&glyph_set);
618    let base_font = format!("{subset_tag}+{postscript_name}");
619    let base_font_type0 = if is_cff {
620        format!("{base_font}-Identity-H")
621    } else {
622        base_font.clone()
623    };
624
625    Ok(FontMetrics {
626        base_font,
627        base_font_type0,
628        is_cff,
629        font_ref: ctx.alloc.bump(),
630        font_ref_name: Vec::from(ctx.next_font_name().as_bytes()),
631        font_data,
632        face_index: face.index,
633        glyph_set,
634        cid_set,
635        char_set,
636        flags,
637        bbox,
638        widths,
639        italic_angle,
640        ascender,
641        descender,
642        cap_height,
643        stem_v,
644    })
645}
646
647/// Produce a unique 6 letter tag for a glyph set.
648fn subset_tag(glyphs: &BTreeMap<u16, String>) -> String {
649    const LEN: usize = 6;
650    const BASE: u128 = 26;
651    let mut hash = hash128(glyphs);
652    let mut letter = [b'A'; LEN];
653    for l in letter.iter_mut() {
654        *l = b'A' + (hash % BASE) as u8;
655        hash /= BASE;
656    }
657    std::str::from_utf8(&letter).unwrap().to_string()
658}
659
660/// Calculate a 128-bit siphash of a value.
661fn hash128<T: Hash + ?Sized>(value: &T) -> u128 {
662    let mut state = SipHasher13::new();
663    value.hash(&mut state);
664    state.finish128().as_u128()
665}
666
667/// Create a /ToUnicode CMap.
668fn create_cmap(cid_set: &BTreeMap<u16, String>) -> UnicodeCmap {
669    // Produce a reverse mapping from glyphs to unicode strings.
670    let mut cmap = UnicodeCmap::new(CMAP_NAME, SYSTEM_INFO);
671    for (&g, text) in cid_set.iter() {
672        if !text.is_empty() {
673            cmap.pair_with_multiple(g, text.chars());
674        }
675    }
676
677    cmap
678}
679
680fn deflate(data: &[u8]) -> Vec<u8> {
681    const COMPRESSION_LEVEL: u8 = 6;
682    miniz_oxide::deflate::compress_to_vec_zlib(data, COMPRESSION_LEVEL)
683}
684
685/// Get the CID for a glyph id.
686///
687/// jonmmease: function and docstring taken from Typst
688///
689/// When writing text into a PDF, we have to specify CIDs (character ids) not
690/// GIDs (glyph IDs).
691///
692/// Most of the time, the mapping between these two is an identity mapping. In
693/// particular, for TrueType fonts, the mapping is an identity mapping because
694/// of this line above:
695/// ```ignore
696/// cid.cid_to_gid_map_predefined(Name(b"Identity"));
697/// ```
698///
699/// However, CID-keyed CFF fonts may have a non-identity mapping defined in
700/// their charset. For those, we must map the glyph IDs in a `TextItem` to CIDs.
701/// The font defines the map through its charset. The charset usually maps
702/// glyphs to SIDs (string ids) specifying the glyph's name. Not for CID-keyed
703/// fonts though! For these, the SIDs are CIDs in disguise. Relevant quote from
704/// the CFF spec:
705///
706/// > The charset data, although in the same format as non-CIDFonts, will
707/// > represent CIDs rather than SIDs, [...]
708///
709/// This function performs the mapping from glyph ID to CID. It also works for
710/// non CID-keyed fonts. Then, it will simply return the glyph ID.
711fn glyph_cid(ttf: &ttf_parser::Face, glyph_id: u16) -> u16 {
712    ttf.tables()
713        .cff
714        .and_then(|cff| cff.glyph_cid(ttf_parser::GlyphId(glyph_id)))
715        .unwrap_or(glyph_id)
716}