1#![deny(missing_docs)]
2
3use 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#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum SubsetProfile {
34 Minimal,
36 Web,
38 Full,
40 Custom(Vec<u32>),
42}
43
44impl Default for SubsetProfile {
45 fn default() -> Self {
46 SubsetProfile::Minimal
47 }
48}
49
50const PROFILE_MINIMAL: &[u32] = &[]; const 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
65const 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, tag::GSUB, tag::VHEA, tag::VMTX, tag::GDEF, tag::CVT, tag::FPGM, tag::PREP, ];
84
85const UNICODE_RANGES: &[(u32, u32, u32)] = &[
92 (0x0000, 0x007F, 0), (0x0080, 0x00FF, 1), (0x0100, 0x017F, 2), (0x0180, 0x024F, 3), (0x0250, 0x02AF, 4), (0x02B0, 0x02FF, 5), (0x0300, 0x036F, 6), (0x0370, 0x03FF, 7), (0x0400, 0x04FF, 9), (0x0530, 0x058F, 10), (0x0590, 0x05FF, 11), (0x0600, 0x06FF, 12), (0x0700, 0x074F, 13), (0x0750, 0x077F, 14), (0x0780, 0x07BF, 15), (0x07C0, 0x07FF, 16), (0x0800, 0x083F, 17), (0x0840, 0x085F, 18), (0x0860, 0x086F, 19), (0x08A0, 0x08FF, 20), (0x0900, 0x097F, 21), (0x0980, 0x09FF, 22), (0x0A00, 0x0A7F, 23), (0x0A80, 0x0AFF, 24), (0x0B00, 0x0B7F, 25), (0x0B80, 0x0BFF, 26), (0x0C00, 0x0C7F, 27), (0x0C80, 0x0CFF, 28), (0x0D00, 0x0D7F, 29), (0x0D80, 0x0DFF, 30), (0x0E00, 0x0E7F, 31), (0x0E80, 0x0EFF, 32), (0x0F00, 0x0FFF, 33), (0x1000, 0x109F, 34), (0x10A0, 0x10FF, 35), (0x1100, 0x11FF, 36), (0x1E00, 0x1EFF, 37), (0x1F00, 0x1FFF, 38), ];
131
132impl SubsetProfile {
133 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 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#[derive(Debug)]
180pub enum SubsetError {
181 Parse(ParseError),
183 Write(WriteError),
185 CFF(CFFError),
187 NotDef,
189 TooManyGlyphs,
191 InvalidFontCount,
194}
195
196pub(crate) trait SubsetGlyphs {
197 fn len(&self) -> usize;
199
200 fn old_id(&self, new_id: u16) -> u16;
202
203 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 let (new_first, new_last) = if mappings.is_empty() {
231 (0, 0) } 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 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, 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
284pub 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
321fn subset_ttf(
326 provider: &impl FontTableProvider,
327 glyph_ids: &[u16],
328 cmap_strategy: CmapStrategy,
329 profile: &SubsetProfile,
330) -> Result<Vec<u8>, ReadWriteError> {
331 let profile_tables = profile.get_tables();
333
334 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 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 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 let post_data = provider.read_table_data(tag::POST)?;
376 let mut post = ReadScope::new(&post_data).read::<PostTable<'_>>()?;
377 post.header.version = 0x00030000; post.opt_sub_table = None;
379
380 let subset_glyphs = glyf.subset(glyph_ids)?;
382
383 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 let num_glyphs = u16::try_from(subset_glyphs.len()).map_err(ParseError::from)?;
397 maxp.num_glyphs = num_glyphs;
398
399 let num_h_metrics = usize::from(hhea.num_h_metrics);
401 hhea.num_h_metrics = num_glyphs;
402
403 let hmtx = create_hmtx_table(&hmtx, num_h_metrics, &subset_glyphs)?;
405
406 let glyf = GlyfTable::from(subset_glyphs);
408
409 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 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 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 let cff_subset = cff2
504 .subset_to_cff(glyph_ids, provider, include_fstype, output_format)?
505 .into();
506
507 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 let profile_tables = profile.get_tables();
532
533 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 let post_data = provider.read_table_data(tag::POST)?;
552 let mut post = ReadScope::new(&post_data).read::<PostTable<'_>>()?;
553 post.header.version = 0x00030000; post.opt_sub_table = None;
555
556 let cmap = create_cmap_table(&mappings_to_keep)?;
558
559 let num_glyphs = u16::try_from(cff_subset.len()).map_err(ParseError::from)?;
561 maxp.num_glyphs = num_glyphs;
562
563 let num_h_metrics = usize::from(hhea.num_h_metrics);
565 hhea.num_h_metrics = num_glyphs;
566
567 let hmtx = create_hmtx_table(hmtx, num_h_metrics, &cff_subset)?;
569
570 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 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 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, glyph_id_array: cmap,
632 },
633 }],
634 })
635}
636
637pub 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 builder.add_table::<_, MaxpTable>(tag::MAXP, &maxp, ())?;
667 let mut builder_with_head = builder.add_head_table(&head)?;
668
669 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 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 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 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 let mut ordered_tables = self.write_table_directory(&mut font)?;
788
789 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 let headers_checksum = checksum::table_checksum(font.bytes())?;
800 let checksum = Wrapping(0xB1B0AFBA) - (headers_checksum + ordered_tables.checksum);
801
802 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
866fn max_power_of_2(num: u16) -> u16 {
868 15u16.saturating_sub(num.leading_zeros() as u16)
869}
870
871fn 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#[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 #[derive(Debug, Clone)]
900 pub enum PrinceCmapTarget {
901 Unrestricted,
903 MacRoman,
905 Omit,
907 MacRomanCmap(Box<[u8; 256]>),
909 }
910
911 impl PrinceCmapTarget {
912 pub fn new(tag: c_int, cmap: Option<Box<[u8; 256]>>) -> Self {
914 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 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 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 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 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 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 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 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 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 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 let buffer = read_fixture("tests/fonts/opentype/SourceCodePro-Regular.otf");
1552 let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1553 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 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 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 #[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 let new_font = subset(&provider, &[0, 1], &SubsetProfile::Minimal).unwrap();
1650
1651 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 let glyph_ids = (0..=256).collect::<Vec<_>>();
1677 let new_font = subset(&provider, &glyph_ids, &SubsetProfile::Minimal).unwrap();
1678
1679 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}