1#![deny(missing_docs)]
2
3use std::collections::BTreeMap;
6use std::fmt;
7use std::num::Wrapping;
8
9use crate::binary::read::{ReadArrayCow, ReadScope};
10use crate::binary::write::{Placeholder, WriteBinary};
11use crate::binary::write::{WriteBinaryDep, WriteBuffer, WriteContext};
12use crate::binary::{long_align, U16Be, U32Be};
13use crate::cff::cff2::{OutputFormat, CFF2};
14use crate::cff::{CFFError, SubsetCFF, CFF};
15use crate::error::{ParseError, ReadWriteError, WriteError};
16use crate::post::PostTable;
17use crate::tables::cmap::subset::{CmapStrategy, MappingsToKeep, NewIds, OldIds};
18use crate::tables::cmap::{owned, EncodingId, PlatformId};
19use crate::tables::glyf::GlyfTable;
20use crate::tables::loca::{self, LocaTable};
21use crate::tables::os2::Os2;
22use crate::tables::{
23 self, cmap, FontTableProvider, HeadTable, HheaTable, HmtxTable, IndexToLocFormat, MaxpTable,
24 TableRecord,
25};
26use crate::{checksum, tag};
27
28const PROFILE_PDF: &[u32] = &[
30 tag::CMAP,
31 tag::HEAD,
32 tag::CVT,
33 tag::FPGM,
34 tag::HHEA,
35 tag::HMTX,
36 tag::MAXP,
37 tag::NAME,
38 tag::POST,
39 tag::PREP,
40];
41
42const PROFILE_MINIMAL: &[u32] = &[
46 tag::CMAP,
47 tag::HEAD,
48 tag::HHEA,
49 tag::HMTX,
50 tag::MAXP,
51 tag::NAME,
52 tag::OS_2,
53 tag::POST,
54];
55
56#[derive(Debug, Clone, PartialEq, Eq)]
58pub enum SubsetProfile {
59 Pdf,
61 Minimal,
63 Custom(Vec<u32>),
65}
66
67#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
69pub enum CmapTarget {
70 #[default]
72 Unrestricted,
73 MacRoman,
77 Unicode,
82}
83
84impl SubsetProfile {
85 fn get_tables(&self, extra: &[u32]) -> Vec<u32> {
87 let tables = match self {
88 SubsetProfile::Pdf => PROFILE_PDF,
89 SubsetProfile::Minimal => PROFILE_MINIMAL,
90 SubsetProfile::Custom(items) => items.as_slice(),
91 };
92 let mut tables = tables.to_vec();
93 tables.extend_from_slice(extra);
94 tables
95 }
96
97 pub fn parse_custom(s: String) -> Result<Self, ParseError> {
122 let mut bytes = s.into_bytes();
123 let tags = bytes
124 .split_mut(|&c| c == b',' || c.is_ascii_whitespace())
125 .map(|name| {
126 name.make_ascii_lowercase();
127 match &*name {
128 b"cmap" => Ok(tag::CMAP),
129 b"head" => Ok(tag::HEAD),
130 b"hhea" => Ok(tag::HHEA),
131 b"hmtx" => Ok(tag::HMTX),
132 b"maxp" => Ok(tag::MAXP),
133 b"name" => Ok(tag::NAME),
134 b"os/2" | b"os2" | b"os_2" => Ok(tag::OS_2),
135 b"post" => Ok(tag::POST),
136 b"gpos" => Ok(tag::GPOS),
137 b"gsub" => Ok(tag::GSUB),
138 b"vhea" => Ok(tag::VHEA),
139 b"vmtx" => Ok(tag::VMTX),
140 b"gdef" => Ok(tag::GDEF),
141 b"cvt" => Ok(tag::CVT),
142 b"fpgm" => Ok(tag::FPGM),
143 b"prep" => Ok(tag::PREP),
144 _ => return Err(ParseError::BadValue),
145 }
146 });
147 let mut tables = PROFILE_MINIMAL
148 .iter()
149 .copied()
150 .map(Ok)
151 .chain(tags)
152 .collect::<Result<Vec<_>, _>>()?;
153 tables.sort();
154 tables.dedup();
155 Ok(Self::Custom(tables))
156 }
157}
158
159#[derive(Debug)]
161pub enum SubsetError {
162 Parse(ParseError),
164 Write(WriteError),
166 CFF(CFFError),
168 NotDef,
170 TooManyGlyphs,
172 InvalidFontCount,
175}
176
177pub(crate) trait SubsetGlyphs {
178 fn len(&self) -> usize;
180
181 fn old_id(&self, new_id: u16) -> u16;
183
184 fn new_id(&self, old_id: u16) -> u16;
186}
187
188pub(crate) struct FontBuilder {
189 sfnt_version: u32,
190 tables: BTreeMap<u32, WriteBuffer>,
191 filter: TableFilter,
192}
193
194pub(crate) enum TableFilter {
195 All,
197 Tables(Vec<u32>),
199}
200
201pub(crate) struct FontBuilderWithHead {
202 inner: FontBuilder,
203 check_sum_adjustment: Placeholder<U32Be, u32>,
204 index_to_loc_format: IndexToLocFormat,
205}
206
207struct TaggedBuffer {
208 tag: u32,
209 buffer: WriteBuffer,
210}
211
212struct OrderedTables {
213 tables: Vec<TaggedBuffer>,
214 checksum: Wrapping<u32>,
215}
216
217pub fn subset(
227 provider: &impl FontTableProvider,
228 glyph_ids: &[u16],
229 profile: &SubsetProfile,
230 cmap_target: CmapTarget,
231) -> Result<Vec<u8>, SubsetError> {
232 let mappings_to_keep = MappingsToKeep::new(provider, glyph_ids, cmap_target)?;
233 if provider.has_table(tag::CFF) {
234 subset_cff(provider, glyph_ids, mappings_to_keep, true, profile)
235 } else if provider.has_table(tag::CFF2) {
236 subset_cff2(
237 provider,
238 glyph_ids,
239 mappings_to_keep,
240 false,
241 OutputFormat::Type1OrCid,
242 profile,
243 )
244 } else {
245 subset_ttf(
246 provider,
247 glyph_ids,
248 CmapStrategy::Generate(mappings_to_keep),
249 profile,
250 )
251 .map_err(SubsetError::from)
252 }
253}
254
255fn subset_ttf(
260 provider: &impl FontTableProvider,
261 glyph_ids: &[u16],
262 cmap_strategy: CmapStrategy,
263 profile: &SubsetProfile,
264) -> Result<Vec<u8>, ReadWriteError> {
265 let profile_tables = profile.get_tables(&[tag::LOCA, tag::GLYF]);
266 let head = ReadScope::new(&provider.read_table_data(tag::HEAD)?).read::<HeadTable>()?;
267 let mut maxp = ReadScope::new(&provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
268 let loca_data = provider.read_table_data(tag::LOCA)?;
269 let loca = ReadScope::new(&loca_data)
270 .read_dep::<LocaTable<'_>>((maxp.num_glyphs, head.index_to_loc_format))?;
271 let glyf_data = provider.read_table_data(tag::GLYF)?;
272 let glyf = ReadScope::new(&glyf_data).read_dep::<GlyfTable<'_>>(&loca)?;
273 let mut hhea = ReadScope::new(&provider.read_table_data(tag::HHEA)?).read::<HheaTable>()?;
274 let hmtx_data = provider.read_table_data(tag::HMTX)?;
275 let hmtx = ReadScope::new(&hmtx_data).read_dep::<HmtxTable<'_>>((
276 usize::from(maxp.num_glyphs),
277 usize::from(hhea.num_h_metrics),
278 ))?;
279
280 let post_data = provider.read_table_data(tag::POST)?;
283 let mut post = ReadScope::new(&post_data).read::<PostTable<'_>>()?;
284 post.header.version = 0x00030000; post.opt_sub_table = None;
286
287 let maybe_os2 = profile_tables
289 .contains(&tag::OS_2)
290 .then(|| {
291 provider
292 .read_table_data(tag::OS_2)
293 .and_then(|data| ReadScope::new(&data).read_dep::<Os2>(data.len()))
294 })
295 .transpose()?;
296
297 let subset_os2 = maybe_os2.map(|os2| match &cmap_strategy {
299 CmapStrategy::Generate(mappings) => subset_os2(&os2, mappings),
300 CmapStrategy::MacRomanSupplied(_) | CmapStrategy::Omit => os2,
301 });
302
303 let subset_glyphs = glyf.subset(glyph_ids)?;
305
306 let cmap = match cmap_strategy {
308 CmapStrategy::Generate(mappings_to_keep) => {
309 let mappings_to_keep = mappings_to_keep.update_to_new_ids(&subset_glyphs);
310 Some(create_cmap_table(&mappings_to_keep)?)
311 }
312 CmapStrategy::MacRomanSupplied(cmap) => {
313 Some(create_cmap_table_from_cmap_array(glyph_ids, cmap)?)
314 }
315 CmapStrategy::Omit => None,
316 };
317
318 let num_glyphs = u16::try_from(subset_glyphs.len()).map_err(ParseError::from)?;
320 maxp.num_glyphs = num_glyphs;
321
322 let num_h_metrics = usize::from(hhea.num_h_metrics);
324 hhea.num_h_metrics = num_glyphs;
325
326 let hmtx = create_hmtx_table(&hmtx, num_h_metrics, &subset_glyphs)?;
328
329 let glyf = GlyfTable::from(subset_glyphs);
331
332 let cvt = provider.table_data(tag::CVT)?;
334 let fpgm = provider.table_data(tag::FPGM)?;
335 let name = provider.table_data(tag::NAME)?;
336 let prep = provider.table_data(tag::PREP)?;
337
338 let mut builder = FontBuilder::new(0x00010000_u32, TableFilter::Tables(profile_tables));
340 if let Some(cmap) = cmap {
341 builder.add_table::<_, cmap::owned::Cmap>(tag::CMAP, cmap, ())?;
342 }
343 if let Some(cvt) = cvt {
344 builder.add_table::<_, ReadScope<'_>>(tag::CVT, ReadScope::new(&cvt), ())?;
345 }
346 if let Some(fpgm) = fpgm {
347 builder.add_table::<_, ReadScope<'_>>(tag::FPGM, ReadScope::new(&fpgm), ())?;
348 }
349 builder.add_table::<_, HheaTable>(tag::HHEA, &hhea, ())?;
350 builder.add_table::<_, HmtxTable<'_>>(tag::HMTX, &hmtx, ())?;
351 builder.add_table::<_, MaxpTable>(tag::MAXP, &maxp, ())?;
352 if let Some(name) = name {
353 builder.add_table::<_, ReadScope<'_>>(tag::NAME, ReadScope::new(&name), ())?;
354 }
355 builder.add_table::<_, PostTable<'_>>(tag::POST, &post, ())?;
356 if let Some(prep) = prep {
357 builder.add_table::<_, ReadScope<'_>>(tag::PREP, ReadScope::new(&prep), ())?;
358 }
359 if let Some(os2) = subset_os2 {
360 builder.add_table::<_, Os2>(tag::OS_2, &os2, ())?;
361 }
362 let mut builder = builder.add_head_table(&head)?;
363 builder.add_glyf_table(glyf)?;
364 builder.data()
365}
366
367fn subset_cff(
368 provider: &impl FontTableProvider,
369 glyph_ids: &[u16],
370 mappings_to_keep: MappingsToKeep<OldIds>,
371 convert_cff_to_cid_if_more_than_255_glyphs: bool,
372 profile: &SubsetProfile,
373) -> Result<Vec<u8>, SubsetError> {
374 let cff_data = provider.read_table_data(tag::CFF)?;
375 let scope = ReadScope::new(&cff_data);
376 let cff: CFF<'_> = scope.read::<CFF<'_>>()?;
377 if cff.name_index.len() != 1 || cff.fonts.len() != 1 {
378 return Err(SubsetError::InvalidFontCount);
379 }
380
381 let head = ReadScope::new(&provider.read_table_data(tag::HEAD)?).read::<HeadTable>()?;
382 let maxp = ReadScope::new(&provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
383 let hhea = ReadScope::new(&provider.read_table_data(tag::HHEA)?).read::<HheaTable>()?;
384 let hmtx_data = provider.read_table_data(tag::HMTX)?;
385 let hmtx = ReadScope::new(&hmtx_data).read_dep::<HmtxTable<'_>>((
386 usize::from(maxp.num_glyphs),
387 usize::from(hhea.num_h_metrics),
388 ))?;
389
390 let cff_subset = cff.subset(glyph_ids, convert_cff_to_cid_if_more_than_255_glyphs)?;
392 build_otf(
393 cff_subset,
394 mappings_to_keep,
395 provider,
396 &head,
397 maxp,
398 hhea,
399 &hmtx,
400 profile,
401 )
402}
403
404fn subset_cff2(
405 provider: &impl FontTableProvider,
406 glyph_ids: &[u16],
407 mappings_to_keep: MappingsToKeep<OldIds>,
408 include_fstype: bool,
409 output_format: OutputFormat,
410 profile: &SubsetProfile,
411) -> Result<Vec<u8>, SubsetError> {
412 let cff2_data = provider.read_table_data(tag::CFF2)?;
413 let scope = ReadScope::new(&cff2_data);
414 let cff2: CFF2<'_> = scope.read::<CFF2<'_>>()?;
415 let head = ReadScope::new(&provider.read_table_data(tag::HEAD)?).read::<HeadTable>()?;
416 let maxp = ReadScope::new(&provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
417 let hhea = ReadScope::new(&provider.read_table_data(tag::HHEA)?).read::<HheaTable>()?;
418 let hmtx_data = provider.read_table_data(tag::HMTX)?;
419 let hmtx = ReadScope::new(&hmtx_data).read_dep::<HmtxTable<'_>>((
420 usize::from(maxp.num_glyphs),
421 usize::from(hhea.num_h_metrics),
422 ))?;
423
424 let cff_subset = cff2.subset_to_cff(glyph_ids, provider, include_fstype, output_format)?;
426
427 build_otf(
429 cff_subset,
430 mappings_to_keep,
431 provider,
432 &head,
433 maxp,
434 hhea,
435 &hmtx,
436 profile,
437 )
438}
439
440fn build_otf(
441 cff_subset: SubsetCFF<'_>,
442 mappings_to_keep: MappingsToKeep<OldIds>,
443 provider: &impl FontTableProvider,
444 head: &HeadTable,
445 mut maxp: MaxpTable,
446 mut hhea: HheaTable,
447 hmtx: &HmtxTable<'_>,
448 profile: &SubsetProfile,
449) -> Result<Vec<u8>, SubsetError> {
450 let profile_tables = profile.get_tables(&[tag::CFF]);
451
452 let os2 = if profile_tables.contains(&tag::OS_2) {
454 match provider.table_data(tag::OS_2) {
455 Ok(Some(data)) => {
456 let os2 = ReadScope::new(&data).read_dep::<Os2>(data.len())?;
457 let updated_os2 = subset_os2(&os2, &mappings_to_keep);
458 Some(updated_os2)
459 }
460 _ => None,
461 }
462 } else {
463 None
464 };
465
466 let mappings_to_keep = mappings_to_keep.update_to_new_ids(&cff_subset);
467
468 let post_data = provider.read_table_data(tag::POST)?;
471 let mut post = ReadScope::new(&post_data).read::<PostTable<'_>>()?;
472 post.header.version = 0x00030000; post.opt_sub_table = None;
474
475 let cmap = create_cmap_table(&mappings_to_keep)?;
477
478 let num_glyphs = u16::try_from(cff_subset.len()).map_err(ParseError::from)?;
480 maxp.num_glyphs = num_glyphs;
481
482 let num_h_metrics = usize::from(hhea.num_h_metrics);
484 hhea.num_h_metrics = num_glyphs;
485
486 let hmtx = create_hmtx_table(hmtx, num_h_metrics, &cff_subset)?;
488
489 let cvt = provider.table_data(tag::CVT)?;
491 let fpgm = provider.table_data(tag::FPGM)?;
492 let name = provider.table_data(tag::NAME)?;
493 let prep = provider.table_data(tag::PREP)?;
494
495 let mut builder = FontBuilder::new(tag::OTTO, TableFilter::Tables(profile_tables));
497 builder.add_table::<_, cmap::owned::Cmap>(tag::CMAP, cmap, ())?;
498 if let Some(cvt) = cvt {
499 builder.add_table::<_, ReadScope<'_>>(tag::CVT, ReadScope::new(&cvt), ())?;
500 }
501 if let Some(fpgm) = fpgm {
502 builder.add_table::<_, ReadScope<'_>>(tag::FPGM, ReadScope::new(&fpgm), ())?;
503 }
504 builder.add_table::<_, HheaTable>(tag::HHEA, &hhea, ())?;
505 builder.add_table::<_, HmtxTable<'_>>(tag::HMTX, &hmtx, ())?;
506 builder.add_table::<_, MaxpTable>(tag::MAXP, &maxp, ())?;
507 if let Some(name) = name {
508 builder.add_table::<_, ReadScope<'_>>(tag::NAME, ReadScope::new(&name), ())?;
509 }
510 if let Some(os2) = os2 {
511 builder.add_table::<_, Os2>(tag::OS_2, &os2, ())?;
512 }
513 builder.add_table::<_, PostTable<'_>>(tag::POST, &post, ())?;
514 if let Some(prep) = prep {
515 builder.add_table::<_, ReadScope<'_>>(tag::PREP, ReadScope::new(&prep), ())?;
516 }
517
518 let cff = CFF::from(cff_subset);
520 builder.add_table::<_, CFF<'_>>(tag::CFF, &cff, ())?;
521 let builder = builder.add_head_table(head)?;
522 builder.data().map_err(SubsetError::from)
523}
524
525fn create_cmap_table(
526 mappings_to_keep: &MappingsToKeep<NewIds>,
527) -> Result<owned::Cmap, ReadWriteError> {
528 let encoding_record = owned::EncodingRecord::from_mappings(mappings_to_keep)?;
529 Ok(owned::Cmap {
530 encoding_records: vec![encoding_record],
531 })
532}
533
534fn create_cmap_table_from_cmap_array(
535 glyph_ids: &[u16],
536 cmap: Box<[u8; 256]>,
537) -> Result<owned::Cmap, ReadWriteError> {
538 use cmap::owned::{Cmap, CmapSubtable, EncodingRecord};
539
540 if glyph_ids.len() > 256 {
541 return Err(ReadWriteError::Write(WriteError::BadValue));
542 }
543
544 Ok(Cmap {
545 encoding_records: vec![EncodingRecord {
546 platform_id: PlatformId::MACINTOSH,
547 encoding_id: EncodingId::MACINTOSH_APPLE_ROMAN,
548 sub_table: CmapSubtable::Format0 {
549 language: 0, glyph_id_array: cmap,
551 },
552 }],
553 })
554}
555
556pub fn whole_font<F: FontTableProvider>(
558 provider: &F,
559 tags: &[u32],
560) -> Result<Vec<u8>, ReadWriteError> {
561 let head = ReadScope::new(&provider.read_table_data(tag::HEAD)?).read::<HeadTable>()?;
562 let maxp = ReadScope::new(&provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
563
564 let sfnt_version = tags
565 .iter()
566 .position(|&tag| tag == tag::CFF)
567 .map(|_| tables::CFF_MAGIC)
568 .unwrap_or(tables::TTF_MAGIC);
569 let mut builder = FontBuilder::new(sfnt_version, TableFilter::All);
570 let mut wants_glyf = false;
571 for &tag in tags {
572 match tag {
573 tag::GLYF => wants_glyf = true,
574 tag::HEAD | tag::MAXP | tag::LOCA => (),
575 _ => {
576 builder.add_table::<_, ReadScope<'_>>(
577 tag,
578 ReadScope::new(&provider.read_table_data(tag)?),
579 (),
580 )?;
581 }
582 }
583 }
584 builder.add_table::<_, MaxpTable>(tag::MAXP, &maxp, ())?;
586 let mut builder_with_head = builder.add_head_table(&head)?;
587
588 if wants_glyf {
591 let loca_data = provider.read_table_data(tag::LOCA)?;
592 let loca = ReadScope::new(&loca_data)
593 .read_dep::<LocaTable<'_>>((maxp.num_glyphs, head.index_to_loc_format))?;
594 let glyf_data = provider.read_table_data(tag::GLYF)?;
595 let glyf = ReadScope::new(&glyf_data).read_dep::<GlyfTable<'_>>(&loca)?;
596 builder_with_head.add_glyf_table(glyf)?;
597 }
598 builder_with_head.data()
599}
600
601fn create_hmtx_table<'b>(
602 hmtx: &HmtxTable<'_>,
603 num_h_metrics: usize,
604 subset_glyphs: &impl SubsetGlyphs,
605) -> Result<HmtxTable<'b>, ReadWriteError> {
606 let mut h_metrics = Vec::with_capacity(num_h_metrics);
607
608 for glyph_id in 0..subset_glyphs.len() {
609 let old_id = usize::from(subset_glyphs.old_id(glyph_id as u16));
611
612 if old_id < num_h_metrics {
613 h_metrics.push(hmtx.h_metrics.read_item(old_id)?);
614 } else {
615 let mut metric = hmtx.h_metrics.read_item(num_h_metrics - 1)?;
619 metric.lsb = hmtx.left_side_bearings.read_item(old_id - num_h_metrics)?;
620 h_metrics.push(metric);
621 }
622 }
623
624 Ok(HmtxTable {
625 h_metrics: ReadArrayCow::Owned(h_metrics),
626 left_side_bearings: ReadArrayCow::Owned(vec![]),
627 })
628}
629
630fn subset_os2(os2: &Os2, mappings: &MappingsToKeep<OldIds>) -> Os2 {
631 let (new_first, new_last) = mappings.first_last_codepoints();
632 let new_unicode_mask = mappings.unicode_bitmask();
633 let new_ul_unicode_range1 = (new_unicode_mask & 0xFFFF_FFFF) as u32;
634 let new_ul_unicode_range2 = ((new_unicode_mask >> 32) & 0xFFFF_FFFF) as u32;
635 let new_ul_unicode_range3 = ((new_unicode_mask >> 64) & 0xFFFF_FFFF) as u32;
636 let new_ul_unicode_range4 = ((new_unicode_mask >> 96) & 0xFFFF_FFFF) as u32;
637
638 Os2 {
639 version: os2.version,
640 x_avg_char_width: os2.x_avg_char_width, us_weight_class: os2.us_weight_class,
642 us_width_class: os2.us_width_class,
643 fs_type: os2.fs_type,
644 y_subscript_x_size: os2.y_subscript_x_size,
645 y_subscript_y_size: os2.y_subscript_y_size,
646 y_subscript_x_offset: os2.y_subscript_x_offset,
647 y_subscript_y_offset: os2.y_subscript_y_offset,
648 y_superscript_x_size: os2.y_superscript_x_size,
649 y_superscript_y_size: os2.y_superscript_y_size,
650 y_superscript_x_offset: os2.y_superscript_x_offset,
651 y_superscript_y_offset: os2.y_superscript_y_offset,
652 y_strikeout_size: os2.y_strikeout_size,
653 y_strikeout_position: os2.y_strikeout_position,
654 s_family_class: os2.s_family_class,
655 panose: os2.panose,
656 ul_unicode_range1: new_ul_unicode_range1,
657 ul_unicode_range2: new_ul_unicode_range2,
658 ul_unicode_range3: new_ul_unicode_range3,
659 ul_unicode_range4: new_ul_unicode_range4,
660 ach_vend_id: os2.ach_vend_id,
661 fs_selection: os2.fs_selection,
662 us_first_char_index: u16::try_from(new_first).unwrap_or(0xFFFF),
665 us_last_char_index: u16::try_from(new_last).unwrap_or(0xFFFF),
666 version0: os2.version0.clone(),
667 version1: os2.version1.clone(),
668 version2to4: os2.version2to4.clone(),
669 version5: os2.version5.clone(),
670 }
671}
672
673impl FontBuilder {
674 pub fn new(sfnt_version: u32, filter: TableFilter) -> Self {
675 FontBuilder {
676 sfnt_version,
677 tables: BTreeMap::new(),
678 filter,
679 }
680 }
681
682 pub fn add_table<HostType, T: WriteBinaryDep<HostType>>(
683 &mut self,
684 tag: u32,
685 table: HostType,
686 args: T::Args,
687 ) -> Result<T::Output, ReadWriteError> {
688 assert_ne!(tag, tag::HEAD, "head table must use add_head_table");
689 assert_ne!(tag, tag::GLYF, "glyf table must use add_glyf_table");
690
691 self.add_table_inner::<HostType, T>(tag, table, args)
692 }
693
694 pub fn table_tags(&self) -> impl Iterator<Item = u32> + '_ {
695 self.tables.keys().copied()
696 }
697
698 fn add_table_inner<HostType, T: WriteBinaryDep<HostType>>(
699 &mut self,
700 tag: u32,
701 table: HostType,
702 args: T::Args,
703 ) -> Result<T::Output, ReadWriteError> {
704 let mut buffer = WriteBuffer::new();
705 let output = T::write_dep(&mut buffer, table, args)?;
706
707 if self.filter.contains(tag) {
710 self.tables.insert(tag, buffer);
711 }
712
713 Ok(output)
714 }
715
716 pub fn add_head_table(
717 mut self,
718 table: &HeadTable,
719 ) -> Result<FontBuilderWithHead, ReadWriteError> {
720 let placeholder = self.add_table_inner::<_, HeadTable>(tag::HEAD, table, ())?;
721
722 Ok(FontBuilderWithHead {
723 inner: self,
724 check_sum_adjustment: placeholder,
725 index_to_loc_format: table.index_to_loc_format,
726 })
727 }
728}
729
730impl FontBuilderWithHead {
731 pub fn add_glyf_table(&mut self, table: GlyfTable<'_>) -> Result<(), ReadWriteError> {
732 let loca = self.inner.add_table_inner::<_, GlyfTable<'_>>(
733 tag::GLYF,
734 table,
735 self.index_to_loc_format,
736 )?;
737 self.inner.add_table_inner::<_, loca::owned::LocaTable>(
738 tag::LOCA,
739 loca,
740 self.index_to_loc_format,
741 )?;
742
743 Ok(())
744 }
745
746 pub fn data(mut self) -> Result<Vec<u8>, ReadWriteError> {
748 let mut font = WriteBuffer::new();
749
750 self.write_offset_table(&mut font)?;
751 let table_offset =
752 long_align(self.inner.tables.len() * TableRecord::SIZE + font.bytes_written());
753
754 let mut ordered_tables = self.write_table_directory(&mut font)?;
756
757 let length = font.bytes_written();
759 let padded_length = long_align(length);
760 assert_eq!(
761 padded_length, table_offset,
762 "offset after writing table directory is not at expected position"
763 );
764 font.write_zeros(padded_length - length)?;
765
766 let headers_checksum = checksum::table_checksum(font.bytes())?;
768 let checksum = Wrapping(0xB1B0AFBA) - (headers_checksum + ordered_tables.checksum);
769
770 let mut placeholder = Some(self.check_sum_adjustment);
772 for TaggedBuffer { tag, buffer } in ordered_tables.tables.iter_mut() {
773 if *tag == tag::HEAD {
774 buffer.write_placeholder(placeholder.take().unwrap(), checksum.0)?;
775 }
776 font.write_bytes(buffer.bytes())?;
777 }
778
779 Ok(font.into_inner())
780 }
781
782 fn write_offset_table(&self, font: &mut WriteBuffer) -> Result<(), WriteError> {
783 let num_tables = u16::try_from(self.inner.tables.len())?;
784 let n = max_power_of_2(num_tables);
785 let search_range = (1 << n) * 16;
786 let entry_selector = n;
787 let range_shift = num_tables * 16 - search_range;
788
789 U32Be::write(font, self.inner.sfnt_version)?;
790 U16Be::write(font, num_tables)?;
791 U16Be::write(font, search_range)?;
792 U16Be::write(font, entry_selector)?;
793 U16Be::write(font, range_shift)?;
794
795 Ok(())
796 }
797
798 fn write_table_directory(
799 &mut self,
800 font: &mut WriteBuffer,
801 ) -> Result<OrderedTables, ReadWriteError> {
802 let mut tables = Vec::with_capacity(self.inner.tables.len());
803 let mut checksum = Wrapping(0);
804 let mut table_offset =
805 long_align(self.inner.tables.len() * TableRecord::SIZE + font.bytes_written());
806
807 for (tag, mut table) in std::mem::take(&mut self.inner.tables) {
808 let length = table.len();
809 let padded_length = long_align(length);
810 table.write_zeros(padded_length - length)?;
811
812 let table_checksum = checksum::table_checksum(table.bytes())?;
813 checksum += table_checksum;
814
815 let record = TableRecord {
816 table_tag: tag,
817 checksum: table_checksum.0,
818 offset: u32::try_from(table_offset).map_err(WriteError::from)?,
819 length: u32::try_from(length).map_err(WriteError::from)?,
820 };
821
822 table_offset += padded_length;
823 TableRecord::write(font, &record)?;
824 tables.push(TaggedBuffer { tag, buffer: table });
825 }
826
827 Ok(OrderedTables { tables, checksum })
828 }
829}
830
831impl TableFilter {
832 fn contains(&self, tag: u32) -> bool {
833 match self {
834 TableFilter::All => true,
835 TableFilter::Tables(tables) => tables.contains(&tag),
836 }
837 }
838}
839
840fn max_power_of_2(num: u16) -> u16 {
842 15u16.saturating_sub(num.leading_zeros() as u16)
843}
844
845#[cfg(feature = "prince")]
849pub mod prince {
850 use super::{
851 tag, FontTableProvider, MappingsToKeep, ReadScope, SubsetError, WriteBinary, WriteBuffer,
852 CFF,
853 };
854 use crate::cff::cff2::{OutputFormat, CFF2};
855 use crate::subset::{CmapTarget, SubsetProfile};
856 use crate::tables::cmap::subset::CmapStrategy;
857 use std::ffi::c_int;
858
859 #[derive(Debug, Clone)]
861 pub enum PrinceCmapTarget {
862 Unrestricted,
864 MacRoman,
866 Omit,
868 MacRomanCmap(Box<[u8; 256]>),
870 }
871
872 impl PrinceCmapTarget {
873 pub fn new(tag: c_int, cmap: Option<Box<[u8; 256]>>) -> Self {
875 match (tag, cmap) {
877 (1, _) => PrinceCmapTarget::Unrestricted,
878 (2, _) => PrinceCmapTarget::MacRoman,
879 (3, _) => PrinceCmapTarget::Omit,
880 (4, Some(cmap)) => PrinceCmapTarget::MacRomanCmap(cmap),
881 _ => panic!("invalid value for PrinceCmapTarget: {}", tag),
882 }
883 }
884 }
885
886 pub fn subset(
890 provider: &impl FontTableProvider,
891 glyph_ids: &[u16],
892 cmap_target: PrinceCmapTarget,
893 convert_cff_to_cid_if_more_than_255_glyphs: bool,
894 ) -> Result<Vec<u8>, SubsetError> {
895 if provider.has_table(tag::CFF) {
896 subset_cff_table(
897 provider,
898 glyph_ids,
899 convert_cff_to_cid_if_more_than_255_glyphs,
900 )
901 } else if provider.has_table(tag::CFF2) {
902 subset_cff2_table(provider, glyph_ids)
903 } else {
904 let cmap_strategy = match cmap_target {
905 PrinceCmapTarget::Unrestricted => {
906 let mappings_to_keep =
907 MappingsToKeep::new(provider, glyph_ids, CmapTarget::Unrestricted)?;
908 CmapStrategy::Generate(mappings_to_keep)
909 }
910 PrinceCmapTarget::MacRoman => {
911 let mappings_to_keep =
912 MappingsToKeep::new(provider, glyph_ids, CmapTarget::MacRoman)?;
913 CmapStrategy::Generate(mappings_to_keep)
914 }
915 PrinceCmapTarget::Omit => CmapStrategy::Omit,
916 PrinceCmapTarget::MacRomanCmap(cmap) => CmapStrategy::MacRomanSupplied(cmap),
917 };
918 super::subset_ttf(provider, glyph_ids, cmap_strategy, &SubsetProfile::Pdf)
919 .map_err(SubsetError::from)
920 }
921 }
922
923 fn subset_cff_table(
928 provider: &impl FontTableProvider,
929 glyph_ids: &[u16],
930 convert_cff_to_cid_if_more_than_255_glyphs: bool,
931 ) -> Result<Vec<u8>, SubsetError> {
932 let cff_data = provider.read_table_data(tag::CFF)?;
933 let scope = ReadScope::new(&cff_data);
934 let cff: CFF<'_> = scope.read::<CFF<'_>>()?;
935 if cff.name_index.len() != 1 || cff.fonts.len() != 1 {
936 return Err(SubsetError::InvalidFontCount);
937 }
938
939 let cff = cff
941 .subset(glyph_ids, convert_cff_to_cid_if_more_than_255_glyphs)?
942 .into();
943
944 let mut buffer = WriteBuffer::new();
945 CFF::write(&mut buffer, &cff)?;
946
947 Ok(buffer.into_inner())
948 }
949
950 pub fn subset_cff2_table(
952 provider: &impl FontTableProvider,
953 glyph_ids: &[u16],
954 ) -> Result<Vec<u8>, SubsetError> {
955 let cff2_data = provider.read_table_data(tag::CFF2)?;
956 let scope = ReadScope::new(&cff2_data);
957 let cff2: CFF2<'_> = scope.read::<CFF2<'_>>()?;
958
959 let cff = cff2
961 .subset_to_cff(glyph_ids, provider, true, OutputFormat::CidOnly)?
962 .into();
963
964 let mut buffer = WriteBuffer::new();
965 CFF::write(&mut buffer, &cff)?;
966
967 Ok(buffer.into_inner())
968 }
969}
970
971impl From<ParseError> for SubsetError {
972 fn from(err: ParseError) -> SubsetError {
973 SubsetError::Parse(err)
974 }
975}
976
977impl From<WriteError> for SubsetError {
978 fn from(err: WriteError) -> SubsetError {
979 SubsetError::Write(err)
980 }
981}
982
983impl From<CFFError> for SubsetError {
984 fn from(err: CFFError) -> SubsetError {
985 SubsetError::CFF(err)
986 }
987}
988
989impl From<ReadWriteError> for SubsetError {
990 fn from(err: ReadWriteError) -> SubsetError {
991 match err {
992 ReadWriteError::Read(err) => SubsetError::Parse(err),
993 ReadWriteError::Write(err) => SubsetError::Write(err),
994 }
995 }
996}
997
998impl fmt::Display for SubsetError {
999 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1000 match self {
1001 SubsetError::Parse(err) => write!(f, "subset: parse error: {}", err),
1002 SubsetError::Write(err) => write!(f, "subset: write error: {}", err),
1003 SubsetError::CFF(err) => write!(f, "subset: CFF error: {}", err),
1004 SubsetError::NotDef => write!(f, "subset: first glyph is not .notdef"),
1005 SubsetError::TooManyGlyphs => write!(f, "subset: too many glyphs"),
1006 SubsetError::InvalidFontCount => write!(f, "subset: invalid font count in CFF font"),
1007 }
1008 }
1009}
1010
1011impl std::error::Error for SubsetError {}
1012
1013#[cfg(test)]
1014mod tests {
1015 use super::*;
1016 use crate::font_data::FontData;
1017 use crate::tables::cmap::{Cmap, CmapSubtable};
1018 use crate::tables::glyf::{
1019 BoundingBox, CompositeGlyph, CompositeGlyphArgument, CompositeGlyphComponent,
1020 CompositeGlyphFlag, GlyfRecord, Glyph, Point, SimpleGlyph, SimpleGlyphFlag,
1021 };
1022 use crate::tables::{LongHorMetric, OpenTypeData, OpenTypeFont};
1023 use crate::tag::DisplayTag;
1024 use crate::tests::read_fixture;
1025 use crate::Font;
1026
1027 use std::collections::HashSet;
1028
1029 macro_rules! read_table {
1030 ($file:ident, $scope:expr, $tag:path, $t:ty) => {
1031 $file
1032 .read_table(&$scope, $tag)
1033 .expect("error reading table")
1034 .expect("no table found")
1035 .read::<$t>()
1036 .expect("unable to parse")
1037 };
1038 ($file:ident, $scope:expr, $tag:path, $t:ty, $args:expr) => {
1039 $file
1040 .read_table(&$scope, $tag)
1041 .expect("error reading table")
1042 .expect("no table found")
1043 .read_dep::<$t>($args)
1044 .expect("unable to parse")
1045 };
1046 }
1047
1048 #[test]
1049 fn create_glyf_and_hmtx() {
1050 let buffer = read_fixture("tests/fonts/opentype/SFNT-TTF-Composite.ttf");
1051 let fontfile = ReadScope::new(&buffer)
1052 .read::<OpenTypeFont<'_>>()
1053 .expect("error reading OpenTypeFile");
1054 let font = match fontfile.data {
1055 OpenTypeData::Single(font) => font,
1056 OpenTypeData::Collection(_) => unreachable!(),
1057 };
1058 let head = read_table!(font, fontfile.scope, tag::HEAD, HeadTable);
1059 let maxp = read_table!(font, fontfile.scope, tag::MAXP, MaxpTable);
1060 let hhea = read_table!(font, fontfile.scope, tag::HHEA, HheaTable);
1061 let loca = read_table!(
1062 font,
1063 fontfile.scope,
1064 tag::LOCA,
1065 LocaTable<'_>,
1066 (maxp.num_glyphs, head.index_to_loc_format)
1067 );
1068 let glyf = read_table!(font, fontfile.scope, tag::GLYF, GlyfTable<'_>, &loca);
1069 let hmtx = read_table!(
1070 font,
1071 fontfile.scope,
1072 tag::HMTX,
1073 HmtxTable<'_>,
1074 (
1075 usize::from(maxp.num_glyphs),
1076 usize::from(hhea.num_h_metrics),
1077 )
1078 );
1079
1080 let glyph_ids = [0, 2, 4];
1084 let subset_glyphs = glyf.subset(&glyph_ids).unwrap();
1085 let expected_glyf = GlyfTable::new(vec![
1086 GlyfRecord::empty(),
1087 GlyfRecord::Parsed(Glyph::Composite(CompositeGlyph {
1088 bounding_box: BoundingBox {
1089 x_min: 205,
1090 x_max: 4514,
1091 y_min: 0,
1092 y_max: 1434,
1093 },
1094 glyphs: vec![
1095 CompositeGlyphComponent {
1096 flags: CompositeGlyphFlag::ARG_1_AND_2_ARE_WORDS
1097 | CompositeGlyphFlag::ARGS_ARE_XY_VALUES
1098 | CompositeGlyphFlag::ROUND_XY_TO_GRID
1099 | CompositeGlyphFlag::MORE_COMPONENTS
1100 | CompositeGlyphFlag::UNSCALED_COMPONENT_OFFSET,
1101 glyph_index: 3,
1102 argument1: CompositeGlyphArgument::I16(3453),
1103 argument2: CompositeGlyphArgument::I16(0),
1104 scale: None,
1105 },
1106 CompositeGlyphComponent {
1107 flags: CompositeGlyphFlag::ARG_1_AND_2_ARE_WORDS
1108 | CompositeGlyphFlag::ARGS_ARE_XY_VALUES
1109 | CompositeGlyphFlag::ROUND_XY_TO_GRID
1110 | CompositeGlyphFlag::MORE_COMPONENTS
1111 | CompositeGlyphFlag::UNSCALED_COMPONENT_OFFSET,
1112 glyph_index: 4,
1113 argument1: CompositeGlyphArgument::I16(2773),
1114 argument2: CompositeGlyphArgument::I16(0),
1115 scale: None,
1116 },
1117 CompositeGlyphComponent {
1118 flags: CompositeGlyphFlag::ARG_1_AND_2_ARE_WORDS
1119 | CompositeGlyphFlag::ARGS_ARE_XY_VALUES
1120 | CompositeGlyphFlag::ROUND_XY_TO_GRID
1121 | CompositeGlyphFlag::MORE_COMPONENTS
1122 | CompositeGlyphFlag::UNSCALED_COMPONENT_OFFSET,
1123 glyph_index: 5,
1124 argument1: CompositeGlyphArgument::I16(1182),
1125 argument2: CompositeGlyphArgument::I16(0),
1126 scale: None,
1127 },
1128 CompositeGlyphComponent {
1129 flags: CompositeGlyphFlag::ARG_1_AND_2_ARE_WORDS
1130 | CompositeGlyphFlag::ARGS_ARE_XY_VALUES
1131 | CompositeGlyphFlag::ROUND_XY_TO_GRID
1132 | CompositeGlyphFlag::UNSCALED_COMPONENT_OFFSET,
1133 glyph_index: 2,
1134 argument1: CompositeGlyphArgument::I16(205),
1135 argument2: CompositeGlyphArgument::I16(0),
1136 scale: None,
1137 },
1138 ],
1139 instructions: Box::default(),
1140 phantom_points: None,
1141 })),
1142 GlyfRecord::Parsed(Glyph::Simple(SimpleGlyph {
1143 bounding_box: BoundingBox {
1144 x_min: 0,
1145 x_max: 1073,
1146 y_min: 0,
1147 y_max: 1434,
1148 },
1149 end_pts_of_contours: vec![9],
1150 instructions: Box::default(),
1151 coordinates: vec![
1152 (
1153 SimpleGlyphFlag::ON_CURVE_POINT
1154 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1155 Point(0, 1434),
1156 ),
1157 (
1158 SimpleGlyphFlag::ON_CURVE_POINT
1159 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1160 Point(1073, 1434),
1161 ),
1162 (
1163 SimpleGlyphFlag::ON_CURVE_POINT
1164 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1165 Point(1073, 1098),
1166 ),
1167 (
1168 SimpleGlyphFlag::ON_CURVE_POINT
1169 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1170 Point(485, 1098),
1171 ),
1172 (
1173 SimpleGlyphFlag::ON_CURVE_POINT
1174 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1175 Point(485, 831),
1176 ),
1177 (
1178 SimpleGlyphFlag::ON_CURVE_POINT
1179 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1180 Point(987, 831),
1181 ),
1182 (
1183 SimpleGlyphFlag::ON_CURVE_POINT
1184 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1185 Point(987, 500),
1186 ),
1187 (
1188 SimpleGlyphFlag::ON_CURVE_POINT
1189 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1190 Point(485, 500),
1191 ),
1192 (
1193 SimpleGlyphFlag::ON_CURVE_POINT
1194 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1195 Point(485, 0),
1196 ),
1197 (
1198 SimpleGlyphFlag::ON_CURVE_POINT
1199 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1200 Point::zero(),
1201 ),
1202 ],
1203 phantom_points: None,
1204 })),
1205 GlyfRecord::Parsed(Glyph::Simple(SimpleGlyph {
1206 bounding_box: BoundingBox {
1207 x_min: 0,
1208 x_max: 1061,
1209 y_min: 0,
1210 y_max: 1434,
1211 },
1212 end_pts_of_contours: vec![5],
1213 instructions: Box::default(),
1214 coordinates: vec![
1215 (
1216 SimpleGlyphFlag::ON_CURVE_POINT
1217 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1218 Point(0, 1434),
1219 ),
1220 (
1221 SimpleGlyphFlag::ON_CURVE_POINT
1222 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1223 Point(485, 1434),
1224 ),
1225 (
1226 SimpleGlyphFlag::ON_CURVE_POINT
1227 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1228 Point(485, 369),
1229 ),
1230 (
1231 SimpleGlyphFlag::ON_CURVE_POINT
1232 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1233 Point(1061, 369),
1234 ),
1235 (
1236 SimpleGlyphFlag::ON_CURVE_POINT
1237 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1238 Point(1061, 0),
1239 ),
1240 (
1241 SimpleGlyphFlag::ON_CURVE_POINT
1242 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1243 Point::zero(),
1244 ),
1245 ],
1246 phantom_points: None,
1247 })),
1248 GlyfRecord::Parsed(Glyph::Simple(SimpleGlyph {
1249 bounding_box: BoundingBox {
1250 x_min: 0,
1251 x_max: 485,
1252 y_min: 0,
1253 y_max: 1434,
1254 },
1255 end_pts_of_contours: vec![3],
1256 instructions: Box::default(),
1257 coordinates: vec![
1258 (
1259 SimpleGlyphFlag::ON_CURVE_POINT
1260 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1261 Point(0, 1434),
1262 ),
1263 (
1264 SimpleGlyphFlag::ON_CURVE_POINT
1265 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1266 Point(485, 1434),
1267 ),
1268 (
1269 SimpleGlyphFlag::ON_CURVE_POINT
1270 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1271 Point(485, 0),
1272 ),
1273 (
1274 SimpleGlyphFlag::ON_CURVE_POINT
1275 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1276 Point::zero(),
1277 ),
1278 ],
1279 phantom_points: None,
1280 })),
1281 GlyfRecord::Parsed(Glyph::Simple(SimpleGlyph {
1282 bounding_box: BoundingBox {
1283 x_min: 0,
1284 x_max: 1478,
1285 y_min: 0,
1286 y_max: 1434,
1287 },
1288 end_pts_of_contours: vec![7, 10],
1289 instructions: Box::default(),
1290 coordinates: vec![
1291 (
1292 SimpleGlyphFlag::ON_CURVE_POINT
1293 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR
1294 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1295 Point::zero(),
1296 ),
1297 (SimpleGlyphFlag::ON_CURVE_POINT.into(), Point(436, 1434)),
1298 (
1299 SimpleGlyphFlag::ON_CURVE_POINT
1300 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1301 Point(1042, 1434),
1302 ),
1303 (SimpleGlyphFlag::ON_CURVE_POINT.into(), Point(1478, 0)),
1304 (
1305 SimpleGlyphFlag::ON_CURVE_POINT
1306 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1307 Point(975, 0),
1308 ),
1309 (
1310 SimpleGlyphFlag::ON_CURVE_POINT
1311 | SimpleGlyphFlag::X_SHORT_VECTOR
1312 | SimpleGlyphFlag::Y_SHORT_VECTOR
1313 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1314 Point(909, 244),
1315 ),
1316 (
1317 SimpleGlyphFlag::ON_CURVE_POINT
1318 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1319 Point(493, 244),
1320 ),
1321 (
1322 SimpleGlyphFlag::ON_CURVE_POINT
1323 | SimpleGlyphFlag::X_SHORT_VECTOR
1324 | SimpleGlyphFlag::Y_SHORT_VECTOR,
1325 Point(430, 0),
1326 ),
1327 (
1328 SimpleGlyphFlag::ON_CURVE_POINT
1329 | SimpleGlyphFlag::X_SHORT_VECTOR
1330 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
1331 Point(579, 565),
1332 ),
1333 (
1334 SimpleGlyphFlag::ON_CURVE_POINT
1335 | SimpleGlyphFlag::X_SHORT_VECTOR
1336 | SimpleGlyphFlag::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR
1337 | SimpleGlyphFlag::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
1338 Point(825, 565),
1339 ),
1340 (
1341 SimpleGlyphFlag::ON_CURVE_POINT | SimpleGlyphFlag::X_SHORT_VECTOR,
1342 Point(702, 1032),
1343 ),
1344 ],
1345 phantom_points: None,
1346 })),
1347 ])
1348 .unwrap();
1349
1350 let num_h_metrics = usize::from(hhea.num_h_metrics);
1351 let hmtx = create_hmtx_table(&hmtx, num_h_metrics, &subset_glyphs).unwrap();
1352
1353 let mut glyf: GlyfTable<'_> = subset_glyphs.into();
1354 glyf.records_mut()
1355 .iter_mut()
1356 .for_each(|rec| rec.parse().unwrap());
1357 assert_eq!(glyf, expected_glyf);
1358
1359 let expected = vec![
1360 LongHorMetric {
1361 advance_width: 1536,
1362 lsb: 0,
1363 },
1364 LongHorMetric {
1365 advance_width: 4719,
1366 lsb: 205,
1367 },
1368 LongHorMetric {
1369 advance_width: 0,
1370 lsb: 0,
1371 },
1372 LongHorMetric {
1373 advance_width: 0,
1374 lsb: 0,
1375 },
1376 LongHorMetric {
1377 advance_width: 0,
1378 lsb: 0,
1379 },
1380 LongHorMetric {
1381 advance_width: 0,
1382 lsb: 0,
1383 },
1384 ];
1385
1386 assert_eq!(hmtx.h_metrics.iter().collect::<Vec<_>>(), expected);
1387 assert_eq!(hmtx.left_side_bearings.iter().collect::<Vec<_>>(), vec![]);
1388 }
1389
1390 #[test]
1391 fn font_builder() {
1392 let buffer = read_fixture("tests/fonts/opentype/test-font.ttf");
1395 let fontfile = ReadScope::new(&buffer)
1396 .read::<OpenTypeFont<'_>>()
1397 .expect("error reading OpenTypeFile");
1398 let font = match fontfile.data {
1399 OpenTypeData::Single(font) => font,
1400 OpenTypeData::Collection(_) => unreachable!(),
1401 };
1402 let head = read_table!(font, fontfile.scope, tag::HEAD, HeadTable);
1403 let maxp = read_table!(font, fontfile.scope, tag::MAXP, MaxpTable);
1404 let hhea = read_table!(font, fontfile.scope, tag::HHEA, HheaTable);
1405 let loca = read_table!(
1406 font,
1407 fontfile.scope,
1408 tag::LOCA,
1409 LocaTable<'_>,
1410 (maxp.num_glyphs, head.index_to_loc_format)
1411 );
1412 let glyf = read_table!(font, fontfile.scope, tag::GLYF, GlyfTable<'_>, &loca);
1413 let hmtx = read_table!(
1414 font,
1415 fontfile.scope,
1416 tag::HMTX,
1417 HmtxTable<'_>,
1418 (
1419 usize::from(maxp.num_glyphs),
1420 usize::from(hhea.num_h_metrics),
1421 )
1422 );
1423
1424 let mut builder = FontBuilder::new(tables::TTF_MAGIC, TableFilter::All);
1425 builder
1426 .add_table::<_, HheaTable>(tag::HHEA, &hhea, ())
1427 .unwrap();
1428 builder
1429 .add_table::<_, HmtxTable<'_>>(tag::HMTX, &hmtx, ())
1430 .unwrap();
1431 builder
1432 .add_table::<_, MaxpTable>(tag::MAXP, &maxp, ())
1433 .unwrap();
1434
1435 let tables_added = [
1436 tag::HEAD,
1437 tag::GLYF,
1438 tag::HHEA,
1439 tag::HMTX,
1440 tag::MAXP,
1441 tag::LOCA,
1442 ]
1443 .iter()
1444 .collect::<HashSet<&u32>>();
1445 for record in font.table_records.iter() {
1446 if tables_added.contains(&record.table_tag) {
1447 continue;
1448 }
1449
1450 let table = font
1451 .read_table(&fontfile.scope, record.table_tag)
1452 .unwrap()
1453 .unwrap();
1454 builder
1455 .add_table::<_, ReadScope<'_>>(record.table_tag, table, ())
1456 .unwrap();
1457 }
1458
1459 let mut builder = builder.add_head_table(&head).unwrap();
1460 builder.add_glyf_table(glyf).unwrap();
1461 let data = builder.data().unwrap();
1462
1463 let new_fontfile = ReadScope::new(&data)
1464 .read::<OpenTypeFont<'_>>()
1465 .expect("error reading new OpenTypeFile");
1466 let new_font = match new_fontfile.data {
1467 OpenTypeData::Single(font) => font,
1468 OpenTypeData::Collection(_) => unreachable!(),
1469 };
1470
1471 assert_eq!(new_font.table_records.len(), font.table_records.len());
1472 for record in font.table_records.iter() {
1473 match record.table_tag {
1474 tag::GLYF | tag::LOCA => {
1475 continue;
1480 }
1481 tag => {
1482 let new_record = new_font.find_table_record(record.table_tag).unwrap();
1483 let tag = DisplayTag(tag);
1484 assert_eq!((tag, new_record.checksum), (tag, record.checksum));
1485 }
1486 }
1487 }
1488 }
1489
1490 #[test]
1491 fn invalid_glyph_id() {
1492 let buffer = read_fixture("tests/fonts/opentype/Klei.otf");
1494 let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1495 let mut glyph_ids = [0, 9999];
1496
1497 match subset(
1498 &opentype_file.table_provider(0).unwrap(),
1499 &mut glyph_ids,
1500 &SubsetProfile::Pdf,
1501 CmapTarget::Unrestricted,
1502 ) {
1503 Err(SubsetError::Parse(ParseError::BadIndex)) => {}
1504 err => panic!(
1505 "expected SubsetError::Parse(ParseError::BadIndex) got {:?}",
1506 err
1507 ),
1508 }
1509 }
1510
1511 #[test]
1512 fn empty_mappings_to_keep() {
1513 let buffer = read_fixture("tests/fonts/opentype/SourceCodePro-Regular.otf");
1515 let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1516 let mut glyph_ids = [0, 118];
1518 let subset_font_data = subset(
1519 &opentype_file.table_provider(0).unwrap(),
1520 &mut glyph_ids,
1521 &SubsetProfile::Pdf,
1522 CmapTarget::Unrestricted,
1523 )
1524 .unwrap();
1525
1526 let opentype_file = ReadScope::new(&subset_font_data)
1527 .read::<OpenTypeFont<'_>>()
1528 .unwrap();
1529 let font = Font::new(opentype_file.table_provider(0).unwrap()).unwrap();
1530 let cmap = ReadScope::new(font.cmap_subtable_data())
1531 .read::<CmapSubtable<'_>>()
1532 .unwrap();
1533
1534 if let CmapSubtable::Format0 { glyph_id_array, .. } = cmap {
1537 assert!(glyph_id_array.iter().all(|x| x == 0));
1538 } else {
1539 panic!("expected cmap sub-table format 0");
1540 }
1541 }
1542
1543 #[test]
1544 fn ttf_mappings_to_keep_is_none() {
1545 let buffer = read_fixture("tests/fonts/opentype/test-font.ttf");
1548 let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1549 let mut glyph_ids = [0, 2];
1550 let subset_font_data = subset_ttf(
1551 &opentype_file.table_provider(0).unwrap(),
1552 &mut glyph_ids,
1553 CmapStrategy::Omit,
1554 &SubsetProfile::Pdf,
1555 )
1556 .unwrap();
1557
1558 let opentype_file = ReadScope::new(&subset_font_data)
1559 .read::<OpenTypeFont<'_>>()
1560 .unwrap();
1561 let table_provider = opentype_file.table_provider(0).unwrap();
1562 assert!(!table_provider.has_table(tag::CMAP));
1563 }
1564
1565 #[test]
1567 fn test_whole_font() {
1568 let buffer = read_fixture("tests/fonts/opentype/Klei.otf");
1569 let scope = ReadScope::new(&buffer);
1570 let font_file = scope
1571 .read::<FontData<'_>>()
1572 .expect("unable to read FontFile");
1573 let provider = font_file
1574 .table_provider(0)
1575 .expect("unable to get FontTableProvider");
1576 let tags = [
1577 tag::CFF,
1578 tag::GDEF,
1579 tag::GPOS,
1580 tag::GSUB,
1581 tag::OS_2,
1582 tag::CMAP,
1583 tag::HEAD,
1584 tag::HHEA,
1585 tag::HMTX,
1586 tag::MAXP,
1587 tag::NAME,
1588 tag::POST,
1589 ];
1590 assert!(whole_font(&provider, &tags).is_ok());
1591 }
1592
1593 #[test]
1594 fn test_max_power_of_2() {
1595 assert_eq!(max_power_of_2(0), 0);
1596 assert_eq!(max_power_of_2(1), 0);
1597 assert_eq!(max_power_of_2(2), 1);
1598 assert_eq!(max_power_of_2(4), 2);
1599 assert_eq!(max_power_of_2(8), 3);
1600 assert_eq!(max_power_of_2(16), 4);
1601 assert_eq!(max_power_of_2(49), 5);
1602 assert_eq!(max_power_of_2(std::u16::MAX), 15);
1603 }
1604
1605 #[test]
1606 fn subset_cff2_type1() {
1607 let buffer = read_fixture("tests/fonts/opentype/cff2/SourceSans3.abc.otf");
1608 let otf = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1609 let provider = otf.table_provider(0).expect("error reading font file");
1610
1611 let new_font = subset(
1614 &provider,
1615 &[0, 1],
1616 &SubsetProfile::Pdf,
1617 CmapTarget::Unrestricted,
1618 )
1619 .unwrap();
1620
1621 let subset_otf = ReadScope::new(&new_font)
1623 .read::<OpenTypeFont<'_>>()
1624 .unwrap();
1625 let provider = subset_otf
1626 .table_provider(0)
1627 .expect("error reading new font");
1628 let cff_data = provider
1629 .read_table_data(tag::CFF)
1630 .expect("unable to read CFF data");
1631 let res = ReadScope::new(&cff_data).read::<CFF<'_>>();
1632 assert!(res.is_ok());
1633 let cff = res.unwrap();
1634 let font = &cff.fonts[0];
1635 assert!(!font.is_cid_keyed());
1636 }
1637
1638 #[test]
1639 fn subset_cff2_cid() {
1640 let buffer = read_fixture("tests/fonts/opentype/cff2/SourceSans3-Instance.256.otf");
1641 let otf = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1642 let provider = otf.table_provider(0).expect("error reading font file");
1643
1644 let glyph_ids = (0..=256).collect::<Vec<_>>();
1647 let new_font = subset(
1648 &provider,
1649 &glyph_ids,
1650 &SubsetProfile::Pdf,
1651 CmapTarget::Unrestricted,
1652 )
1653 .unwrap();
1654
1655 let subset_otf = ReadScope::new(&new_font)
1657 .read::<OpenTypeFont<'_>>()
1658 .unwrap();
1659 let provider = subset_otf
1660 .table_provider(0)
1661 .expect("error reading new font");
1662 let cff_data = provider
1663 .read_table_data(tag::CFF)
1664 .expect("unable to read CFF data");
1665 let res = ReadScope::new(&cff_data).read::<CFF<'_>>();
1666 assert!(res.is_ok());
1667 let cff = res.unwrap();
1668 assert_eq!(cff.fonts.len(), 1);
1669 let font = &cff.fonts[0];
1670 assert!(font.is_cid_keyed());
1671 }
1672
1673 #[test]
1674 fn test_subset_with_os2_and_unicode_cmap() {
1675 let test_string = "hello world";
1677
1678 let buffer = read_fixture("tests/fonts/opentype/Klei.otf");
1680 let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1681 let provider = opentype_file.table_provider(0).unwrap();
1682
1683 let font = Font::new(provider).unwrap();
1685
1686 let cmap_data = font.cmap_subtable_data();
1688 let cmap_subtable = ReadScope::new(cmap_data)
1689 .read::<CmapSubtable<'_>>()
1690 .unwrap();
1691
1692 let mut glyph_ids = vec![0]; for c in test_string.chars() {
1696 if let Ok(Some(glyph_id)) = cmap_subtable.map_glyph(c as u32) {
1697 glyph_ids.push(glyph_id);
1698 }
1699 }
1700
1701 glyph_ids.sort();
1703 glyph_ids.dedup();
1704
1705 let subset_buffer = subset(
1707 &font.font_table_provider,
1708 &glyph_ids,
1709 &SubsetProfile::Minimal,
1710 CmapTarget::Unicode,
1711 )
1712 .unwrap();
1713 drop(font); let subset_otf = ReadScope::new(&subset_buffer)
1717 .read::<OpenTypeFont<'_>>()
1718 .unwrap();
1719 let subset_provider = subset_otf.table_provider(0).unwrap();
1720
1721 assert!(
1723 subset_provider.has_table(tag::OS_2),
1724 "Subset font is missing the OS/2 table."
1725 );
1726
1727 let cmap_data = subset_provider.read_table_data(tag::CMAP).unwrap();
1729 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().unwrap();
1730 assert!(
1731 cmap.find_subtable(PlatformId::UNICODE, EncodingId::UNICODE_BMP)
1732 .is_some(),
1733 "subset font does not have expected Unicode cmap"
1734 );
1735 }
1736
1737 #[test]
1738 fn test_subset_with_macroman_cmap() {
1739 let test_string = "hello world";
1741
1742 let buffer = read_fixture("tests/fonts/opentype/Klei.otf");
1744 let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1745 let provider = opentype_file.table_provider(0).unwrap();
1746
1747 let font = Font::new(provider).unwrap();
1749
1750 let cmap_data = font.cmap_subtable_data();
1752 let cmap_subtable = ReadScope::new(cmap_data)
1753 .read::<CmapSubtable<'_>>()
1754 .unwrap();
1755
1756 let mut glyph_ids = vec![0]; for c in test_string.chars() {
1760 if let Ok(Some(glyph_id)) = cmap_subtable.map_glyph(c as u32) {
1761 glyph_ids.push(glyph_id);
1762 }
1763 }
1764
1765 glyph_ids.sort();
1767 glyph_ids.dedup();
1768
1769 let subset_buffer = subset(
1771 &font.font_table_provider,
1772 &glyph_ids,
1773 &SubsetProfile::Minimal,
1774 CmapTarget::Unrestricted,
1775 )
1776 .unwrap();
1777 drop(font); let subset_otf = ReadScope::new(&subset_buffer)
1780 .read::<OpenTypeFont<'_>>()
1781 .unwrap();
1782 let subset_provider = subset_otf.table_provider(0).unwrap();
1783
1784 let cmap_data = subset_provider.read_table_data(tag::CMAP).unwrap();
1787 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().unwrap();
1788 assert!(
1789 cmap.find_subtable(PlatformId::MACINTOSH, EncodingId::MACINTOSH_APPLE_ROMAN)
1790 .is_some(),
1791 "subset font does not have expected Mac Roman cmap"
1792 );
1793 }
1794
1795 #[test]
1796 fn parse_custom_profile() {
1797 let tables = "fpGm,OS/2 os2,GSUB".to_string();
1798 let custom = SubsetProfile::parse_custom(tables)
1799 .unwrap()
1800 .get_tables(&[])
1801 .iter()
1802 .copied()
1803 .map(|table| DisplayTag(table).to_string())
1804 .collect::<Vec<_>>();
1805 let expected = vec![
1806 tag::GSUB,
1807 tag::OS_2,
1808 tag::CMAP,
1809 tag::FPGM,
1810 tag::HEAD,
1811 tag::HHEA,
1812 tag::HMTX,
1813 tag::MAXP,
1814 tag::NAME,
1815 tag::POST,
1816 ]
1817 .into_iter()
1818 .map(|table| DisplayTag(table).to_string())
1819 .collect::<Vec<_>>();
1820
1821 assert_eq!(custom, expected)
1822 }
1823
1824 #[test]
1825 fn parse_custom_profile_invalid() {
1826 assert!(SubsetProfile::parse_custom("toolong".to_string()).is_err());
1827 assert!(SubsetProfile::parse_custom("👓".to_string()).is_err());
1828 }
1829
1830 #[test]
1831 fn notdef_only_cff_produces_valid_font() {
1832 let buffer = read_fixture("tests/fonts/opentype/Klei.otf");
1835 let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1836 let glyph_ids = [0];
1837
1838 let subset_buffer = subset(
1839 &opentype_file.table_provider(0).unwrap(),
1840 &glyph_ids,
1841 &SubsetProfile::Pdf,
1842 CmapTarget::Unicode,
1843 )
1844 .unwrap();
1845
1846 let subset_otf = ReadScope::new(&subset_buffer)
1848 .read::<OpenTypeFont<'_>>()
1849 .unwrap();
1850 let subset_provider = subset_otf.table_provider(0).unwrap();
1851 let cmap_data = subset_provider.read_table_data(tag::CMAP).unwrap();
1852 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().unwrap();
1853 assert!(
1854 cmap.find_subtable(PlatformId::UNICODE, EncodingId::UNICODE_BMP)
1855 .is_some(),
1856 "subset font should have a Unicode BMP cmap subtable"
1857 );
1858 }
1859
1860 #[test]
1861 fn notdef_only_ttf_produces_valid_font() {
1862 let buffer = read_fixture("tests/fonts/opentype/test-font.ttf");
1864 let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1865 let glyph_ids = [0];
1866
1867 let subset_buffer = subset(
1868 &opentype_file.table_provider(0).unwrap(),
1869 &glyph_ids,
1870 &SubsetProfile::Pdf,
1871 CmapTarget::Unicode,
1872 )
1873 .unwrap();
1874
1875 let subset_otf = ReadScope::new(&subset_buffer)
1877 .read::<OpenTypeFont<'_>>()
1878 .unwrap();
1879 let subset_provider = subset_otf.table_provider(0).unwrap();
1880 let cmap_data = subset_provider.read_table_data(tag::CMAP).unwrap();
1881 let cmap = ReadScope::new(&cmap_data).read::<Cmap<'_>>().unwrap();
1882 assert!(
1883 cmap.find_subtable(PlatformId::UNICODE, EncodingId::UNICODE_BMP)
1884 .is_some(),
1885 "subset font should have a Unicode BMP cmap subtable"
1886 );
1887 }
1888}