allsorts_subset_browser/
subset.rs

1#![deny(missing_docs)]
2
3//! Font subsetting.
4
5use std::collections::{BTreeMap, BTreeSet};
6use std::convert::TryFrom;
7use std::fmt;
8use std::num::Wrapping;
9
10use itertools::Itertools;
11
12use crate::binary::read::{ReadArrayCow, ReadScope};
13use crate::binary::write::{Placeholder, WriteBinary};
14use crate::binary::write::{WriteBinaryDep, WriteBuffer, WriteContext};
15use crate::binary::{long_align, U16Be, U32Be};
16use crate::cff::cff2::{OutputFormat, CFF2};
17use crate::cff::{CFFError, SubsetCFF, CFF};
18use crate::error::{ParseError, ReadWriteError, WriteError};
19use crate::post::PostTable;
20use crate::tables::cmap::subset::{CmapStrategy, CmapTarget, MappingsToKeep, NewIds, OldIds};
21use crate::tables::cmap::{owned, EncodingId, PlatformId};
22use crate::tables::glyf::GlyfTable;
23use crate::tables::loca::{self, LocaTable};
24use crate::tables::os2::Os2;
25use crate::tables::{
26    self, cmap, FontTableProvider, HeadTable, HheaTable, HmtxTable, IndexToLocFormat, MaxpTable,
27    TableRecord,
28};
29use crate::{checksum, tag};
30
31/// Profiles for controlling the tables included in subset fonts.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum SubsetProfile {
34    /// Minimal profile, suitable for PDF embedding (smallest file size).
35    Minimal,
36    /// OpenType profile, includes minimum tables for a valid standalone OpenType font.
37    Web,
38    /// Full profile, includes all relevant tables for a fully functional subset font.
39    Full,
40    /// Custom profile, allows specifying a list of tables to include.
41    Custom(Vec<u32>),
42}
43
44impl Default for SubsetProfile {
45    fn default() -> Self {
46        SubsetProfile::Minimal
47    }
48}
49
50/// Minimal set of tables for PDF embedding (current behaviour).
51const PROFILE_MINIMAL: &[u32] = &[]; // Define minimal table set if any
52
53/// Minimum tables required for a valid standalone OpenType font.
54const PROFILE_WEB: &[u32] = &[
55    tag::CMAP,
56    tag::HEAD,
57    tag::HHEA,
58    tag::HMTX,
59    tag::MAXP,
60    tag::NAME,
61    tag::OS_2,
62    tag::POST,
63];
64
65/// Full set of tables for a fully functional OpenType subset font (includes layout tables).
66const PROFILE_FULL: &[u32] = &[
67    tag::CMAP,
68    tag::HEAD,
69    tag::HHEA,
70    tag::HMTX,
71    tag::MAXP,
72    tag::NAME,
73    tag::OS_2,
74    tag::POST,
75    tag::GPOS, // Glyph Positioning
76    tag::GSUB, // Glyph Substitution
77    tag::VHEA, // Vertical Header
78    tag::VMTX, // Vertical Metrics
79    tag::GDEF, // Glyph Definition
80    tag::CVT,  // Control Value Table
81    tag::FPGM, // Font Program
82    tag::PREP, // Control Value Program
83];
84
85/// Constant array defining Unicode ranges and their corresponding bit index in the 128-bit mask.
86///
87/// Each tuple contains:
88/// - the start of the range (inclusive),
89/// - the end of the range (inclusive),
90/// - the bit index (i.e. which bit should be set).
91const UNICODE_RANGES: &[(u32, u32, u32)] = &[
92    (0x0000, 0x007F, 0),  // Basic Latin
93    (0x0080, 0x00FF, 1),  // Latin-1 Supplement
94    (0x0100, 0x017F, 2),  // Latin Extended-A
95    (0x0180, 0x024F, 3),  // Latin Extended-B
96    (0x0250, 0x02AF, 4),  // IPA Extensions
97    (0x02B0, 0x02FF, 5),  // Spacing Modifier Letters
98    (0x0300, 0x036F, 6),  // Combining Diacritical Marks
99    (0x0370, 0x03FF, 7),  // Greek and Coptic
100    (0x0400, 0x04FF, 9),  // Cyrillic
101    (0x0530, 0x058F, 10), // Armenian
102    (0x0590, 0x05FF, 11), // Hebrew
103    (0x0600, 0x06FF, 12), // Arabic
104    (0x0700, 0x074F, 13), // Syriac
105    (0x0750, 0x077F, 14), // Arabic Supplement
106    (0x0780, 0x07BF, 15), // Thaana
107    (0x07C0, 0x07FF, 16), // NKo
108    (0x0800, 0x083F, 17), // Samaritan
109    (0x0840, 0x085F, 18), // Mandaic
110    (0x0860, 0x086F, 19), // Syriac Supplement
111    (0x08A0, 0x08FF, 20), // Arabic Extended-A
112    (0x0900, 0x097F, 21), // Devanagari
113    (0x0980, 0x09FF, 22), // Bengali
114    (0x0A00, 0x0A7F, 23), // Gurmukhi
115    (0x0A80, 0x0AFF, 24), // Gujarati
116    (0x0B00, 0x0B7F, 25), // Oriya
117    (0x0B80, 0x0BFF, 26), // Tamil
118    (0x0C00, 0x0C7F, 27), // Telugu
119    (0x0C80, 0x0CFF, 28), // Kannada
120    (0x0D00, 0x0D7F, 29), // Malayalam
121    (0x0D80, 0x0DFF, 30), // Sinhala
122    (0x0E00, 0x0E7F, 31), // Thai
123    (0x0E80, 0x0EFF, 32), // Lao
124    (0x0F00, 0x0FFF, 33), // Tibetan
125    (0x1000, 0x109F, 34), // Myanmar
126    (0x10A0, 0x10FF, 35), // Georgian
127    (0x1100, 0x11FF, 36), // Hangul Jamo
128    (0x1E00, 0x1EFF, 37), // Latin Extended Additional
129    (0x1F00, 0x1FFF, 38), // Greek Extended
130];
131
132impl SubsetProfile {
133    /// Parses a custom subset profile from a string such as "gsub,vmtx,prep", includes the minimal tables automatically
134    pub fn parse_custom(s: &str) -> Self {
135        let mut tables = PROFILE_MINIMAL.to_vec();
136        let s = s
137            .split(",")
138            .flat_map(|s| s.split_whitespace().into_iter())
139            .collect::<BTreeSet<_>>();
140        for feature in s.iter() {
141            let newtag = match feature.to_lowercase().as_str() {
142                "cmap" => tag::CMAP,
143                "head" => tag::HEAD,
144                "hhea" => tag::HHEA,
145                "htmx" => tag::HMTX,
146                "maxp" => tag::MAXP,
147                "name" => tag::NAME,
148                "os/2" | "os2" | "os_2" => tag::OS_2,
149                "post" => tag::POST,
150                "gpos" => tag::GPOS,
151                "gsub" => tag::GSUB,
152                "vhea" => tag::VHEA,
153                "vtmx" => tag::VMTX,
154                "gdef" => tag::GDEF,
155                "cvt" => tag::CVT,
156                "fpgm" => tag::FPGM,
157                "prep" => tag::PREP,
158                _ => continue,
159            };
160            tables.push(newtag);
161        }
162        tables.sort();
163        tables.dedup();
164        Self::Custom(tables)
165    }
166
167    /// Returns the tables needed to subset for this profile.
168    fn get_tables(&self) -> BTreeSet<u32> {
169        match self {
170            SubsetProfile::Minimal => PROFILE_MINIMAL.iter().copied().collect(),
171            SubsetProfile::Web => PROFILE_WEB.iter().copied().collect(),
172            SubsetProfile::Full => PROFILE_FULL.iter().copied().collect(),
173            SubsetProfile::Custom(items) => items.iter().copied().collect(),
174        }
175    }
176}
177
178/// Error type returned from subsetting.
179#[derive(Debug)]
180pub enum SubsetError {
181    /// An error occurred reading or parsing data.
182    Parse(ParseError),
183    /// An error occurred serializing data.
184    Write(WriteError),
185    /// An error occurred when interpreting CFF CharStrings
186    CFF(CFFError),
187    /// The glyph subset did not include glyph 0/.notdef in the first position
188    NotDef,
189    /// The subset glyph count exceeded the maximum number of glyphs
190    TooManyGlyphs,
191    /// The CFF font did not contain a sole font, which is the only supported configuration for
192    /// subsetting
193    InvalidFontCount,
194}
195
196pub(crate) trait SubsetGlyphs {
197    /// The number of glyphs in this collection
198    fn len(&self) -> usize;
199
200    /// Return the old glyph id for the supplied new glyph id
201    fn old_id(&self, new_id: u16) -> u16;
202
203    /// Return the new glyph id for the supplied old glyph id
204    fn new_id(&self, old_id: u16) -> u16;
205}
206
207pub(crate) struct FontBuilder {
208    sfnt_version: u32,
209    tables: BTreeMap<u32, WriteBuffer>,
210}
211
212pub(crate) struct FontBuilderWithHead {
213    inner: FontBuilder,
214    check_sum_adjustment: Placeholder<U32Be, u32>,
215    index_to_loc_format: IndexToLocFormat,
216}
217
218struct TaggedBuffer {
219    tag: u32,
220    buffer: WriteBuffer,
221}
222
223struct OrderedTables {
224    tables: Vec<TaggedBuffer>,
225    checksum: Wrapping<u32>,
226}
227
228fn subset_os2(os2: &Os2, mappings: &MappingsToKeep<OldIds>) -> Result<Os2, SubsetError> {
229    // Calculate new first and last Unicode codepoints
230    let (new_first, new_last) = if mappings.is_empty() {
231        (0, 0) // No mappings, use 0 for both
232    } else {
233        mappings
234            .iter()
235            .fold((u32::MAX, 0_u32), |(min, max), (ch, _)| {
236                let code = ch.as_u32();
237                (min.min(code), max.max(code))
238            })
239    };
240
241    // Compute the new ulUnicodeRange bitmask
242    let new_unicode_mask: u128 = mappings
243        .iter()
244        .fold(0, |mask, (ch, _)| mask | unicode_range_mask(ch.as_u32()));
245
246    let new_ul_unicode_range1 = (new_unicode_mask & 0xFFFF_FFFF) as u32;
247    let new_ul_unicode_range2 = ((new_unicode_mask >> 32) & 0xFFFF_FFFF) as u32;
248    let new_ul_unicode_range3 = ((new_unicode_mask >> 64) & 0xFFFF_FFFF) as u32;
249    let new_ul_unicode_range4 = ((new_unicode_mask >> 96) & 0xFFFF_FFFF) as u32;
250
251    Ok(Os2 {
252        version: os2.version,
253        x_avg_char_width: os2.x_avg_char_width, // Ideally would be recalculated based on subset glyphs
254        us_weight_class: os2.us_weight_class,
255        us_width_class: os2.us_width_class,
256        fs_type: os2.fs_type,
257        y_subscript_x_size: os2.y_subscript_x_size,
258        y_subscript_y_size: os2.y_subscript_y_size,
259        y_subscript_x_offset: os2.y_subscript_x_offset,
260        y_subscript_y_offset: os2.y_subscript_y_offset,
261        y_superscript_x_size: os2.y_superscript_x_size,
262        y_superscript_y_size: os2.y_superscript_y_size,
263        y_superscript_x_offset: os2.y_superscript_x_offset,
264        y_superscript_y_offset: os2.y_superscript_y_offset,
265        y_strikeout_size: os2.y_strikeout_size,
266        y_strikeout_position: os2.y_strikeout_position,
267        s_family_class: os2.s_family_class,
268        panose: os2.panose,
269        ul_unicode_range1: new_ul_unicode_range1,
270        ul_unicode_range2: new_ul_unicode_range2,
271        ul_unicode_range3: new_ul_unicode_range3,
272        ul_unicode_range4: new_ul_unicode_range4,
273        ach_vend_id: os2.ach_vend_id,
274        fs_selection: os2.fs_selection,
275        us_first_char_index: new_first as u16,
276        us_last_char_index: new_last as u16,
277        version0: os2.version0.clone(),
278        version1: os2.version1.clone(),
279        version2to4: os2.version2to4.clone(),
280        version5: os2.version5.clone(),
281    })
282}
283
284/// Subset this font so that it only contains the glyphs with the supplied `glyph_ids`.
285///
286/// `glyph_ids` requirements:
287///
288/// * Glyph id 0, corresponding to the `.notdef` glyph must always be present.
289/// * There must be no duplicate glyph ids.
290///
291/// If either of these requirements are not upheld this function will return
292/// `ParseError::BadValue`.
293pub fn subset(
294    provider: &impl FontTableProvider,
295    glyph_ids: &[u16],
296    profile: &SubsetProfile,
297) -> Result<Vec<u8>, SubsetError> {
298    let mappings_to_keep = MappingsToKeep::new(provider, glyph_ids, CmapTarget::Unrestricted)?;
299    if provider.has_table(tag::CFF) {
300        subset_cff(provider, glyph_ids, mappings_to_keep, true, profile)
301    } else if provider.has_table(tag::CFF2) {
302        subset_cff2(
303            provider,
304            glyph_ids,
305            mappings_to_keep,
306            false,
307            OutputFormat::Type1OrCid,
308            profile,
309        )
310    } else {
311        subset_ttf(
312            provider,
313            glyph_ids,
314            CmapStrategy::Generate(mappings_to_keep),
315            profile,
316        )
317        .map_err(SubsetError::from)
318    }
319}
320
321/// Subset a TTF font.
322///
323/// If `mappings_to_keep` is `None` a `cmap` table in the subset font will be omitted.
324/// Otherwise it will be used to build a new `cmap` table.
325fn subset_ttf(
326    provider: &impl FontTableProvider,
327    glyph_ids: &[u16],
328    cmap_strategy: CmapStrategy,
329    profile: &SubsetProfile,
330) -> Result<Vec<u8>, ReadWriteError> {
331    // Get profile tables
332    let profile_tables = profile.get_tables();
333
334    // Get the OS/2 table if needed
335    let os2 = if profile_tables.contains(&tag::OS_2) {
336        match provider.read_table_data(tag::OS_2) {
337            Ok(data) => Some(ReadScope::new(&data).read_dep::<Os2>(data.len())?),
338            Err(_) => None,
339        }
340    } else {
341        None
342    };
343
344    // Subset the OS/2 table if we have one and mappings
345    let subset_os2 =
346        if let (Some(os2), CmapStrategy::Generate(ref mappings)) = (&os2, &cmap_strategy) {
347            match subset_os2(os2, mappings) {
348                Ok(table) => Some(table),
349                Err(e) => {
350                    // If subsetting fails, keep the original table
351                    println!("Warning: Failed to subset OS/2 table: {:?}", e);
352                    Some(os2.clone())
353                }
354            }
355        } else {
356            os2
357        };
358
359    let head = ReadScope::new(&provider.read_table_data(tag::HEAD)?).read::<HeadTable>()?;
360    let mut maxp = ReadScope::new(&provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
361    let loca_data = provider.read_table_data(tag::LOCA)?;
362    let loca = ReadScope::new(&loca_data)
363        .read_dep::<LocaTable<'_>>((usize::from(maxp.num_glyphs), head.index_to_loc_format))?;
364    let glyf_data = provider.read_table_data(tag::GLYF)?;
365    let glyf = ReadScope::new(&glyf_data).read_dep::<GlyfTable<'_>>(&loca)?;
366    let mut hhea = ReadScope::new(&provider.read_table_data(tag::HHEA)?).read::<HheaTable>()?;
367    let hmtx_data = provider.read_table_data(tag::HMTX)?;
368    let hmtx = ReadScope::new(&hmtx_data).read_dep::<HmtxTable<'_>>((
369        usize::from(maxp.num_glyphs),
370        usize::from(hhea.num_h_metrics),
371    ))?;
372
373    // Build a new post table with version set to 3, which does not contain any additional
374    // PostScript data
375    let post_data = provider.read_table_data(tag::POST)?;
376    let mut post = ReadScope::new(&post_data).read::<PostTable<'_>>()?;
377    post.header.version = 0x00030000; // version 3.0
378    post.opt_sub_table = None;
379
380    // Subset the glyphs
381    let subset_glyphs = glyf.subset(glyph_ids)?;
382
383    // Build a new cmap table
384    let cmap = match cmap_strategy {
385        CmapStrategy::Generate(mappings_to_keep) => {
386            let mappings_to_keep = mappings_to_keep.update_to_new_ids(&subset_glyphs);
387            Some(create_cmap_table(&mappings_to_keep)?)
388        }
389        CmapStrategy::MacRomanSupplied(cmap) => {
390            Some(create_cmap_table_from_cmap_array(glyph_ids, cmap)?)
391        }
392        CmapStrategy::Omit => None,
393    };
394
395    // Build new maxp table
396    let num_glyphs = u16::try_from(subset_glyphs.len()).map_err(ParseError::from)?;
397    maxp.num_glyphs = num_glyphs;
398
399    // Build new hhea table
400    let num_h_metrics = usize::from(hhea.num_h_metrics);
401    hhea.num_h_metrics = num_glyphs;
402
403    // Build new hmtx table
404    let hmtx = create_hmtx_table(&hmtx, num_h_metrics, &subset_glyphs)?;
405
406    // Extract the new glyf table now that we're done with subset_glyphs
407    let glyf = GlyfTable::from(subset_glyphs);
408
409    // Get the remaining tables
410    let cvt = provider.table_data(tag::CVT)?;
411    let fpgm = provider.table_data(tag::FPGM)?;
412    let name = provider.table_data(tag::NAME)?;
413    let prep = provider.table_data(tag::PREP)?;
414
415    // Build the new font
416    let mut builder = FontBuilder::new(0x00010000_u32);
417
418    if let Some(cmap) = cmap {
419        builder.add_table::<_, cmap::owned::Cmap>(tag::CMAP, cmap, ())?;
420    }
421    if let Some(cvt) = cvt {
422        builder.add_table::<_, ReadScope<'_>>(tag::CVT, ReadScope::new(&cvt), ())?;
423    }
424    if let Some(fpgm) = fpgm {
425        builder.add_table::<_, ReadScope<'_>>(tag::FPGM, ReadScope::new(&fpgm), ())?;
426    }
427    builder.add_table::<_, HheaTable>(tag::HHEA, &hhea, ())?;
428    builder.add_table::<_, HmtxTable<'_>>(tag::HMTX, &hmtx, ())?;
429    builder.add_table::<_, MaxpTable>(tag::MAXP, &maxp, ())?;
430    if let Some(name) = name {
431        builder.add_table::<_, ReadScope<'_>>(tag::NAME, ReadScope::new(&name), ())?;
432    }
433    builder.add_table::<_, PostTable<'_>>(tag::POST, &post, ())?;
434    if let Some(prep) = prep {
435        builder.add_table::<_, ReadScope<'_>>(tag::PREP, ReadScope::new(&prep), ())?;
436    }
437    if let Some(os2) = subset_os2 {
438        builder.add_table::<_, Os2>(tag::OS_2, &os2, ())?;
439    }
440    let mut builder = builder.add_head_table(&head)?;
441    builder.add_glyf_table(glyf)?;
442    builder.data()
443}
444
445fn subset_cff(
446    provider: &impl FontTableProvider,
447    glyph_ids: &[u16],
448    mappings_to_keep: MappingsToKeep<OldIds>,
449    convert_cff_to_cid_if_more_than_255_glyphs: bool,
450    profile: &SubsetProfile,
451) -> Result<Vec<u8>, SubsetError> {
452    let cff_data = provider.read_table_data(tag::CFF)?;
453    let scope = ReadScope::new(&cff_data);
454    let cff: CFF<'_> = scope.read::<CFF<'_>>()?;
455    if cff.name_index.len() != 1 || cff.fonts.len() != 1 {
456        return Err(SubsetError::InvalidFontCount);
457    }
458
459    let head = ReadScope::new(&provider.read_table_data(tag::HEAD)?).read::<HeadTable>()?;
460    let maxp = ReadScope::new(&provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
461    let hhea = ReadScope::new(&provider.read_table_data(tag::HHEA)?).read::<HheaTable>()?;
462    let hmtx_data = provider.read_table_data(tag::HMTX)?;
463    let hmtx = ReadScope::new(&hmtx_data).read_dep::<HmtxTable<'_>>((
464        usize::from(maxp.num_glyphs),
465        usize::from(hhea.num_h_metrics),
466    ))?;
467
468    // Build the new CFF table
469    let cff_subset = cff.subset(glyph_ids, convert_cff_to_cid_if_more_than_255_glyphs)?;
470    build_otf(
471        cff_subset,
472        mappings_to_keep,
473        provider,
474        &head,
475        maxp,
476        hhea,
477        &hmtx,
478        profile,
479    )
480}
481
482fn subset_cff2(
483    provider: &impl FontTableProvider,
484    glyph_ids: &[u16],
485    mappings_to_keep: MappingsToKeep<OldIds>,
486    include_fstype: bool,
487    output_format: OutputFormat,
488    profile: &SubsetProfile,
489) -> Result<Vec<u8>, SubsetError> {
490    let cff2_data = provider.read_table_data(tag::CFF2)?;
491    let scope = ReadScope::new(&cff2_data);
492    let cff2: CFF2<'_> = scope.read::<CFF2<'_>>()?;
493    let head = ReadScope::new(&provider.read_table_data(tag::HEAD)?).read::<HeadTable>()?;
494    let maxp = ReadScope::new(&provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
495    let hhea = ReadScope::new(&provider.read_table_data(tag::HHEA)?).read::<HheaTable>()?;
496    let hmtx_data = provider.read_table_data(tag::HMTX)?;
497    let hmtx = ReadScope::new(&hmtx_data).read_dep::<HmtxTable<'_>>((
498        usize::from(maxp.num_glyphs),
499        usize::from(hhea.num_h_metrics),
500    ))?;
501
502    // Build the new CFF table
503    let cff_subset = cff2
504        .subset_to_cff(glyph_ids, provider, include_fstype, output_format)?
505        .into();
506
507    // Wrap the rest of the OpenType tables around it
508    build_otf(
509        cff_subset,
510        mappings_to_keep,
511        provider,
512        &head,
513        maxp,
514        hhea,
515        &hmtx,
516        profile,
517    )
518}
519
520fn build_otf(
521    cff_subset: SubsetCFF<'_>,
522    mappings_to_keep: MappingsToKeep<OldIds>,
523    provider: &impl FontTableProvider,
524    head: &HeadTable,
525    mut maxp: MaxpTable,
526    mut hhea: HheaTable,
527    hmtx: &HmtxTable<'_>,
528    profile: &SubsetProfile,
529) -> Result<Vec<u8>, SubsetError> {
530    // Get profile tables
531    let profile_tables = profile.get_tables();
532
533    // Get the OS/2 table if needed
534    let os2 = if profile_tables.contains(&tag::OS_2) {
535        match provider.table_data(tag::OS_2) {
536            Ok(Some(data)) => {
537                let os2 = ReadScope::new(&data).read_dep::<Os2>(data.len())?;
538                let updated_os2 = subset_os2(&os2, &mappings_to_keep)?;
539                Some(updated_os2)
540            }
541            _ => None,
542        }
543    } else {
544        None
545    };
546
547    let mappings_to_keep = mappings_to_keep.update_to_new_ids(&cff_subset);
548
549    // Build a new post table with version set to 3, which does not contain any additional
550    // PostScript data
551    let post_data = provider.read_table_data(tag::POST)?;
552    let mut post = ReadScope::new(&post_data).read::<PostTable<'_>>()?;
553    post.header.version = 0x00030000; // version 3.0
554    post.opt_sub_table = None;
555
556    // Build a new cmap table
557    let cmap = create_cmap_table(&mappings_to_keep)?;
558
559    // Build new maxp table
560    let num_glyphs = u16::try_from(cff_subset.len()).map_err(ParseError::from)?;
561    maxp.num_glyphs = num_glyphs;
562
563    // Build new hhea table
564    let num_h_metrics = usize::from(hhea.num_h_metrics);
565    hhea.num_h_metrics = num_glyphs;
566
567    // Build new hmtx table
568    let hmtx = create_hmtx_table(hmtx, num_h_metrics, &cff_subset)?;
569
570    // Get the remaining tables
571    let cvt = provider.table_data(tag::CVT)?;
572    let fpgm = provider.table_data(tag::FPGM)?;
573    let name = provider.table_data(tag::NAME)?;
574    let prep = provider.table_data(tag::PREP)?;
575
576    // Build the new font
577    let mut builder = FontBuilder::new(tag::OTTO);
578    builder.add_table::<_, cmap::owned::Cmap>(tag::CMAP, cmap, ())?;
579    if let Some(cvt) = cvt {
580        builder.add_table::<_, ReadScope<'_>>(tag::CVT, ReadScope::new(&cvt), ())?;
581    }
582    if let Some(fpgm) = fpgm {
583        builder.add_table::<_, ReadScope<'_>>(tag::FPGM, ReadScope::new(&fpgm), ())?;
584    }
585    builder.add_table::<_, HheaTable>(tag::HHEA, &hhea, ())?;
586    builder.add_table::<_, HmtxTable<'_>>(tag::HMTX, &hmtx, ())?;
587    builder.add_table::<_, MaxpTable>(tag::MAXP, &maxp, ())?;
588    if let Some(name) = name {
589        builder.add_table::<_, ReadScope<'_>>(tag::NAME, ReadScope::new(&name), ())?;
590    }
591    if let Some(os2) = os2 {
592        builder.add_table::<_, Os2>(tag::OS_2, &os2, ())?;
593    }
594    builder.add_table::<_, PostTable<'_>>(tag::POST, &post, ())?;
595    if let Some(prep) = prep {
596        builder.add_table::<_, ReadScope<'_>>(tag::PREP, ReadScope::new(&prep), ())?;
597    }
598
599    // Extract the new CFF table now that we're done with cff_subset
600    let cff = CFF::from(cff_subset);
601    builder.add_table::<_, CFF<'_>>(tag::CFF, &cff, ())?;
602    let builder = builder.add_head_table(&head)?;
603    builder.data().map_err(SubsetError::from)
604}
605
606fn create_cmap_table(
607    mappings_to_keep: &MappingsToKeep<NewIds>,
608) -> Result<owned::Cmap, ReadWriteError> {
609    let encoding_record = owned::EncodingRecord::from_mappings(mappings_to_keep)?;
610    Ok(owned::Cmap {
611        encoding_records: vec![encoding_record],
612    })
613}
614
615fn create_cmap_table_from_cmap_array(
616    glyph_ids: &[u16],
617    cmap: Box<[u8; 256]>,
618) -> Result<owned::Cmap, ReadWriteError> {
619    use cmap::owned::{Cmap, CmapSubtable, EncodingRecord};
620
621    if glyph_ids.len() > 256 {
622        return Err(ReadWriteError::Write(WriteError::BadValue));
623    }
624
625    Ok(Cmap {
626        encoding_records: vec![EncodingRecord {
627            platform_id: PlatformId::MACINTOSH,
628            encoding_id: EncodingId::MACINTOSH_APPLE_ROMAN,
629            sub_table: CmapSubtable::Format0 {
630                language: 0, // the subtable is language independent
631                glyph_id_array: cmap,
632            },
633        }],
634    })
635}
636
637/// Construct a complete font from the supplied provider and tags.
638pub fn whole_font<F: FontTableProvider>(
639    provider: &F,
640    tags: &[u32],
641) -> Result<Vec<u8>, ReadWriteError> {
642    let head = ReadScope::new(&provider.read_table_data(tag::HEAD)?).read::<HeadTable>()?;
643    let maxp = ReadScope::new(&provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
644
645    let sfnt_version = tags
646        .iter()
647        .position(|&tag| tag == tag::CFF)
648        .map(|_| tables::CFF_MAGIC)
649        .unwrap_or(tables::TTF_MAGIC);
650    let mut builder = FontBuilder::new(sfnt_version);
651    let mut wants_glyf = false;
652    for &tag in tags {
653        match tag {
654            tag::GLYF => wants_glyf = true,
655            tag::HEAD | tag::MAXP | tag::LOCA => (),
656            _ => {
657                builder.add_table::<_, ReadScope<'_>>(
658                    tag,
659                    ReadScope::new(&provider.read_table_data(tag)?),
660                    (),
661                )?;
662            }
663        }
664    }
665    // maxp and head are required for the font to be usable, so they're always added.
666    builder.add_table::<_, MaxpTable>(tag::MAXP, &maxp, ())?;
667    let mut builder_with_head = builder.add_head_table(&head)?;
668
669    // Add glyf and loca if requested, glyf implies loca. They may not be requested in the case of
670    // a CFF font, or CBDT/CBLC font.
671    if wants_glyf {
672        let loca_data = provider.read_table_data(tag::LOCA)?;
673        let loca = ReadScope::new(&loca_data)
674            .read_dep::<LocaTable<'_>>((usize::from(maxp.num_glyphs), head.index_to_loc_format))?;
675        let glyf_data = provider.read_table_data(tag::GLYF)?;
676        let glyf = ReadScope::new(&glyf_data).read_dep::<GlyfTable<'_>>(&loca)?;
677        builder_with_head.add_glyf_table(glyf)?;
678    }
679    builder_with_head.data()
680}
681
682fn create_hmtx_table<'b>(
683    hmtx: &HmtxTable<'_>,
684    num_h_metrics: usize,
685    subset_glyphs: &impl SubsetGlyphs,
686) -> Result<HmtxTable<'b>, ReadWriteError> {
687    let mut h_metrics = Vec::with_capacity(num_h_metrics);
688
689    for glyph_id in 0..subset_glyphs.len() {
690        // Cast is safe as glyph indexes are 16-bit values
691        let old_id = usize::from(subset_glyphs.old_id(glyph_id as u16));
692
693        if old_id < num_h_metrics {
694            h_metrics.push(hmtx.h_metrics.read_item(old_id)?);
695        } else {
696            // As an optimization, the number of records can be less than the number of glyphs, in which case the
697            // advance width value of the last record applies to all remaining glyph IDs.
698            // https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx
699            let mut metric = hmtx.h_metrics.read_item(num_h_metrics - 1)?;
700            metric.lsb = hmtx.left_side_bearings.read_item(old_id - num_h_metrics)?;
701            h_metrics.push(metric);
702        }
703    }
704
705    Ok(HmtxTable {
706        h_metrics: ReadArrayCow::Owned(h_metrics),
707        left_side_bearings: ReadArrayCow::Owned(vec![]),
708    })
709}
710
711impl FontBuilder {
712    pub fn new(sfnt_version: u32) -> Self {
713        FontBuilder {
714            sfnt_version,
715            tables: BTreeMap::new(),
716        }
717    }
718
719    pub fn add_table<HostType, T: WriteBinaryDep<HostType>>(
720        &mut self,
721        tag: u32,
722        table: HostType,
723        args: T::Args,
724    ) -> Result<T::Output, ReadWriteError> {
725        assert_ne!(tag, tag::HEAD, "head table must use add_head_table");
726        assert_ne!(tag, tag::GLYF, "glyf table must use add_glyf_table");
727
728        self.add_table_inner::<HostType, T>(tag, table, args)
729    }
730
731    pub fn table_tags(&self) -> impl Iterator<Item = u32> + '_ {
732        self.tables.keys().copied()
733    }
734
735    fn add_table_inner<HostType, T: WriteBinaryDep<HostType>>(
736        &mut self,
737        tag: u32,
738        table: HostType,
739        args: T::Args,
740    ) -> Result<T::Output, ReadWriteError> {
741        let mut buffer = WriteBuffer::new();
742        let output = T::write_dep(&mut buffer, table, args)?;
743        self.tables.insert(tag, buffer);
744
745        Ok(output)
746    }
747
748    pub fn add_head_table(
749        mut self,
750        table: &HeadTable,
751    ) -> Result<FontBuilderWithHead, ReadWriteError> {
752        let placeholder = self.add_table_inner::<_, HeadTable>(tag::HEAD, table, ())?;
753
754        Ok(FontBuilderWithHead {
755            inner: self,
756            check_sum_adjustment: placeholder,
757            index_to_loc_format: table.index_to_loc_format,
758        })
759    }
760}
761
762impl FontBuilderWithHead {
763    pub fn add_glyf_table(&mut self, table: GlyfTable<'_>) -> Result<(), ReadWriteError> {
764        let loca = self.inner.add_table_inner::<_, GlyfTable<'_>>(
765            tag::GLYF,
766            table,
767            self.index_to_loc_format,
768        )?;
769        self.inner.add_table_inner::<_, loca::owned::LocaTable>(
770            tag::LOCA,
771            loca,
772            self.index_to_loc_format,
773        )?;
774
775        Ok(())
776    }
777
778    /// Returns a `Vec<u8>` containing the built font
779    pub fn data(mut self) -> Result<Vec<u8>, ReadWriteError> {
780        let mut font = WriteBuffer::new();
781
782        self.write_offset_table(&mut font)?;
783        let table_offset =
784            long_align(self.inner.tables.len() * TableRecord::SIZE + font.bytes_written());
785
786        // Add tables in desired order
787        let mut ordered_tables = self.write_table_directory(&mut font)?;
788
789        // pad
790        let length = font.bytes_written();
791        let padded_length = long_align(length);
792        assert_eq!(
793            padded_length, table_offset,
794            "offset after writing table directory is not at expected position"
795        );
796        font.write_zeros(padded_length - length)?;
797
798        // Fill in check_sum_adjustment in the head table. the magic number comes from the OpenType spec.
799        let headers_checksum = checksum::table_checksum(font.bytes())?;
800        let checksum = Wrapping(0xB1B0AFBA) - (headers_checksum + ordered_tables.checksum);
801
802        // Write out the font tables
803        let mut placeholder = Some(self.check_sum_adjustment);
804        for TaggedBuffer { tag, buffer } in ordered_tables.tables.iter_mut() {
805            if *tag == tag::HEAD {
806                buffer.write_placeholder(placeholder.take().unwrap(), checksum.0)?;
807            }
808            font.write_bytes(buffer.bytes())?;
809        }
810
811        Ok(font.into_inner())
812    }
813
814    fn write_offset_table(&self, font: &mut WriteBuffer) -> Result<(), WriteError> {
815        let num_tables = u16::try_from(self.inner.tables.len())?;
816        let n = max_power_of_2(num_tables);
817        let search_range = (1 << n) * 16;
818        let entry_selector = n;
819        let range_shift = num_tables * 16 - search_range;
820
821        U32Be::write(font, self.inner.sfnt_version)?;
822        U16Be::write(font, num_tables)?;
823        U16Be::write(font, search_range)?;
824        U16Be::write(font, entry_selector)?;
825        U16Be::write(font, range_shift)?;
826
827        Ok(())
828    }
829
830    fn write_table_directory(
831        &mut self,
832        font: &mut WriteBuffer,
833    ) -> Result<OrderedTables, ReadWriteError> {
834        let mut tables = Vec::with_capacity(self.inner.tables.len());
835        let mut checksum = Wrapping(0);
836        let mut table_offset =
837            long_align(self.inner.tables.len() * TableRecord::SIZE + font.bytes_written());
838
839        let tags = self.inner.tables.keys().cloned().collect_vec();
840        for tag in tags {
841            if let Some(mut table) = self.inner.tables.remove(&tag) {
842                let length = table.len();
843                let padded_length = long_align(length);
844                table.write_zeros(padded_length - length)?;
845
846                let table_checksum = checksum::table_checksum(table.bytes())?;
847                checksum += table_checksum;
848
849                let record = TableRecord {
850                    table_tag: tag,
851                    checksum: table_checksum.0,
852                    offset: u32::try_from(table_offset).map_err(WriteError::from)?,
853                    length: u32::try_from(length).map_err(WriteError::from)?,
854                };
855
856                table_offset += padded_length;
857                TableRecord::write(font, &record)?;
858                tables.push(TaggedBuffer { tag, buffer: table });
859            }
860        }
861
862        Ok(OrderedTables { tables, checksum })
863    }
864}
865
866/// Calculate the maximum power of 2 that is <= num
867fn max_power_of_2(num: u16) -> u16 {
868    15u16.saturating_sub(num.leading_zeros() as u16)
869}
870
871/// Map a Unicode codepoint to a 128-bit mask for the ulUnicodeRange field.
872///
873/// This function iterates over the array of defined ranges and returns a mask with the bit
874/// corresponding to the Unicode block set if the input codepoint falls within that range.
875/// If no range matches, it returns 0.
876fn unicode_range_mask(ch: u32) -> u128 {
877    for &(start, end, bit) in UNICODE_RANGES.iter() {
878        if (start..=end).contains(&ch) {
879            return 1 << bit;
880        }
881    }
882    0
883}
884
885/// Prince specific subsetting behaviour.
886///
887/// prince::subset will produce a bare CFF table in the case of an input CFF font.
888#[cfg(feature = "prince")]
889pub mod prince {
890    use super::{
891        tag, FontTableProvider, MappingsToKeep, ReadScope, SubsetError, WriteBinary, WriteBuffer,
892        CFF,
893    };
894    use crate::cff::cff2::{OutputFormat, CFF2};
895    use crate::tables::cmap::subset::{CmapStrategy, CmapTarget};
896    use std::ffi::c_int;
897
898    /// This enum describes the desired cmap generation and maps to the `cmap_target` type in Prince
899    #[derive(Debug, Clone)]
900    pub enum PrinceCmapTarget {
901        /// Build a suitable cmap table
902        Unrestricted,
903        /// Build a Mac Roman cmap table
904        MacRoman,
905        /// Omit the cmap table entirely
906        Omit,
907        /// Use the supplied array as a Mac Roman cmap table
908        MacRomanCmap(Box<[u8; 256]>),
909    }
910
911    impl PrinceCmapTarget {
912        /// Build a new cmap from a `cmap_target` tag
913        pub fn new(tag: c_int, cmap: Option<Box<[u8; 256]>>) -> Self {
914            // NOTE: These tags should be kept in sync with the `cmap_target` type in Prince.
915            match (tag, cmap) {
916                (1, _) => PrinceCmapTarget::Unrestricted,
917                (2, _) => PrinceCmapTarget::MacRoman,
918                (3, _) => PrinceCmapTarget::Omit,
919                (4, Some(cmap)) => PrinceCmapTarget::MacRomanCmap(cmap),
920                _ => panic!("invalid value for PrinceCmapTarget: {}", tag),
921            }
922        }
923    }
924
925    /// Subset this font so that it only contains the glyphs with the supplied `glyph_ids`.
926    ///
927    /// Returns just the CFF table in the case of a CFF font, not a complete OpenType font.
928    pub fn subset(
929        provider: &impl FontTableProvider,
930        glyph_ids: &[u16],
931        cmap_target: PrinceCmapTarget,
932        convert_cff_to_cid_if_more_than_255_glyphs: bool,
933    ) -> Result<Vec<u8>, SubsetError> {
934        if provider.has_table(tag::CFF) {
935            subset_cff_table(
936                provider,
937                glyph_ids,
938                convert_cff_to_cid_if_more_than_255_glyphs,
939            )
940        } else if provider.has_table(tag::CFF2) {
941            subset_cff2_table(provider, glyph_ids)
942        } else {
943            let cmap_strategy = match cmap_target {
944                PrinceCmapTarget::Unrestricted => {
945                    let mappings_to_keep =
946                        MappingsToKeep::new(provider, glyph_ids, CmapTarget::Unrestricted)?;
947                    CmapStrategy::Generate(mappings_to_keep)
948                }
949                PrinceCmapTarget::MacRoman => {
950                    let mappings_to_keep =
951                        MappingsToKeep::new(provider, glyph_ids, CmapTarget::MacRoman)?;
952                    CmapStrategy::Generate(mappings_to_keep)
953                }
954                PrinceCmapTarget::Omit => CmapStrategy::Omit,
955                PrinceCmapTarget::MacRomanCmap(cmap) => CmapStrategy::MacRomanSupplied(cmap),
956            };
957            super::subset_ttf(provider, glyph_ids, cmap_strategy).map_err(SubsetError::from)
958        }
959    }
960
961    /// Subset the CFF table and discard the rest
962    ///
963    /// Useful for PDF because a CFF table can be embedded directly without the need to wrap it in
964    /// an OTF.
965    fn subset_cff_table(
966        provider: &impl FontTableProvider,
967        glyph_ids: &[u16],
968        convert_cff_to_cid_if_more_than_255_glyphs: bool,
969    ) -> Result<Vec<u8>, SubsetError> {
970        let cff_data = provider.read_table_data(tag::CFF)?;
971        let scope = ReadScope::new(&cff_data);
972        let cff: CFF<'_> = scope.read::<CFF<'_>>()?;
973        if cff.name_index.len() != 1 || cff.fonts.len() != 1 {
974            return Err(SubsetError::InvalidFontCount);
975        }
976
977        // Build the new CFF table
978        let cff = cff
979            .subset(glyph_ids, convert_cff_to_cid_if_more_than_255_glyphs)?
980            .into();
981
982        let mut buffer = WriteBuffer::new();
983        CFF::write(&mut buffer, &cff)?;
984
985        Ok(buffer.into_inner())
986    }
987
988    /// Subset a non-variable CFF2 font into a CFF table
989    pub fn subset_cff2_table(
990        provider: &impl FontTableProvider,
991        glyph_ids: &[u16],
992    ) -> Result<Vec<u8>, SubsetError> {
993        let cff2_data = provider.read_table_data(tag::CFF2)?;
994        let scope = ReadScope::new(&cff2_data);
995        let cff2: CFF2<'_> = scope.read::<CFF2<'_>>()?;
996
997        // Build the new CFF table
998        let cff = cff2
999            .subset_to_cff(glyph_ids, provider, true, OutputFormat::CidOnly)?
1000            .into();
1001
1002        let mut buffer = WriteBuffer::new();
1003        CFF::write(&mut buffer, &cff)?;
1004
1005        Ok(buffer.into_inner())
1006    }
1007}
1008
1009impl From<ParseError> for SubsetError {
1010    fn from(err: ParseError) -> SubsetError {
1011        SubsetError::Parse(err)
1012    }
1013}
1014
1015impl From<WriteError> for SubsetError {
1016    fn from(err: WriteError) -> SubsetError {
1017        SubsetError::Write(err)
1018    }
1019}
1020
1021impl From<CFFError> for SubsetError {
1022    fn from(err: CFFError) -> SubsetError {
1023        SubsetError::CFF(err)
1024    }
1025}
1026
1027impl From<ReadWriteError> for SubsetError {
1028    fn from(err: ReadWriteError) -> SubsetError {
1029        match err {
1030            ReadWriteError::Read(err) => SubsetError::Parse(err),
1031            ReadWriteError::Write(err) => SubsetError::Write(err),
1032        }
1033    }
1034}
1035
1036impl fmt::Display for SubsetError {
1037    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1038        match self {
1039            SubsetError::Parse(err) => write!(f, "subset: parse error: {}", err),
1040            SubsetError::Write(err) => write!(f, "subset: write error: {}", err),
1041            SubsetError::CFF(err) => write!(f, "subset: CFF error: {}", err),
1042            SubsetError::NotDef => write!(f, "subset: first glyph is not .notdef"),
1043            SubsetError::TooManyGlyphs => write!(f, "subset: too many glyphs"),
1044            SubsetError::InvalidFontCount => write!(f, "subset: invalid font count in CFF font"),
1045        }
1046    }
1047}
1048
1049impl std::error::Error for SubsetError {}
1050
1051#[cfg(test)]
1052mod tests {
1053    use super::*;
1054    use crate::font_data::FontData;
1055    use crate::tables::cmap::CmapSubtable;
1056    use crate::tables::glyf::{
1057        BoundingBox, CompositeGlyph, CompositeGlyphArgument, CompositeGlyphComponent,
1058        CompositeGlyphFlag, GlyfRecord, Glyph, Point, SimpleGlyph, SimpleGlyphFlag,
1059    };
1060    use crate::tables::{LongHorMetric, OpenTypeData, OpenTypeFont};
1061    use crate::tag::DisplayTag;
1062    use crate::tests::read_fixture;
1063    use crate::Font;
1064
1065    use std::collections::HashSet;
1066
1067    macro_rules! read_table {
1068        ($file:ident, $scope:expr, $tag:path, $t:ty) => {
1069            $file
1070                .read_table(&$scope, $tag)
1071                .expect("error reading table")
1072                .expect("no table found")
1073                .read::<$t>()
1074                .expect("unable to parse")
1075        };
1076        ($file:ident, $scope:expr, $tag:path, $t:ty, $args:expr) => {
1077            $file
1078                .read_table(&$scope, $tag)
1079                .expect("error reading table")
1080                .expect("no table found")
1081                .read_dep::<$t>($args)
1082                .expect("unable to parse")
1083        };
1084    }
1085
1086    #[test]
1087    fn create_glyf_and_hmtx() {
1088        let buffer = read_fixture("tests/fonts/opentype/SFNT-TTF-Composite.ttf");
1089        let fontfile = ReadScope::new(&buffer)
1090            .read::<OpenTypeFont<'_>>()
1091            .expect("error reading OpenTypeFile");
1092        let font = match fontfile.data {
1093            OpenTypeData::Single(font) => font,
1094            OpenTypeData::Collection(_) => unreachable!(),
1095        };
1096        let head = read_table!(font, fontfile.scope, tag::HEAD, HeadTable);
1097        let maxp = read_table!(font, fontfile.scope, tag::MAXP, MaxpTable);
1098        let hhea = read_table!(font, fontfile.scope, tag::HHEA, HheaTable);
1099        let loca = read_table!(
1100            font,
1101            fontfile.scope,
1102            tag::LOCA,
1103            LocaTable<'_>,
1104            (usize::from(maxp.num_glyphs), head.index_to_loc_format)
1105        );
1106        let glyf = read_table!(font, fontfile.scope, tag::GLYF, GlyfTable<'_>, &loca);
1107        let hmtx = read_table!(
1108            font,
1109            fontfile.scope,
1110            tag::HMTX,
1111            HmtxTable<'_>,
1112            (
1113                usize::from(maxp.num_glyphs),
1114                usize::from(hhea.num_h_metrics),
1115            )
1116        );
1117
1118        // 0 - .notdef
1119        // 2 - composite
1120        // 4 - simple
1121        let glyph_ids = [0, 2, 4];
1122        let subset_glyphs = glyf.subset(&glyph_ids).unwrap();
1123        let expected_glyf = GlyfTable::new(vec![
1124            GlyfRecord::empty(),
1125            GlyfRecord::Parsed(Glyph::Composite(CompositeGlyph {
1126                bounding_box: BoundingBox {
1127                    x_min: 205,
1128                    x_max: 4514,
1129                    y_min: 0,
1130                    y_max: 1434,
1131                },
1132                glyphs: vec![
1133                    CompositeGlyphComponent {
1134                        flags: CompositeGlyphFlag::ARG_1_AND_2_ARE_WORDS
1135                            | CompositeGlyphFlag::ARGS_ARE_XY_VALUES
1136                            | CompositeGlyphFlag::ROUND_XY_TO_GRID
1137                            | CompositeGlyphFlag::MORE_COMPONENTS
1138                            | CompositeGlyphFlag::UNSCALED_COMPONENT_OFFSET,
1139                        glyph_index: 3,
1140                        argument1: CompositeGlyphArgument::I16(3453),
1141                        argument2: CompositeGlyphArgument::I16(0),
1142                        scale: None,
1143                    },
1144                    CompositeGlyphComponent {
1145                        flags: CompositeGlyphFlag::ARG_1_AND_2_ARE_WORDS
1146                            | CompositeGlyphFlag::ARGS_ARE_XY_VALUES
1147                            | CompositeGlyphFlag::ROUND_XY_TO_GRID
1148                            | CompositeGlyphFlag::MORE_COMPONENTS
1149                            | CompositeGlyphFlag::UNSCALED_COMPONENT_OFFSET,
1150                        glyph_index: 4,
1151                        argument1: CompositeGlyphArgument::I16(2773),
1152                        argument2: CompositeGlyphArgument::I16(0),
1153                        scale: None,
1154                    },
1155                    CompositeGlyphComponent {
1156                        flags: CompositeGlyphFlag::ARG_1_AND_2_ARE_WORDS
1157                            | CompositeGlyphFlag::ARGS_ARE_XY_VALUES
1158                            | CompositeGlyphFlag::ROUND_XY_TO_GRID
1159                            | CompositeGlyphFlag::MORE_COMPONENTS
1160                            | CompositeGlyphFlag::UNSCALED_COMPONENT_OFFSET,
1161                        glyph_index: 5,
1162                        argument1: CompositeGlyphArgument::I16(1182),
1163                        argument2: CompositeGlyphArgument::I16(0),
1164                        scale: None,
1165                    },
1166                    CompositeGlyphComponent {
1167                        flags: CompositeGlyphFlag::ARG_1_AND_2_ARE_WORDS
1168                            | CompositeGlyphFlag::ARGS_ARE_XY_VALUES
1169                            | CompositeGlyphFlag::ROUND_XY_TO_GRID
1170                            | CompositeGlyphFlag::UNSCALED_COMPONENT_OFFSET,
1171                        glyph_index: 2,
1172                        argument1: CompositeGlyphArgument::I16(205),
1173                        argument2: CompositeGlyphArgument::I16(0),
1174                        scale: None,
1175                    },
1176                ],
1177                instructions: &[],
1178                phantom_points: None,
1179            })),
1180            GlyfRecord::Parsed(Glyph::Simple(SimpleGlyph {
1181                bounding_box: BoundingBox {
1182                    x_min: 0,
1183                    x_max: 1073,
1184                    y_min: 0,
1185                    y_max: 1434,
1186                },
1187                end_pts_of_contours: vec![9],
1188                instructions: &[],
1189                coordinates: vec![
1190                    (
1191                        SimpleGlyphFlag::ON_CURVE_POINT
1192                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1193                        Point(0, 1434),
1194                    ),
1195                    (
1196                        SimpleGlyphFlag::ON_CURVE_POINT
1197                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1198                        Point(1073, 1434),
1199                    ),
1200                    (
1201                        SimpleGlyphFlag::ON_CURVE_POINT
1202                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1203                        Point(1073, 1098),
1204                    ),
1205                    (
1206                        SimpleGlyphFlag::ON_CURVE_POINT
1207                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1208                        Point(485, 1098),
1209                    ),
1210                    (
1211                        SimpleGlyphFlag::ON_CURVE_POINT
1212                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1213                        Point(485, 831),
1214                    ),
1215                    (
1216                        SimpleGlyphFlag::ON_CURVE_POINT
1217                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1218                        Point(987, 831),
1219                    ),
1220                    (
1221                        SimpleGlyphFlag::ON_CURVE_POINT
1222                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1223                        Point(987, 500),
1224                    ),
1225                    (
1226                        SimpleGlyphFlag::ON_CURVE_POINT
1227                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1228                        Point(485, 500),
1229                    ),
1230                    (
1231                        SimpleGlyphFlag::ON_CURVE_POINT
1232                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1233                        Point(485, 0),
1234                    ),
1235                    (
1236                        SimpleGlyphFlag::ON_CURVE_POINT
1237                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1238                        Point::zero(),
1239                    ),
1240                ],
1241                phantom_points: None,
1242            })),
1243            GlyfRecord::Parsed(Glyph::Simple(SimpleGlyph {
1244                bounding_box: BoundingBox {
1245                    x_min: 0,
1246                    x_max: 1061,
1247                    y_min: 0,
1248                    y_max: 1434,
1249                },
1250                end_pts_of_contours: vec![5],
1251                instructions: &[],
1252                coordinates: vec![
1253                    (
1254                        SimpleGlyphFlag::ON_CURVE_POINT
1255                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1256                        Point(0, 1434),
1257                    ),
1258                    (
1259                        SimpleGlyphFlag::ON_CURVE_POINT
1260                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1261                        Point(485, 1434),
1262                    ),
1263                    (
1264                        SimpleGlyphFlag::ON_CURVE_POINT
1265                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1266                        Point(485, 369),
1267                    ),
1268                    (
1269                        SimpleGlyphFlag::ON_CURVE_POINT
1270                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1271                        Point(1061, 369),
1272                    ),
1273                    (
1274                        SimpleGlyphFlag::ON_CURVE_POINT
1275                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1276                        Point(1061, 0),
1277                    ),
1278                    (
1279                        SimpleGlyphFlag::ON_CURVE_POINT
1280                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1281                        Point::zero(),
1282                    ),
1283                ],
1284                phantom_points: None,
1285            })),
1286            GlyfRecord::Parsed(Glyph::Simple(SimpleGlyph {
1287                bounding_box: BoundingBox {
1288                    x_min: 0,
1289                    x_max: 485,
1290                    y_min: 0,
1291                    y_max: 1434,
1292                },
1293                end_pts_of_contours: vec![3],
1294                instructions: &[],
1295                coordinates: vec![
1296                    (
1297                        SimpleGlyphFlag::ON_CURVE_POINT
1298                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1299                        Point(0, 1434),
1300                    ),
1301                    (
1302                        SimpleGlyphFlag::ON_CURVE_POINT
1303                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1304                        Point(485, 1434),
1305                    ),
1306                    (
1307                        SimpleGlyphFlag::ON_CURVE_POINT
1308                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1309                        Point(485, 0),
1310                    ),
1311                    (
1312                        SimpleGlyphFlag::ON_CURVE_POINT
1313                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1314                        Point::zero(),
1315                    ),
1316                ],
1317                phantom_points: None,
1318            })),
1319            GlyfRecord::Parsed(Glyph::Simple(SimpleGlyph {
1320                bounding_box: BoundingBox {
1321                    x_min: 0,
1322                    x_max: 1478,
1323                    y_min: 0,
1324                    y_max: 1434,
1325                },
1326                end_pts_of_contours: vec![7, 10],
1327                instructions: &[],
1328                coordinates: vec![
1329                    (
1330                        SimpleGlyphFlag::ON_CURVE_POINT
1331                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR
1332                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1333                        Point::zero(),
1334                    ),
1335                    (SimpleGlyphFlag::ON_CURVE_POINT, Point(436, 1434)),
1336                    (
1337                        SimpleGlyphFlag::ON_CURVE_POINT
1338                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1339                        Point(1042, 1434),
1340                    ),
1341                    (SimpleGlyphFlag::ON_CURVE_POINT, Point(1478, 0)),
1342                    (
1343                        SimpleGlyphFlag::ON_CURVE_POINT
1344                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1345                        Point(975, 0),
1346                    ),
1347                    (
1348                        SimpleGlyphFlag::ON_CURVE_POINT
1349                            | SimpleGlyphFlag::X_SHORT_VECTOR
1350                            | SimpleGlyphFlag::Y_SHORT_VECTOR
1351                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1352                        Point(909, 244),
1353                    ),
1354                    (
1355                        SimpleGlyphFlag::ON_CURVE_POINT
1356                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1357                        Point(493, 244),
1358                    ),
1359                    (
1360                        SimpleGlyphFlag::ON_CURVE_POINT
1361                            | SimpleGlyphFlag::X_SHORT_VECTOR
1362                            | SimpleGlyphFlag::Y_SHORT_VECTOR,
1363                        Point(430, 0),
1364                    ),
1365                    (
1366                        SimpleGlyphFlag::ON_CURVE_POINT
1367                            | SimpleGlyphFlag::X_SHORT_VECTOR
1368                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1369                        Point(579, 565),
1370                    ),
1371                    (
1372                        SimpleGlyphFlag::ON_CURVE_POINT
1373                            | SimpleGlyphFlag::X_SHORT_VECTOR
1374                            | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR
1375                            | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1376                        Point(825, 565),
1377                    ),
1378                    (
1379                        SimpleGlyphFlag::ON_CURVE_POINT | SimpleGlyphFlag::X_SHORT_VECTOR,
1380                        Point(702, 1032),
1381                    ),
1382                ],
1383                phantom_points: None,
1384            })),
1385        ])
1386        .unwrap();
1387
1388        let num_h_metrics = usize::from(hhea.num_h_metrics);
1389        let hmtx = create_hmtx_table(&hmtx, num_h_metrics, &subset_glyphs).unwrap();
1390
1391        let mut glyf: GlyfTable<'_> = subset_glyphs.into();
1392        glyf.records_mut()
1393            .iter_mut()
1394            .for_each(|rec| rec.parse().unwrap());
1395        assert_eq!(glyf, expected_glyf);
1396
1397        let expected = vec![
1398            LongHorMetric {
1399                advance_width: 1536,
1400                lsb: 0,
1401            },
1402            LongHorMetric {
1403                advance_width: 4719,
1404                lsb: 205,
1405            },
1406            LongHorMetric {
1407                advance_width: 0,
1408                lsb: 0,
1409            },
1410            LongHorMetric {
1411                advance_width: 0,
1412                lsb: 0,
1413            },
1414            LongHorMetric {
1415                advance_width: 0,
1416                lsb: 0,
1417            },
1418            LongHorMetric {
1419                advance_width: 0,
1420                lsb: 0,
1421            },
1422        ];
1423
1424        assert_eq!(hmtx.h_metrics.iter().collect::<Vec<_>>(), expected);
1425        assert_eq!(hmtx.left_side_bearings.iter().collect::<Vec<_>>(), vec![]);
1426    }
1427
1428    #[test]
1429    fn font_builder() {
1430        // Test that reading a font in, adding all its tables and writing it out equals the
1431        // original font
1432        let buffer = read_fixture("tests/fonts/opentype/test-font.ttf");
1433        let fontfile = ReadScope::new(&buffer)
1434            .read::<OpenTypeFont<'_>>()
1435            .expect("error reading OpenTypeFile");
1436        let font = match fontfile.data {
1437            OpenTypeData::Single(font) => font,
1438            OpenTypeData::Collection(_) => unreachable!(),
1439        };
1440        let head = read_table!(font, fontfile.scope, tag::HEAD, HeadTable);
1441        let maxp = read_table!(font, fontfile.scope, tag::MAXP, MaxpTable);
1442        let hhea = read_table!(font, fontfile.scope, tag::HHEA, HheaTable);
1443        let loca = read_table!(
1444            font,
1445            fontfile.scope,
1446            tag::LOCA,
1447            LocaTable<'_>,
1448            (usize::from(maxp.num_glyphs), head.index_to_loc_format)
1449        );
1450        let glyf = read_table!(font, fontfile.scope, tag::GLYF, GlyfTable<'_>, &loca);
1451        let hmtx = read_table!(
1452            font,
1453            fontfile.scope,
1454            tag::HMTX,
1455            HmtxTable<'_>,
1456            (
1457                usize::from(maxp.num_glyphs),
1458                usize::from(hhea.num_h_metrics),
1459            )
1460        );
1461
1462        let mut builder = FontBuilder::new(tables::TTF_MAGIC);
1463        builder
1464            .add_table::<_, HheaTable>(tag::HHEA, &hhea, ())
1465            .unwrap();
1466        builder
1467            .add_table::<_, HmtxTable<'_>>(tag::HMTX, &hmtx, ())
1468            .unwrap();
1469        builder
1470            .add_table::<_, MaxpTable>(tag::MAXP, &maxp, ())
1471            .unwrap();
1472
1473        let tables_added = [
1474            tag::HEAD,
1475            tag::GLYF,
1476            tag::HHEA,
1477            tag::HMTX,
1478            tag::MAXP,
1479            tag::LOCA,
1480        ]
1481        .iter()
1482        .collect::<HashSet<&u32>>();
1483        for record in font.table_records.iter() {
1484            if tables_added.contains(&record.table_tag) {
1485                continue;
1486            }
1487
1488            let table = font
1489                .read_table(&fontfile.scope, record.table_tag)
1490                .unwrap()
1491                .unwrap();
1492            builder
1493                .add_table::<_, ReadScope<'_>>(record.table_tag, table, ())
1494                .unwrap();
1495        }
1496
1497        let mut builder = builder.add_head_table(&head).unwrap();
1498        builder.add_glyf_table(glyf).unwrap();
1499        let data = builder.data().unwrap();
1500
1501        let new_fontfile = ReadScope::new(&data)
1502            .read::<OpenTypeFont<'_>>()
1503            .expect("error reading new OpenTypeFile");
1504        let new_font = match new_fontfile.data {
1505            OpenTypeData::Single(font) => font,
1506            OpenTypeData::Collection(_) => unreachable!(),
1507        };
1508
1509        assert_eq!(new_font.table_records.len(), font.table_records.len());
1510        for record in font.table_records.iter() {
1511            match record.table_tag {
1512                tag::GLYF | tag::LOCA => {
1513                    // TODO: check content of glyf and loca
1514                    // glyf differs because we don't do anything fancy with points at the moment
1515                    // and always write them out as i16 values.
1516                    // loca differs because glyf differs
1517                    continue;
1518                }
1519                tag => {
1520                    let new_record = new_font.find_table_record(record.table_tag).unwrap();
1521                    let tag = DisplayTag(tag);
1522                    assert_eq!((tag, new_record.checksum), (tag, record.checksum));
1523                }
1524            }
1525        }
1526    }
1527
1528    #[test]
1529    fn invalid_glyph_id() {
1530        // Test to ensure that invalid glyph ids don't panic when subsetting
1531        let buffer = read_fixture("tests/fonts/opentype/Klei.otf");
1532        let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1533        let mut glyph_ids = [0, 9999];
1534
1535        match subset(
1536            &opentype_file.table_provider(0).unwrap(),
1537            &mut glyph_ids,
1538            &SubsetProfile::Minimal,
1539        ) {
1540            Err(SubsetError::Parse(ParseError::BadIndex)) => {}
1541            err => panic!(
1542                "expected SubsetError::Parse(ParseError::BadIndex) got {:?}",
1543                err
1544            ),
1545        }
1546    }
1547
1548    #[test]
1549    fn empty_mappings_to_keep() {
1550        // Test to ensure that an empty mappings to keep doesn't panic when subsetting
1551        let buffer = read_fixture("tests/fonts/opentype/SourceCodePro-Regular.otf");
1552        let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1553        // glyph 118 is not Unicode, so does not end up in the mappings to keep
1554        let mut glyph_ids = [0, 118];
1555        let subset_font_data = subset(
1556            &opentype_file.table_provider(0).unwrap(),
1557            &mut glyph_ids,
1558            &SubsetProfile::Minimal,
1559        )
1560        .unwrap();
1561
1562        let opentype_file = ReadScope::new(&subset_font_data)
1563            .read::<OpenTypeFont<'_>>()
1564            .unwrap();
1565        let font = Font::new(opentype_file.table_provider(0).unwrap()).unwrap();
1566        let cmap = ReadScope::new(font.cmap_subtable_data())
1567            .read::<CmapSubtable<'_>>()
1568            .unwrap();
1569
1570        // If mappings_to_keep is empty a mac roman cmap sub-table is created, which doesn't
1571        // care that it's empty.
1572        if let CmapSubtable::Format0 { glyph_id_array, .. } = cmap {
1573            assert!(glyph_id_array.iter().all(|x| x == 0));
1574        } else {
1575            panic!("expected cmap sub-table format 0");
1576        }
1577    }
1578
1579    #[test]
1580    fn ttf_mappings_to_keep_is_none() {
1581        // Test that when subsetting a TTF font with mappings_to_keep set to None the cmap table is
1582        // omitted from the subset font.
1583        let buffer = read_fixture("tests/fonts/opentype/test-font.ttf");
1584        let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1585        let mut glyph_ids = [0, 2];
1586        let subset_font_data = subset_ttf(
1587            &opentype_file.table_provider(0).unwrap(),
1588            &mut glyph_ids,
1589            CmapStrategy::Omit,
1590            &SubsetProfile::Minimal,
1591        )
1592        .unwrap();
1593
1594        let opentype_file = ReadScope::new(&subset_font_data)
1595            .read::<OpenTypeFont<'_>>()
1596            .unwrap();
1597        let table_provider = opentype_file.table_provider(0).unwrap();
1598        assert!(!table_provider.has_table(tag::CMAP));
1599    }
1600
1601    // This test ensures we can call whole_font on a font without a `glyf` table (E.g. CFF).
1602    #[test]
1603    fn test_whole_font() {
1604        let buffer = read_fixture("tests/fonts/opentype/Klei.otf");
1605        let scope = ReadScope::new(&buffer);
1606        let font_file = scope
1607            .read::<FontData<'_>>()
1608            .expect("unable to read FontFile");
1609        let provider = font_file
1610            .table_provider(0)
1611            .expect("unable to get FontTableProvider");
1612        let tags = [
1613            tag::CFF,
1614            tag::GDEF,
1615            tag::GPOS,
1616            tag::GSUB,
1617            tag::OS_2,
1618            tag::CMAP,
1619            tag::HEAD,
1620            tag::HHEA,
1621            tag::HMTX,
1622            tag::MAXP,
1623            tag::NAME,
1624            tag::POST,
1625        ];
1626        assert!(whole_font(&provider, &tags).is_ok());
1627    }
1628
1629    #[test]
1630    fn test_max_power_of_2() {
1631        assert_eq!(max_power_of_2(0), 0);
1632        assert_eq!(max_power_of_2(1), 0);
1633        assert_eq!(max_power_of_2(2), 1);
1634        assert_eq!(max_power_of_2(4), 2);
1635        assert_eq!(max_power_of_2(8), 3);
1636        assert_eq!(max_power_of_2(16), 4);
1637        assert_eq!(max_power_of_2(49), 5);
1638        assert_eq!(max_power_of_2(std::u16::MAX), 15);
1639    }
1640
1641    #[test]
1642    fn subset_cff2_type1() {
1643        let buffer = read_fixture("tests/fonts/opentype/cff2/SourceSans3.abc.otf");
1644        let otf = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1645        let provider = otf.table_provider(0).expect("error reading font file");
1646
1647        // Subset the CFF2, producing CFF. Since there is only two glyphs in the subset font it
1648        // will produce a Type 1 CFF font.
1649        let new_font = subset(&provider, &[0, 1], &SubsetProfile::Minimal).unwrap();
1650
1651        // Read it back
1652        let subset_otf = ReadScope::new(&new_font)
1653            .read::<OpenTypeFont<'_>>()
1654            .unwrap();
1655        let provider = subset_otf
1656            .table_provider(0)
1657            .expect("error reading new font");
1658        let cff_data = provider
1659            .read_table_data(tag::CFF)
1660            .expect("unable to read CFF data");
1661        let res = ReadScope::new(&cff_data).read::<CFF<'_>>();
1662        assert!(res.is_ok());
1663        let cff = res.unwrap();
1664        let font = &cff.fonts[0];
1665        assert!(!font.is_cid_keyed());
1666    }
1667
1668    #[test]
1669    fn subset_cff2_cid() {
1670        let buffer = read_fixture("tests/fonts/opentype/cff2/SourceSans3-Instance.256.otf");
1671        let otf = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1672        let provider = otf.table_provider(0).expect("error reading font file");
1673
1674        // Subset the CFF2, producing CFF. Since there is more than 255 glyphs in the subset font it
1675        // will produce a CID-keyed CFF font.
1676        let glyph_ids = (0..=256).collect::<Vec<_>>();
1677        let new_font = subset(&provider, &glyph_ids, &SubsetProfile::Minimal).unwrap();
1678
1679        // Read it back
1680        let subset_otf = ReadScope::new(&new_font)
1681            .read::<OpenTypeFont<'_>>()
1682            .unwrap();
1683        let provider = subset_otf
1684            .table_provider(0)
1685            .expect("error reading new font");
1686        let cff_data = provider
1687            .read_table_data(tag::CFF)
1688            .expect("unable to read CFF data");
1689        let res = ReadScope::new(&cff_data).read::<CFF<'_>>();
1690        assert!(res.is_ok());
1691        let cff = res.unwrap();
1692        assert_eq!(cff.fonts.len(), 1);
1693        let font = &cff.fonts[0];
1694        assert!(font.is_cid_keyed());
1695    }
1696}