1#![deny(missing_docs)]
4
5use std::borrow::Cow;
6use std::convert::{TryFrom, TryInto};
7use std::fmt;
8use std::fmt::Write;
9use std::str::FromStr;
10
11use pathfinder_geometry::rect::RectI;
12use pathfinder_geometry::vector::vec2i;
13use rustc_hash::FxHashSet;
14
15use crate::binary::read::{ReadArrayCow, ReadScope};
16use crate::cff::cff2::CFF2;
17use crate::cff::CFFError;
18use crate::error::{ParseError, ReadWriteError, WriteError};
19use crate::post::PostTable;
20use crate::subset::FontBuilder;
21use crate::tables::glyf::{BoundingBox, GlyfRecord, GlyfTable, Glyph};
22use crate::tables::loca::LocaTable;
23use crate::tables::os2::{FsSelection, Os2};
24use crate::tables::variable_fonts::avar::AvarTable;
25use crate::tables::variable_fonts::cvar::CvarTable;
26use crate::tables::variable_fonts::fvar::FvarTable;
27use crate::tables::variable_fonts::gvar::GvarTable;
28use crate::tables::variable_fonts::hvar::HvarTable;
29use crate::tables::variable_fonts::mvar::MvarTable;
30use crate::tables::variable_fonts::stat::{ElidableName, StatTable};
31use crate::tables::variable_fonts::OwnedTuple;
32use crate::tables::{
33 owned, CvtTable, Fixed, FontTableProvider, HeadTable, HheaTable, HmtxTable, IndexToLocFormat,
34 LongHorMetric, MacStyle, MaxpTable, NameTable, CFF_MAGIC, TRUE_MAGIC,
35};
36use crate::tag;
37use crate::tag::DisplayTag;
38
39#[derive(Debug)]
41pub enum VariationError {
42 Parse(ParseError),
44 CFF(CFFError),
46 Write(WriteError),
48 NotVariableFont,
50 NotImplemented,
55 NameError,
58 TagError,
60}
61
62enum GlyphData<'a> {
63 Glyf(GlyfTable<'a>),
64 Cff2(CFF2<'a>),
65}
66
67#[derive(Debug, Eq, PartialEq)]
69pub struct NamedAxis<'a> {
70 pub tag: u32,
72 pub name: Cow<'a, str>,
74 pub ordering: u16,
76}
77
78#[derive(Debug, Eq, PartialEq)]
80pub enum AxisNamesError {
81 Parse(ParseError),
83 NoStatTable,
85 NoNameTable,
87}
88
89pub fn axis_names<'a>(
94 provider: &impl FontTableProvider,
95) -> Result<Vec<NamedAxis<'a>>, AxisNamesError> {
96 let stat_data = provider
97 .table_data(tag::STAT)?
98 .ok_or(AxisNamesError::NoStatTable)?;
99 let stat = ReadScope::new(&stat_data).read::<StatTable<'_>>()?;
100 let name_data = provider
101 .table_data(tag::NAME)?
102 .ok_or(AxisNamesError::NoNameTable)?;
103 let name = ReadScope::new(&name_data).read::<NameTable<'_>>()?;
104
105 stat.design_axes()
106 .map(|axis| {
107 let axis = axis?;
108 let name = name
109 .string_for_id(axis.axis_name_id)
110 .map(Cow::from)
111 .unwrap_or_else(|| Cow::from(String::from("Unknown")));
112 Ok(NamedAxis {
113 tag: axis.axis_tag,
114 name,
115 ordering: axis.axis_ordering,
116 })
117 })
118 .collect()
119}
120
121pub fn instance(
128 provider: &impl FontTableProvider,
129 user_instance: &[Fixed],
130) -> Result<(Vec<u8>, OwnedTuple), VariationError> {
131 is_supported_variable_font(provider)?;
132
133 let mut head = ReadScope::new(&provider.read_table_data(tag::HEAD)?).read::<HeadTable>()?;
146 let maxp = ReadScope::new(&provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
147 let loca_data = provider.table_data(tag::LOCA)?;
148 let loca = loca_data
149 .as_ref()
150 .map(|loca_data| {
151 ReadScope::new(loca_data)
152 .read_dep::<LocaTable<'_>>((usize::from(maxp.num_glyphs), head.index_to_loc_format))
153 })
154 .transpose()?;
155
156 let glyf_data = provider.table_data(tag::GLYF)?;
157 let cff2_data = provider.table_data(tag::CFF2)?;
158 let glyph_data = match (&loca, &glyf_data, &cff2_data) {
159 (Some(loca), Some(glyf_data), _) => {
160 let glyf = ReadScope::new(glyf_data).read_dep::<GlyfTable<'_>>(&loca)?;
161 GlyphData::Glyf(glyf)
162 }
163 (_, _, Some(cff2_data)) => {
164 let cff2 = ReadScope::new(cff2_data).read::<CFF2<'_>>()?;
165 GlyphData::Cff2(cff2)
166 }
167 _ => return Err(ParseError::MissingValue.into()),
168 };
169 let mut hhea = ReadScope::new(&provider.read_table_data(tag::HHEA)?).read::<HheaTable>()?;
170 let hmtx_data = provider.read_table_data(tag::HMTX)?;
171 let hmtx = ReadScope::new(&hmtx_data).read_dep::<HmtxTable<'_>>((
172 usize::from(maxp.num_glyphs),
173 usize::from(hhea.num_h_metrics),
174 ))?;
175 let vhea_data = provider.table_data(tag::VHEA)?;
176 let vhea = vhea_data
177 .as_ref()
178 .map(|vhea_data| ReadScope::new(vhea_data).read::<HheaTable>())
179 .transpose()?;
180 let vmtx_data = provider.table_data(tag::VMTX)?;
181 let vmtx = vhea
182 .and_then(|vhea| {
183 vmtx_data.as_ref().map(|vmtx_data| {
184 ReadScope::new(vmtx_data).read_dep::<HmtxTable<'_>>((
185 usize::from(maxp.num_glyphs),
186 usize::from(vhea.num_h_metrics),
187 ))
188 })
189 })
190 .transpose()?;
191
192 let os2_data = provider.read_table_data(tag::OS_2)?;
193 let mut os2 = ReadScope::new(&os2_data).read_dep::<Os2>(os2_data.len())?;
194 let post_data = provider.read_table_data(tag::POST)?;
195 let mut post = ReadScope::new(&post_data).read::<PostTable<'_>>()?;
196 let fvar_data = provider.read_table_data(tag::FVAR)?;
197 let fvar = ReadScope::new(&fvar_data).read::<FvarTable<'_>>()?;
198 let avar_data = provider.table_data(tag::AVAR)?;
199 let avar = avar_data
200 .as_ref()
201 .map(|avar_data| ReadScope::new(avar_data).read::<AvarTable<'_>>())
202 .transpose()?;
203 let cvt_data = provider.table_data(tag::CVT)?;
204 let mut cvt = cvt_data
205 .as_ref()
206 .map(|cvt_data| ReadScope::new(cvt_data).read_dep::<CvtTable<'_>>(cvt_data.len() as u32))
207 .transpose()?;
208 let cvar_data = provider.table_data(tag::CVAR)?;
209 let cvar = cvt
210 .as_ref()
211 .and_then(|cvt| {
212 cvar_data.as_ref().map(|cvar_data| {
213 ReadScope::new(cvar_data)
214 .read_dep::<CvarTable<'_>>((fvar.axis_count(), cvt.values.len() as u32))
215 })
216 })
217 .transpose()?;
218 let gvar_data = provider.table_data(tag::GVAR)?;
219 let gvar = gvar_data
220 .as_ref()
221 .map(|gvar_data| ReadScope::new(gvar_data).read::<GvarTable<'_>>())
222 .transpose()?;
223 let hvar_data = provider.table_data(tag::HVAR)?;
224 let hvar = hvar_data
225 .as_ref()
226 .map(|hvar_data| ReadScope::new(hvar_data).read::<HvarTable<'_>>())
227 .transpose()?;
228 let mvar_data = provider.table_data(tag::MVAR)?;
229 let mvar = mvar_data
230 .as_ref()
231 .map(|mvar_data| ReadScope::new(mvar_data).read::<MvarTable<'_>>())
232 .transpose()?;
233 let stat_data = provider.table_data(tag::STAT)?;
234 let stat = stat_data
235 .as_ref()
236 .map(|stat_data| ReadScope::new(stat_data).read::<StatTable<'_>>())
237 .transpose()?;
238 let name_data = provider.read_table_data(tag::NAME)?;
239 let name = ReadScope::new(&name_data).read::<NameTable<'_>>()?;
240
241 let instance = fvar.normalize(user_instance.iter().copied(), avar.as_ref())?;
242
243 let (glyph_data, hmtx) = match (glyph_data, &gvar) {
245 (GlyphData::Glyf(mut glyf), Some(gvar)) => {
246 glyf = apply_gvar(
247 glyf,
248 gvar,
249 &hmtx,
250 vmtx.as_ref(),
251 Some(&os2),
252 &hhea,
253 &instance,
254 )?;
255
256 let mut bbox = RectI::default();
258 glyf.records().iter().for_each(|glyph| match glyph {
259 GlyfRecord::Present { .. } => {}
260 GlyfRecord::Parsed(glyph) => {
261 if let Some(bounding_box) = glyph.bounding_box() {
262 bbox = union_rect(bbox, bounding_box.into())
263 }
264 }
265 });
266 head.x_min = bbox.min_x().try_into().ok().unwrap_or(i16::MIN);
267 head.y_min = bbox.min_y().try_into().ok().unwrap_or(i16::MIN);
268 head.x_max = bbox.max_x().try_into().ok().unwrap_or(i16::MAX);
269 head.y_max = bbox.max_y().try_into().ok().unwrap_or(i16::MAX);
270
271 let hmtx = create_hmtx_table(&hmtx, hvar.as_ref(), &glyf, &instance, maxp.num_glyphs)?;
273 (GlyphData::Glyf(glyf), hmtx)
274 }
275 (GlyphData::Cff2(mut cff2), _) => {
276 cff2.instance_char_strings(&instance)?;
277 cff2.vstore = None; match &hvar {
279 Some(hvar) => {
282 let hmtx = apply_hvar(&hmtx, hvar, None, &instance, maxp.num_glyphs)?;
283 (GlyphData::Cff2(cff2), hmtx)
284 }
285 None => (GlyphData::Cff2(cff2), hmtx),
287 }
288 }
289 _ => return Err(VariationError::NotImplemented),
295 };
296
297 head.mac_style
299 .set(MacStyle::ITALIC, is_italic(user_instance, &fvar));
300 os2.fs_selection.set(FsSelection::ITALIC, head.is_italic());
301
302 hhea.num_h_metrics = maxp.num_glyphs; hhea.advance_width_max = hmtx
305 .h_metrics
306 .iter()
307 .map(|m| m.advance_width)
308 .max()
309 .unwrap_or(0);
310
311 if let Some(mvar) = &mvar {
313 process_mvar(mvar, &instance, &mut os2, &mut hhea, &mut None, &mut post);
314 }
315
316 for (axis, value) in fvar.axes().zip(user_instance.iter().copied()) {
319 if value == axis.default_value {
320 continue;
321 }
322
323 match axis.axis_tag {
324 tag::WGHT => {
325 os2.us_weight_class = ((f32::from(value).clamp(1., 1000.) / 100.0).round() as u16
328 * 100)
329 .clamp(100, 900);
330 head.mac_style
331 .set(MacStyle::BOLD, os2.us_weight_class >= 600);
332 os2.fs_selection.set(FsSelection::BOLD, head.is_bold());
333 }
334 tag::WDTH => {
335 os2.us_width_class = Os2::value_to_width_class(value);
336 head.mac_style
337 .set(MacStyle::CONDENSED, os2.us_width_class < 4);
338 head.mac_style
339 .set(MacStyle::EXTENDED, os2.us_width_class > 6);
340 }
341 _ => {}
342 }
343 }
344 os2.fs_selection
345 .set(FsSelection::REGULAR, !(head.is_bold() || head.is_italic()));
346
347 if let (Some(cvt), Some(cvar)) = (cvt.as_mut(), cvar) {
348 *cvt = cvar.apply(&instance, cvt)?;
349 }
350
351 let subfamily_name = stat
353 .as_ref()
354 .map(|stat| typographic_subfamily_name(user_instance, &fvar, stat, &name, "Regular"))
355 .unwrap_or_else(|| {
356 name.string_for_id(NameTable::TYPOGRAPHIC_SUBFAMILY_NAME)
357 .or_else(|| name.string_for_id(NameTable::FONT_SUBFAMILY_NAME))
358 .ok_or(VariationError::NameError)
359 })?;
360 let font_family_name = name
361 .string_for_id(NameTable::FONT_FAMILY_NAME)
362 .or_else(|| name.string_for_id(NameTable::TYPOGRAPHIC_FAMILY_NAME))
363 .ok_or(VariationError::NameError)?;
364 let typographic_family = name
365 .string_for_id(NameTable::TYPOGRAPHIC_FAMILY_NAME)
366 .or_else(|| name.string_for_id(NameTable::FONT_FAMILY_NAME))
367 .ok_or(VariationError::NameError)?;
368 let postscript_prefix = name.string_for_id(NameTable::VARIATIONS_POSTSCRIPT_NAME_PREFIX);
369 let mut name = owned::NameTable::try_from(&name)?;
370
371 let full_name = format!("{} {}", typographic_family, subfamily_name);
374 let postscript_name = generate_postscript_name(
375 &postscript_prefix,
376 &typographic_family,
377 user_instance,
378 &fvar,
379 );
380 let unique_id = generate_unique_id(&head, &os2, &postscript_name);
381 name.replace_entries(
382 NameTable::FONT_FAMILY_NAME,
383 &format!("{font_family_name} {subfamily_name}"),
384 );
385 name.replace_entries(NameTable::FONT_SUBFAMILY_NAME, "Regular");
386 name.replace_entries(NameTable::UNIQUE_FONT_IDENTIFIER, &unique_id);
387 name.replace_entries(NameTable::FULL_FONT_NAME, &full_name);
388 name.replace_entries(NameTable::POSTSCRIPT_NAME, &postscript_name);
389 name.replace_entries(NameTable::TYPOGRAPHIC_FAMILY_NAME, &typographic_family);
390 name.replace_entries(NameTable::TYPOGRAPHIC_SUBFAMILY_NAME, &subfamily_name);
391
392 let mut builder = match glyph_data {
394 GlyphData::Cff2(_) => FontBuilder::new(CFF_MAGIC),
395 GlyphData::Glyf(_) => FontBuilder::new(TRUE_MAGIC),
396 };
397 if let Some(cvt) = cvt {
398 builder.add_table::<_, CvtTable<'_>>(tag::CVT, &cvt, ())?;
399 }
400 builder.add_table::<_, HheaTable>(tag::HHEA, &hhea, ())?;
401 builder.add_table::<_, HmtxTable<'_>>(tag::HMTX, &hmtx, ())?;
402 builder.add_table::<_, MaxpTable>(tag::MAXP, &maxp, ())?;
403 builder.add_table::<_, owned::NameTable<'_>>(tag::NAME, &name, ())?;
404 builder.add_table::<_, Os2>(tag::OS_2, &os2, ())?;
405 builder.add_table::<_, PostTable<'_>>(tag::POST, &post, ())?;
406
407 let glyf = match glyph_data {
408 GlyphData::Cff2(cff2) => {
409 builder.add_table::<_, CFF2<'_>>(tag::CFF2, cff2, ())?;
410 None
411 }
412 GlyphData::Glyf(glyf) => Some(glyf),
413 };
414
415 let builder_tables = builder.table_tags().collect::<FxHashSet<_>>();
418 let tags = provider.table_tags().ok_or(VariationError::TagError)?;
419
420 for tag in tags.into_iter().filter(|tag| {
421 ![tag::HEAD, tag::GLYF, tag::LOCA].contains(tag)
423 && !is_var_table(*tag)
424 && !builder_tables.contains(tag)
425 }) {
426 let data = provider.read_table_data(tag)?;
427 builder.add_table::<_, ReadScope<'_>>(tag, ReadScope::new(&data), ())?;
428 }
429
430 head.index_to_loc_format = IndexToLocFormat::Long;
432 let mut builder = builder.add_head_table(&head)?;
433 if let Some(glyf) = glyf {
434 builder.add_glyf_table(glyf)?;
435 }
436 builder
437 .data()
438 .map(|data| (data, instance))
439 .map_err(VariationError::from)
440}
441
442fn typographic_subfamily_name<'a>(
443 user_instance: &[Fixed],
444 fvar: &FvarTable<'a>,
445 stat: &'a StatTable<'a>,
446 name: &NameTable<'a>,
447 default: &str,
448) -> Result<String, VariationError> {
449 let mut names = Vec::new();
450 for (axis, value) in fvar.axes().zip(user_instance.iter().copied()) {
451 for (i, rec) in stat.design_axes().enumerate() {
452 let rec = rec?;
453 if rec.axis_tag == axis.axis_tag {
454 if let Some(name_id) =
455 stat.name_for_axis_value(i as u16, value, ElidableName::Exclude)
456 {
457 names.push((name_id, rec.axis_ordering));
458 }
459 }
460 }
461 }
462 names.sort_by_key(|res| res.1);
464 let names = if names.is_empty() {
465 let name = stat
468 .elided_fallback_name_id
469 .and_then(|name_id| name.string_for_id(name_id))
470 .unwrap_or_else(|| default.to_string());
471 vec![name]
472 } else {
473 names
474 .into_iter()
475 .filter_map(|(name_id, _)| name.string_for_id(name_id))
476 .collect::<Vec<_>>()
477 };
478 Ok(names.join(" "))
479}
480
481fn generate_postscript_name(
483 prefix: &Option<String>,
484 typographic_family: &str,
485 user_tuple: &[Fixed],
486 fvar: &FvarTable<'_>,
487) -> String {
488 let mut prefix: String = prefix
491 .as_deref()
492 .unwrap_or(typographic_family)
493 .chars()
494 .filter(|c| c.is_ascii_alphanumeric())
495 .collect();
496 let mut postscript_name = prefix.clone();
497 fvar.axes()
498 .zip(user_tuple.iter().copied())
499 .for_each(|(axis, value)| {
500 if value != axis.default_value {
501 let tag = DisplayTag(axis.axis_tag).to_string();
504 write!(
505 postscript_name,
506 "_{}{}",
507 fixed_to_min_float(value),
508 tag.trim()
509 )
510 .unwrap();
511 }
512 });
513
514 if postscript_name.len() > 63 {
515 let crc = crc32fast::hash(postscript_name.as_bytes());
517 let hash = format!("-{:X}...", crc);
518 prefix.truncate(63 - hash.len());
521 postscript_name = prefix + &hash;
522 }
523
524 postscript_name
525}
526
527fn generate_unique_id(head: &HeadTable, os2: &Os2, postscript_name: &str) -> String {
528 let version = head.font_revision;
529 let vendor = DisplayTag(os2.ach_vend_id).to_string();
530 format!(
531 "{:.3};{};{}",
532 f32::from(version),
533 vendor.trim(),
534 postscript_name
535 )
536}
537
538const VAR_UPPER: u32 = tag!(b"\0VAR");
539const VAR_LOWER: u32 = tag!(b"\0var");
540
541fn is_var_table(tag: u32) -> bool {
543 ((tag & VAR_LOWER) == VAR_LOWER) || ((tag & VAR_UPPER) == VAR_UPPER)
544}
545
546fn fixed_to_min_float(fixed: Fixed) -> f64 {
549 if fixed.raw_value() == 0 {
552 return 0.0;
553 }
554 let scale = (1 << 16) as f64;
555 let value = fixed.raw_value() as f64 / scale;
556 let eps = 0.5 / scale;
557 let lo = value - eps;
558 let hi = value + eps;
559 if lo as i32 != hi as i32 {
561 return value.round();
562 }
563
564 let lo = format!("{:.8}", lo);
565 let hi = format!("{:.8}", hi);
566 debug_assert!(
567 lo.len() == hi.len() && lo != hi,
568 "lo = {}, hi = {}, eps = {}",
569 lo,
570 hi,
571 eps
572 );
573 let mut i = lo.len() - 1;
574 for (index, (l, h)) in lo.bytes().zip(hi.bytes()).enumerate() {
575 if l != h {
576 i = index;
577 break;
578 }
579 }
580 let period = lo.bytes().position(|b| b == b'.').unwrap();
581 debug_assert!(period < i);
582 f64::from_str(&format!("{:.digits$}", value, digits = i - period)).unwrap()
583}
584
585fn process_mvar(
586 mvar: &MvarTable<'_>,
587 instance: &OwnedTuple,
588 os2: &mut Os2,
589 hhea: &mut HheaTable,
590 vhea: &mut Option<HheaTable>,
591 post: &mut PostTable<'_>,
592) {
593 for value_record in mvar.value_records() {
594 let Some(delta) = mvar.lookup(value_record.value_tag, instance) else {
595 continue;
596 };
597
598 match value_record.value_tag {
599 tag::HASC => {
601 if let Some(v0) = &mut os2.version0 {
602 v0.s_typo_ascender = add_delta_i16(v0.s_typo_ascender, delta);
603 }
604 }
605 tag::HDSC => {
607 if let Some(v0) = &mut os2.version0 {
608 v0.s_typo_descender = add_delta_i16(v0.s_typo_descender, delta);
609 }
610 }
611 tag::HLGP => {
613 if let Some(v0) = &mut os2.version0 {
614 v0.s_typo_line_gap = add_delta_i16(v0.s_typo_line_gap, delta);
615 }
616 }
617 tag::HCLA => {
619 if let Some(v0) = &mut os2.version0 {
620 v0.us_win_ascent = add_delta_u16(v0.us_win_ascent, delta);
621 }
622 }
623 tag::HCLD => {
625 if let Some(v0) = &mut os2.version0 {
626 v0.us_win_descent = add_delta_u16(v0.us_win_descent, delta);
627 }
628 }
629 tag::VASC => {
631 if let Some(vhea) = vhea {
632 vhea.ascender = add_delta_i16(vhea.ascender, delta);
633 }
634 }
635 tag::VDSC => {
637 if let Some(vhea) = vhea {
638 vhea.descender = add_delta_i16(vhea.descender, delta);
639 }
640 }
641 tag::VLGP => {
643 if let Some(vhea) = vhea {
644 vhea.line_gap = add_delta_i16(vhea.line_gap, delta);
645 }
646 }
647 tag::HCRS => {
649 hhea.caret_slope_rise = add_delta_i16(hhea.caret_slope_rise, delta);
650 }
651 tag::HCRN => {
653 hhea.caret_slope_run = add_delta_i16(hhea.caret_slope_run, delta);
654 }
655 tag::HCOF => {
657 hhea.caret_offset = add_delta_i16(hhea.caret_offset, delta);
658 }
659 tag::VCRS => {
661 if let Some(vhea) = vhea {
662 vhea.caret_slope_rise = add_delta_i16(vhea.caret_slope_rise, delta);
663 }
664 }
665 tag::VCRN => {
667 if let Some(vhea) = vhea {
668 vhea.caret_slope_run = add_delta_i16(vhea.caret_slope_run, delta);
669 }
670 }
671 tag::VCOF => {
673 if let Some(vhea) = vhea {
674 vhea.caret_offset = add_delta_i16(vhea.caret_offset, delta);
675 }
676 }
677 tag::XHGT => {
679 if let Some(version) = &mut os2.version2to4 {
680 version.sx_height = add_delta_i16(version.sx_height, delta);
681 }
682 }
683 tag::CPHT => {
685 if let Some(version) = &mut os2.version2to4 {
686 version.s_cap_height = add_delta_i16(version.s_cap_height, delta);
687 }
688 }
689 tag::SBXS => {
691 os2.y_subscript_x_size = add_delta_i16(os2.y_subscript_x_size, delta);
692 }
693 tag::SBYS => {
695 os2.y_subscript_y_size = add_delta_i16(os2.y_subscript_y_size, delta);
696 }
697 tag::SBXO => {
699 os2.y_subscript_x_offset = add_delta_i16(os2.y_subscript_x_offset, delta);
700 }
701 tag::SBYO => {
703 os2.y_subscript_y_offset = add_delta_i16(os2.y_subscript_y_offset, delta);
704 }
705 tag::SPXS => {
707 os2.y_superscript_x_size = add_delta_i16(os2.y_superscript_x_size, delta);
708 }
709 tag::SPYS => {
711 os2.y_superscript_y_size = add_delta_i16(os2.y_superscript_y_size, delta);
712 }
713 tag::SPXO => {
715 os2.y_superscript_x_offset = add_delta_i16(os2.y_superscript_x_offset, delta);
716 }
717 tag::SPYO => {
719 os2.y_superscript_y_offset = add_delta_i16(os2.y_superscript_y_offset, delta);
720 }
721 tag::STRS => {
723 os2.y_strikeout_size = add_delta_i16(os2.y_strikeout_size, delta);
724 }
725 tag::STRO => {
727 os2.y_strikeout_position = add_delta_i16(os2.y_strikeout_position, delta);
728 }
729 tag::UNDS => {
731 post.header.underline_thickness =
732 add_delta_i16(post.header.underline_thickness, delta);
733 }
734 tag::UNDO => {
736 post.header.underline_position =
737 add_delta_i16(post.header.underline_position, delta);
738 }
739 tag::GSP0
743 | tag::GSP1
744 | tag::GSP2
745 | tag::GSP3
746 | tag::GSP4
747 | tag::GSP5
748 | tag::GSP6
749 | tag::GSP7
750 | tag::GSP8
751 | tag::GSP9 => (),
752 _ => (),
754 }
755 }
756}
757
758fn add_delta_i16(value: i16, delta: f32) -> i16 {
759 (value as f32 + delta)
760 .round()
761 .clamp(i16::MIN as f32, i16::MAX as f32) as i16
762}
763
764fn add_delta_u16(value: u16, delta: f32) -> u16 {
765 (value as f32 + delta).round().clamp(0., u16::MAX as f32) as u16
766}
767
768fn is_supported_variable_font(provider: &impl FontTableProvider) -> Result<(), VariationError> {
769 if provider.has_table(tag::FVAR) {
788 Ok(())
789 } else {
790 Err(VariationError::NotVariableFont)
791 }
792}
793
794fn create_hmtx_table<'b>(
795 hmtx: &HmtxTable<'_>,
796 hvar: Option<&HvarTable<'_>>,
797 glyf: &GlyfTable<'_>,
798 instance: &OwnedTuple,
799 num_glyphs: u16,
800) -> Result<HmtxTable<'b>, ReadWriteError> {
801 match hvar {
802 Some(hvar) => apply_hvar(hmtx, hvar, Some(glyf), instance, num_glyphs),
804 None => htmx_from_phantom_points(glyf, num_glyphs),
806 }
807}
808
809fn apply_hvar<'a>(
810 hmtx: &HmtxTable<'_>,
811 hvar: &HvarTable<'_>,
812 glyf: Option<&GlyfTable<'_>>,
813 instance: &OwnedTuple,
814 num_glyphs: u16,
815) -> Result<HmtxTable<'a>, ReadWriteError> {
816 let mut h_metrics = Vec::with_capacity(usize::from(num_glyphs));
817 for glyph_id in 0..num_glyphs {
818 let mut metric = hmtx.metric(glyph_id)?;
819 let delta = hvar.advance_delta(instance, glyph_id)?;
820 let new = (metric.advance_width as f32 + delta).round();
821 metric.advance_width = new.clamp(0., u16::MAX as f32) as u16;
822
823 if let Some(delta) = hvar.left_side_bearing_delta(instance, glyph_id)? {
824 metric.lsb = (metric.lsb as f32 + delta)
825 .round()
826 .clamp(i16::MIN as f32, i16::MAX as f32) as i16;
827 } else if let Some(glyf) = glyf {
828 let glyph = glyf
830 .records()
831 .get(usize::from(glyph_id))
832 .and_then(|glyph_record| match glyph_record {
833 GlyfRecord::Parsed(glyph) => Some(glyph),
834 _ => None,
835 })
836 .ok_or(ParseError::BadIndex)?;
837 let bounding_box = glyph.bounding_box().unwrap_or_else(BoundingBox::empty);
838 let phantom_points = glyph.phantom_points().unwrap();
840 let pp1 = phantom_points[0].0;
841 metric.lsb = bounding_box.x_min - pp1;
842 }
843 h_metrics.push(metric)
844 }
845
846 Ok(HmtxTable {
848 h_metrics: ReadArrayCow::Owned(h_metrics),
849 left_side_bearings: ReadArrayCow::Owned(vec![]),
850 })
851}
852
853fn htmx_from_phantom_points<'a>(
854 glyf: &GlyfTable<'_>,
855 num_glyphs: u16,
856) -> Result<HmtxTable<'a>, ReadWriteError> {
857 let mut h_metrics = Vec::with_capacity(usize::from(num_glyphs));
864
865 for glyph_record in glyf.records().iter() {
866 let metric = match glyph_record {
867 GlyfRecord::Parsed(glyph) => {
868 let bounding_box = glyph.bounding_box().unwrap_or_else(BoundingBox::empty);
869 let phantom_points = glyph.phantom_points().unwrap();
871 let pp1 = phantom_points[0].0;
872 let pp2 = phantom_points[1].0;
873 let lsb = bounding_box.x_min - pp1;
876 let advance_width = u16::try_from(pp2 - pp1).unwrap_or(0);
877 LongHorMetric { advance_width, lsb }
878 }
879 _ => unreachable!("glyph should be parsed with phantom points present"),
880 };
881 h_metrics.push(metric);
882 }
883
884 Ok(HmtxTable {
886 h_metrics: ReadArrayCow::Owned(h_metrics),
887 left_side_bearings: ReadArrayCow::Owned(vec![]),
888 })
889}
890
891fn apply_gvar<'a>(
898 mut glyf: GlyfTable<'a>,
899 gvar: &GvarTable<'a>,
900 hmtx: &HmtxTable<'a>,
901 vmtx: Option<&HmtxTable<'a>>,
902 os2: Option<&Os2>,
903 hhea: &HheaTable,
904 instance: &OwnedTuple,
905) -> Result<GlyfTable<'a>, ReadWriteError> {
906 for (glyph_id, glyph_record) in glyf.records_mut().iter_mut().enumerate() {
907 let glyph_id = glyph_id as u16;
909 glyph_record.parse()?;
910 match glyph_record {
911 GlyfRecord::Parsed(glyph) => {
912 glyph.apply_variations(glyph_id, instance, gvar, hmtx, vmtx, os2, hhea)?;
913 }
914 GlyfRecord::Present { .. } => unreachable!("glyph should be parsed"),
915 }
916 }
917
918 for glyph_id in 0..glyf.num_glyphs() {
920 if glyf.records()[usize::from(glyph_id)].is_composite() {
930 let mut glyph_record = glyf.take(glyph_id).unwrap();
932 let GlyfRecord::Parsed(Glyph::Composite(ref mut composite)) = glyph_record else {
933 unreachable!("expected parsed composite glyph")
934 };
935 let bbox = composite
937 .calculate_bounding_box(&glyf)?
938 .round_out()
939 .to_i32();
940 composite.bounding_box = BoundingBox {
941 x_min: bbox
942 .min_x()
943 .try_into()
944 .map_err(|_| ParseError::LimitExceeded)?,
945 x_max: bbox
946 .max_x()
947 .try_into()
948 .map_err(|_| ParseError::LimitExceeded)?,
949 y_min: bbox
950 .min_y()
951 .try_into()
952 .map_err(|_| ParseError::LimitExceeded)?,
953 y_max: bbox
954 .max_y()
955 .try_into()
956 .map_err(|_| ParseError::LimitExceeded)?,
957 };
958 glyf.replace(glyph_id, glyph_record)?;
959 }
960 }
961
962 Ok(glyf)
963}
964
965fn union_rect(rect: RectI, other: RectI) -> RectI {
966 RectI::from_points(
967 rect.origin().min(other.origin()),
968 rect.lower_right().max(other.lower_right()),
969 )
970}
971
972fn is_italic(tuple: &[Fixed], fvar: &FvarTable<'_>) -> bool {
973 let Some(slnt_index) = fvar.axes().position(|axis| axis.axis_tag == tag::SLNT) else {
976 return false;
977 };
978
979 tuple
980 .get(slnt_index)
981 .filter(|&&value| value != Fixed::from(0i32))
982 .is_some()
983}
984
985impl From<BoundingBox> for RectI {
986 fn from(bbox: BoundingBox) -> Self {
987 RectI::from_points(
988 vec2i(bbox.x_min.into(), bbox.y_min.into()),
989 vec2i(bbox.x_max.into(), bbox.y_max.into()),
990 )
991 }
992}
993
994impl From<ParseError> for VariationError {
995 fn from(err: ParseError) -> VariationError {
996 VariationError::Parse(err)
997 }
998}
999
1000impl From<CFFError> for VariationError {
1001 fn from(err: CFFError) -> VariationError {
1002 VariationError::CFF(err)
1003 }
1004}
1005
1006impl From<WriteError> for VariationError {
1007 fn from(err: WriteError) -> VariationError {
1008 VariationError::Write(err)
1009 }
1010}
1011
1012impl From<ReadWriteError> for VariationError {
1013 fn from(err: ReadWriteError) -> VariationError {
1014 match err {
1015 ReadWriteError::Read(err) => VariationError::Parse(err),
1016 ReadWriteError::Write(err) => VariationError::Write(err),
1017 }
1018 }
1019}
1020
1021impl fmt::Display for VariationError {
1022 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1023 match self {
1024 VariationError::Parse(err) => write!(f, "variation: parse error: {}", err),
1025 VariationError::CFF(err) => write!(f, "variation: CFF error: {}", err),
1026 VariationError::Write(err) => write!(f, "variation: write error: {}", err),
1027 VariationError::NotVariableFont => write!(f, "variation: not a variable font"),
1028 VariationError::NotImplemented => {
1029 write!(f, "variation: unsupported variable font format")
1030 }
1031 VariationError::NameError => write!(f, "font did not contain a `name` table entry for the family name in a usable encoding"),
1032 VariationError::TagError => write!(f, "the list of table tags was unable to be retrieved from the font"),
1033 }
1034 }
1035}
1036
1037impl std::error::Error for VariationError {}
1038
1039impl From<ParseError> for AxisNamesError {
1040 fn from(err: ParseError) -> AxisNamesError {
1041 AxisNamesError::Parse(err)
1042 }
1043}
1044
1045impl fmt::Display for AxisNamesError {
1046 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1047 match self {
1048 AxisNamesError::Parse(err) => write!(f, "axis names: parse error: {}", err),
1049 AxisNamesError::NoStatTable => f.write_str("axis names: no STAT table"),
1050 AxisNamesError::NoNameTable => f.write_str("axis names: no name table"),
1051 }
1052 }
1053}
1054
1055impl std::error::Error for AxisNamesError {}
1056
1057#[cfg(test)]
1058mod tests {
1059 use super::*;
1060 use crate::assert_close;
1061 use crate::cff::charstring::{ArgumentsStack, CharStringVisitorContext};
1062 use crate::cff::{cff2, CFFFont};
1063 use crate::font_data::FontData;
1064 use crate::tables::{OpenTypeData, OpenTypeFont};
1065 use crate::tests::read_fixture;
1066
1067 #[test]
1068 fn test_generate_postscript_name_with_postscript_prefix() {
1069 let buffer = read_fixture("tests/fonts/opentype/NotoSans-VF.abc.ttf");
1070 let scope = ReadScope::new(&buffer);
1071 let font_file = scope
1072 .read::<FontData<'_>>()
1073 .expect("unable to parse font file");
1074 let table_provider = font_file
1075 .table_provider(0)
1076 .expect("unable to create font provider");
1077 let fvar_data = table_provider
1078 .read_table_data(tag::FVAR)
1079 .expect("unable to read fvar table data");
1080 let fvar = ReadScope::new(&fvar_data).read::<FvarTable<'_>>().unwrap();
1081
1082 let user_tuple = [Fixed::from(100.0), Fixed::from(87.5), Fixed::from(100.0)];
1084 let typographic_family = "Family";
1085 let postscript_prefix = Some(String::from("PSPrefix"));
1086 let postscript_name =
1087 generate_postscript_name(&postscript_prefix, typographic_family, &user_tuple, &fvar);
1088 assert_eq!(postscript_name, "PSPrefix_100wght_87.5wdth_100CTGR");
1089 }
1090
1091 #[test]
1092 fn test_generate_postscript_name_without_postscript_prefix() {
1093 let buffer = read_fixture("tests/fonts/opentype/NotoSans-VF.abc.ttf");
1094 let scope = ReadScope::new(&buffer);
1095 let font_file = scope
1096 .read::<FontData<'_>>()
1097 .expect("unable to parse font file");
1098 let table_provider = font_file
1099 .table_provider(0)
1100 .expect("unable to create font provider");
1101 let fvar_data = table_provider
1102 .read_table_data(tag::FVAR)
1103 .expect("unable to read fvar table data");
1104 let fvar = ReadScope::new(&fvar_data).read::<FvarTable<'_>>().unwrap();
1105
1106 let user_tuple = [Fixed::from(100.0), Fixed::from(87.5), Fixed::from(100.0)];
1108 let typographic_family = "Family";
1109 let postscript_prefix = None;
1110 let postscript_name =
1111 generate_postscript_name(&postscript_prefix, typographic_family, &user_tuple, &fvar);
1112 assert_eq!(postscript_name, "Family_100wght_87.5wdth_100CTGR");
1113 }
1114
1115 #[test]
1116 fn test_generate_postscript_name_omit_defaults() {
1117 let buffer = read_fixture("tests/fonts/opentype/NotoSans-VF.abc.ttf");
1118 let scope = ReadScope::new(&buffer);
1119 let font_file = scope
1120 .read::<FontData<'_>>()
1121 .expect("unable to parse font file");
1122 let table_provider = font_file
1123 .table_provider(0)
1124 .expect("unable to create font provider");
1125 let fvar_data = table_provider
1126 .read_table_data(tag::FVAR)
1127 .expect("unable to read fvar table data");
1128 let fvar = ReadScope::new(&fvar_data).read::<FvarTable<'_>>().unwrap();
1129
1130 let user_tuple = [Fixed::from(400.0), Fixed::from(87.5), Fixed::from(0.0)];
1131 let typographic_family = "Family";
1132 let postscript_prefix = Some(String::from("PSPrefix"));
1133 let postscript_name =
1134 generate_postscript_name(&postscript_prefix, typographic_family, &user_tuple, &fvar);
1135 assert_eq!(postscript_name, "PSPrefix_87.5wdth");
1136 }
1137
1138 #[test]
1139 fn test_generate_postscript_name_strip_forbidden_chars() {
1140 let buffer = read_fixture("tests/fonts/opentype/NotoSans-VF.abc.ttf");
1141 let scope = ReadScope::new(&buffer);
1142 let font_file = scope
1143 .read::<FontData<'_>>()
1144 .expect("unable to parse font file");
1145 let table_provider = font_file
1146 .table_provider(0)
1147 .expect("unable to create font provider");
1148 let fvar_data = table_provider
1149 .read_table_data(tag::FVAR)
1150 .expect("unable to read fvar table data");
1151 let fvar = ReadScope::new(&fvar_data).read::<FvarTable<'_>>().unwrap();
1152
1153 let user_tuple = [Fixed::from(100.0), Fixed::from(87.5), Fixed::from(100.0)];
1154 let typographic_family = "These aren't allowed []<>!";
1155 let postscript_name =
1156 generate_postscript_name(&None, typographic_family, &user_tuple, &fvar);
1157 assert_eq!(
1158 postscript_name,
1159 "Thesearentallowed_100wght_87.5wdth_100CTGR"
1160 );
1161 }
1162
1163 #[test]
1164 fn test_generate_postscript_name_truncate() {
1165 let buffer = read_fixture("tests/fonts/opentype/NotoSans-VF.abc.ttf");
1166 let scope = ReadScope::new(&buffer);
1167 let font_file = scope
1168 .read::<FontData<'_>>()
1169 .expect("unable to parse font file");
1170 let table_provider = font_file
1171 .table_provider(0)
1172 .expect("unable to create font provider");
1173 let fvar_data = table_provider
1174 .read_table_data(tag::FVAR)
1175 .expect("unable to read fvar table data");
1176 let fvar = ReadScope::new(&fvar_data).read::<FvarTable<'_>>().unwrap();
1177
1178 let user_tuple = [Fixed::from(100.0), Fixed::from(87.5), Fixed::from(100.0)];
1179 let typographic_family = "IfAfterConstructingThePostScriptNameInThisWayTheLengthIsGreaterThan127CharactersThenConstructTheLastResortPostScriptName";
1180 let postscript_name =
1181 generate_postscript_name(&None, typographic_family, &user_tuple, &fvar);
1182 assert!(postscript_name.len() <= 63);
1183 assert_eq!(
1184 postscript_name,
1185 "IfAfterConstructingThePostScriptNameInThisWayTheLen-189E39CF..."
1186 );
1187 }
1188
1189 #[test]
1190 fn typographic_subfamily_name_non_elidable() -> Result<(), ReadWriteError> {
1191 let buffer = read_fixture("tests/fonts/opentype/NotoSans-VF.abc.ttf");
1192 let scope = ReadScope::new(&buffer);
1193 let font_file = scope.read::<FontData<'_>>()?;
1194 let table_provider = font_file.table_provider(0)?;
1195 let fvar_data = table_provider.read_table_data(tag::FVAR)?;
1196 let fvar = ReadScope::new(&fvar_data).read::<FvarTable<'_>>()?;
1197 let stat_data = table_provider.read_table_data(tag::STAT)?;
1198 let stat = ReadScope::new(&stat_data).read::<StatTable<'_>>()?;
1199 let name_data = table_provider.read_table_data(tag::NAME)?;
1200 let name = ReadScope::new(&name_data).read::<NameTable<'_>>()?;
1201
1202 let user_tuple = [Fixed::from(100.0), Fixed::from(87.5), Fixed::from(100.0)];
1203 let name = typographic_subfamily_name(&user_tuple, &fvar, &stat, &name, "Default").unwrap();
1204 assert_eq!(name, "Thin SemiCondensed Display");
1205 Ok(())
1206 }
1207
1208 #[test]
1209 fn typographic_subfamily_name_elidable() -> Result<(), ReadWriteError> {
1210 let buffer = read_fixture("tests/fonts/opentype/NotoSans-VF.abc.ttf");
1211 let scope = ReadScope::new(&buffer);
1212 let font_file = scope.read::<FontData<'_>>()?;
1213 let table_provider = font_file.table_provider(0)?;
1214 let fvar_data = table_provider.read_table_data(tag::FVAR)?;
1215 let fvar = ReadScope::new(&fvar_data).read::<FvarTable<'_>>()?;
1216 let stat_data = table_provider.read_table_data(tag::STAT)?;
1217 let stat = ReadScope::new(&stat_data).read::<StatTable<'_>>()?;
1218 let name_data = table_provider.read_table_data(tag::NAME)?;
1219 let name = ReadScope::new(&name_data).read::<NameTable<'_>>()?;
1220
1221 let user_tuple = [Fixed::from(400.0), Fixed::from(100.0), Fixed::from(0.0)];
1227 let name = typographic_subfamily_name(&user_tuple, &fvar, &stat, &name, "Default").unwrap();
1228 assert_eq!(name, "Regular");
1229 Ok(())
1230 }
1231
1232 #[test]
1233 fn subfamily_name_axis_value_format3() -> Result<(), ReadWriteError> {
1234 let buffer = read_fixture("tests/fonts/variable/Inter[slnt,wght].abc.ttf");
1235 let scope = ReadScope::new(&buffer);
1236 let font_file = scope.read::<FontData<'_>>()?;
1237 let table_provider = font_file.table_provider(0)?;
1238 let fvar_data = table_provider.read_table_data(tag::FVAR)?;
1239 let fvar = ReadScope::new(&fvar_data).read::<FvarTable<'_>>()?;
1240 let stat_data = table_provider.read_table_data(tag::STAT)?;
1241 let stat = ReadScope::new(&stat_data).read::<StatTable<'_>>()?;
1242 let name_data = table_provider.read_table_data(tag::NAME)?;
1243 let name = ReadScope::new(&name_data).read::<NameTable<'_>>()?;
1244
1245 let user_tuple = [Fixed::from(700.0), Fixed::from(0.0)];
1251 let name = typographic_subfamily_name(&user_tuple, &fvar, &stat, &name, "Default").unwrap();
1252 assert_eq!(name, "Bold");
1253 Ok(())
1254 }
1255
1256 #[test]
1257 fn test_fixed_to_float() {
1258 assert_close!(fixed_to_min_float(Fixed::from(0)), 0., f64::EPSILON);
1259 assert_close!(fixed_to_min_float(Fixed::from(900)), 900., f64::EPSILON);
1260 assert_close!(fixed_to_min_float(Fixed::from(5.5)), 5.5, f64::EPSILON);
1261 assert_close!(fixed_to_min_float(Fixed::from(2.9)), 2.9, f64::EPSILON);
1262 assert_close!(fixed_to_min_float(Fixed::from(-1.4)), -1.4, f64::EPSILON);
1263 assert_close!(
1264 fixed_to_min_float(Fixed::from(-1. + (1. / 65536.))),
1265 -0.99998,
1266 f64::EPSILON
1267 );
1268 }
1269
1270 #[test]
1272 fn instance_underline_test() -> Result<(), ReadWriteError> {
1273 let buffer = read_fixture("tests/fonts/variable/UnderlineTest-VF.ttf");
1274 let scope = ReadScope::new(&buffer);
1275 let font_file = scope.read::<FontData<'_>>()?;
1276 let table_provider = font_file.table_provider(0)?;
1277 let user_tuple = [Fixed::from(500), Fixed::from(500)];
1278 let (inst, _tuple) = instance(&table_provider, &user_tuple).unwrap();
1279
1280 let scope = ReadScope::new(&inst);
1281 let font_file = scope.read::<FontData<'_>>()?;
1282 let table_provider = font_file.table_provider(0)?;
1283 let maxp =
1284 ReadScope::new(&table_provider.read_table_data(tag::MAXP)?).read::<MaxpTable>()?;
1285 let hhea =
1286 ReadScope::new(&table_provider.read_table_data(tag::HHEA)?).read::<HheaTable>()?;
1287 let hmtx_data = table_provider.read_table_data(tag::HMTX)?;
1288 assert!(ReadScope::new(&hmtx_data)
1289 .read_dep::<HmtxTable<'_>>((
1290 usize::from(maxp.num_glyphs),
1291 usize::from(hhea.num_h_metrics),
1292 ))
1293 .is_ok());
1294 Ok(())
1295 }
1296
1297 #[test]
1298 #[cfg(feature = "prince")]
1299 fn instance_minipax() -> Result<(), ReadWriteError> {
1300 let buffer =
1301 read_fixture("../../../tests/data/fonts/minipax/variable/Minipax Variable.ttf");
1302 let scope = ReadScope::new(&buffer);
1303 let font_file = scope.read::<FontData<'_>>()?;
1304 let table_provider = font_file.table_provider(0)?;
1305 let user_tuple = [Fixed::from(600)];
1306 assert!(instance(&table_provider, &user_tuple).is_ok());
1307
1308 Ok(())
1309 }
1310
1311 #[test]
1312 fn test_axis_names() {
1313 let buffer = read_fixture("tests/fonts/variable/UnderlineTest-VF.ttf");
1314 let scope = ReadScope::new(&buffer);
1315 let font_file = scope.read::<FontData<'_>>().unwrap();
1316 let table_provider = font_file.table_provider(0).unwrap();
1317 let names = axis_names(&table_provider).unwrap();
1318 assert_eq!(
1319 names,
1320 vec![
1321 NamedAxis {
1322 tag: tag!(b"UNDO"),
1323 name: Cow::from("Underline Offset"),
1324 ordering: 0
1325 },
1326 NamedAxis {
1327 tag: tag!(b"UNDS"),
1328 name: Cow::from("Underline Size"),
1329 ordering: 1
1330 }
1331 ]
1332 );
1333 }
1334
1335 #[test]
1336 fn test_axis_names_not_variable() {
1337 let buffer = read_fixture("tests/fonts/opentype/SourceCodePro-Regular.otf");
1338 let scope = ReadScope::new(&buffer);
1339 let font_file = scope.read::<FontData<'_>>().unwrap();
1340 let table_provider = font_file.table_provider(0).unwrap();
1341 let names = axis_names(&table_provider);
1342 assert_eq!(names, Err(AxisNamesError::NoStatTable));
1343 }
1344
1345 #[test]
1346 fn instance_cff2() -> Result<(), VariationError> {
1347 let buffer = read_fixture("tests/fonts/opentype/cff2/SourceSansVariable-Roman.abc.otf");
1348 let scope = ReadScope::new(&buffer);
1349 let font_file = scope.read::<FontData<'_>>()?;
1350 let table_provider = font_file.table_provider(0)?;
1351
1352 let user_tuple = [Fixed::from(650.0)];
1353 let (res, _tuple) = instance(&table_provider, &user_tuple)?;
1354
1355 let otf = ReadScope::new(&res).read::<OpenTypeFont<'_>>().unwrap();
1357
1358 let offset_table = match otf.data {
1359 OpenTypeData::Single(ttf) => ttf,
1360 OpenTypeData::Collection(_) => unreachable!(),
1361 };
1362
1363 let cff2_table_data = offset_table
1364 .read_table(&otf.scope, tag::CFF2)
1365 .unwrap()
1366 .unwrap();
1367 let cff2 = cff2_table_data
1368 .read::<CFF2<'_>>()
1369 .expect("unable to parse CFF2 instance");
1370
1371 for glyph_id in 0..cff2.char_strings_index.len() as u16 {
1372 let font_dict_index = cff2
1373 .fd_select
1374 .as_ref()
1375 .and_then(|fd_select| fd_select.font_dict_index(glyph_id))
1376 .unwrap_or(0);
1377 let font_dict = &cff2.fonts[usize::from(font_dict_index)];
1378 println!("-- glyph {glyph_id} --");
1379 let mut visitor = crate::cff::charstring::DebugVisitor;
1380 let variable = None;
1381 let mut ctx = CharStringVisitorContext::new(
1382 glyph_id,
1383 &cff2.char_strings_index,
1384 font_dict.local_subr_index.as_ref(),
1385 &cff2.global_subr_index,
1386 variable,
1387 );
1388 let mut stack = ArgumentsStack {
1389 data: &mut [0.0; cff2::MAX_OPERANDS],
1390 len: 0,
1391 max_len: cff2::MAX_OPERANDS,
1392 };
1393 ctx.visit(CFFFont::CFF2(&font_dict), &mut stack, &mut visitor)?;
1394 }
1395
1396 Ok(())
1397 }
1398}