use crate::{Component, Font, Layer, MetricType, NodeType, Shape};
use fontdrasil::{
coords::NormalizedCoord,
coords::NormalizedLocation,
orchestration::{Access, AccessBuilder, Work},
types::GlyphName,
};
use fontir::{
error::{BadGlyph, BadGlyphKind, Error, PathConversionError},
ir::{
self, AnchorBuilder, GdefCategories, GlobalMetric, GlobalMetrics, GlobalMetricsBuilder, GlyphInstance, GlyphOrder, GlyphPathBuilder, KernGroup, KernSide, KerningGroups, KerningInstance, NameBuilder, NameKey, NamedInstance, PostscriptNames, StaticMetadata
},
orchestration::{Context, Flags, IrWork, WorkId},
source::Source,
};
use kurbo::BezPath;
use log::{debug, trace, warn};
use ordered_float::OrderedFloat;
use smol_str::{ SmolStr};
use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
sync::Arc,
};
use write_fonts::{
tables::{
gdef::GlyphClassDef,
os2::SelectionFlags,
},
types::{NameId, Tag},
};
#[derive(Debug, Clone)]
pub struct BabelfontIrSource {
font_info: Arc<FontInfo>,
source_path: Option<Arc<std::path::Path>>,
}
impl BabelfontIrSource {
fn create_work_for_one_glyph(
&self,
glyph_name: GlyphName,
base_name: Option<GlyphName>,
) -> Result<GlyphIrWork, Error> {
Ok(GlyphIrWork {
glyph_name,
font_info: self.font_info.clone(),
bracket_glyph_parent: base_name,
})
}
}
impl Source for BabelfontIrSource {
fn new(_unused: &std::path::Path) -> Result<Self, Error> {
unimplemented!();
}
fn create_static_metadata_work(&self) -> Result<Box<IrWork>, Error> {
Ok(Box::new(StaticMetadataWork(self.clone())))
}
fn create_global_metric_work(&self) -> Result<Box<IrWork>, Error> {
Ok(Box::new(GlobalMetricWork(self.font_info.clone())))
}
fn create_glyph_ir_work(&self) -> Result<Vec<Box<IrWork>>, fontir::error::Error> {
let mut work: Vec<Box<IrWork>> = Vec::new();
for glyph in self.font_info.font.glyphs.iter() {
work.push(Box::new(
self.create_work_for_one_glyph(glyph.name.clone().into(), None)?,
));
}
Ok(work)
}
fn create_feature_ir_work(&self) -> Result<Box<IrWork>, Error> {
Ok(Box::new(FeatureWork {
font_info: self.font_info.clone(),
font_file_path: self.source_path.clone(),
}))
}
fn create_kerning_group_ir_work(&self) -> Result<Box<IrWork>, Error> {
Ok(Box::new(KerningGroupWork(self.font_info.clone())))
}
fn create_kerning_instance_ir_work(
&self,
at: NormalizedLocation,
) -> Result<Box<IrWork>, Error> {
Ok(Box::new(KerningInstanceWork {
font_info: self.font_info.clone(),
location: at,
}))
}
fn create_color_palette_work(
&self,
) -> Result<Box<fontir::orchestration::IrWork>, fontir::error::Error> {
Ok(Box::new(ColorPaletteWork {
_font_info: self.font_info.clone(),
}))
}
fn create_color_glyphs_work(&self) -> Result<Box<IrWork>, Error> {
Ok(Box::new(PaintGraphWork {
_font_info: self.font_info.clone(),
}))
}
}
impl BabelfontIrSource {
pub fn new_from_memory(font: Font) -> Result<Self, Error> {
Ok(Self {
font_info: Arc::new(FontInfo::try_from(font)?),
source_path: None,
})
}
}
fn names(font: &Font, flags: SelectionFlags) -> HashMap<NameKey, String> {
let mut builder = NameBuilder::default();
builder.set_version(font.version.0 as i32, font.version.1 as u32);
for name_id_u32 in 0..=25 {
let name_id = NameId::from(name_id_u32);
if let Some(name) = font.names.get(name_id).and_then(|n| n.get_default()) {
builder.add(name_id, name.clone());
}
}
let subfamily = if flags.contains(SelectionFlags::BOLD | SelectionFlags::ITALIC) {
"Bold Italic"
} else if flags.contains(SelectionFlags::BOLD) {
"Bold"
} else if flags.contains(SelectionFlags::ITALIC) {
"Italic"
} else {
"Regular"
};
builder.add(NameId::SUBFAMILY_NAME, subfamily.to_string());
let original_family = builder
.get(NameId::FAMILY_NAME)
.map(|s| s.to_string())
.unwrap_or_default();
let family = NameBuilder::make_family_name(
&original_family,
font
.default_master()
.and_then(|f| f.name.get_default())
.map(|f| f.as_str())
.unwrap_or("Regular"),
true,
);
builder.add(NameId::FAMILY_NAME, family.clone());
if let Some(typographic_family) = &builder
.get(NameId::TYPOGRAPHIC_FAMILY_NAME)
.or(Some(&original_family))
{
builder.add(
NameId::TYPOGRAPHIC_FAMILY_NAME,
typographic_family.to_string(),
);
}
if let Some(typographic_subfamily) = &builder.get(NameId::TYPOGRAPHIC_SUBFAMILY_NAME).or(font
.default_master()
.and_then(|x| x.name.get_default().map(|x| x.as_str())))
{
builder.add(
NameId::TYPOGRAPHIC_SUBFAMILY_NAME,
typographic_subfamily.to_string(),
);
}
builder.into_inner()
}
#[derive(Debug)]
struct StaticMetadataWork(BabelfontIrSource);
impl Work<Context, WorkId, Error> for StaticMetadataWork {
fn id(&self) -> WorkId {
WorkId::StaticMetadata
}
fn also_completes(&self) -> Vec<WorkId> {
vec![WorkId::PreliminaryGlyphOrder]
}
fn exec(&self, context: &Context) -> Result<(), Error> {
let font_info = self.0.font_info.as_ref();
let font = &font_info.font;
debug!(
"Static metadata for {}",
font.names
.family_name
.get_default()
.map(|s| s.as_str())
.unwrap_or("<nameless family>")
);
let axes = font_info.axes.clone();
let named_instances = font
.instances
.iter()
.map(|inst| {
NamedInstance {
name: inst
.name
.get_default()
.map(|x| x.to_string())
.unwrap_or("<Unnamed Instance>".to_string()),
postscript_name: inst
.custom_names
.postscript_name
.get_default()
.map(|x| x.to_string()),
location: inst.location.to_user(&axes),
}
})
.collect();
let global_locations = font
.masters
.iter()
.map(|m| m.location.to_normalized(&axes))
.collect();
let italic_angle = font
.default_master()
.and_then(|x| x.metrics.get(&MetricType::ItalicAngle))
.map(|v| -v as f32)
.unwrap_or(0.0);
let master_name = font.default_master().and_then(|x| x.name.get_default()).map(|x| x.to_ascii_lowercase()).unwrap_or("regular".to_string());
let mut selection_flags = match false {
true => SelectionFlags::USE_TYPO_METRICS,
false => SelectionFlags::empty(),
} | match font.names.wws_family_name.get_default().is_some() {
true => SelectionFlags::WWS,
false => SelectionFlags::empty(),
} |
match italic_angle {
0.0 => SelectionFlags::empty(),
_ => SelectionFlags::ITALIC,
} |
match master_name.as_str() {
"italic" => SelectionFlags::ITALIC,
"bold" => SelectionFlags::BOLD,
"bold italic" => SelectionFlags::BOLD | SelectionFlags::ITALIC,
_ => SelectionFlags::empty(),
};
if selection_flags.intersection(SelectionFlags::ITALIC | SelectionFlags::BOLD)
== SelectionFlags::empty()
{
selection_flags |= SelectionFlags::REGULAR;
}
let categories = make_glyph_categories(font);
let build_vertical = false;
let dont_use_prod_names = false;
let postscript_names =
if context.flags.contains(Flags::PRODUCTION_NAMES) && !dont_use_prod_names {
let mut postscript_names = PostscriptNames::default();
for glyph in font.glyphs.iter() {
if let Some(production_name) = glyph.production_name.as_ref() {
postscript_names
.insert(glyph.name.clone().into(), production_name.clone().into());
}
}
Some(postscript_names)
} else {
None
};
let mut static_metadata = StaticMetadata::new(
font.upm,
names(font, selection_flags),
axes.into_inner(),
named_instances,
global_locations,
postscript_names,
italic_angle.into(),
categories,
None,
build_vertical,
)
.map_err(Error::VariationModelError)?;
static_metadata.misc.selection_flags = selection_flags;
static_metadata.variations = None;
static_metadata.misc.version_major = font.version.0 as i32;
static_metadata.misc.version_minor = font.version.1 as u32;
static_metadata.misc.created = Some(font.date.to_utc());
let glyph_order: GlyphOrder =
font.glyphs.iter().map(|g| GlyphName::new(&g.name)).collect();
context.static_metadata.set(static_metadata);
context.preliminary_glyph_order.set(glyph_order);
Ok(())
}
}
fn make_glyph_categories(font: &Font) -> GdefCategories {
let categories = font
.glyphs
.iter()
.map(|glyph| {
(
GlyphName::new(&glyph.name),
match glyph.category {
crate::GlyphCategory::Base => GlyphClassDef::Base,
crate::GlyphCategory::Mark => GlyphClassDef::Mark,
crate::GlyphCategory::Ligature => GlyphClassDef::Ligature,
crate::GlyphCategory::Unknown => GlyphClassDef::Base, },
)
})
.collect();
GdefCategories {
categories,
prefer_gdef_categories_in_fea: false,
}
}
#[derive(Debug)]
struct GlobalMetricWork(Arc<FontInfo>);
impl Work<Context, WorkId, Error> for GlobalMetricWork {
fn id(&self) -> WorkId {
WorkId::GlobalMetrics
}
fn read_access(&self) -> Access<WorkId> {
Access::Variant(WorkId::StaticMetadata)
}
fn exec(&self, context: &Context) -> Result<(), Error> {
let font_info = self.0.as_ref();
let font = &font_info.font;
debug!(
"Global metrics for {}",
font.names
.family_name
.get_default()
.unwrap_or(&"<nameless family>".to_string())
);
let static_metadata = context.static_metadata.get();
let mut metrics = GlobalMetricsBuilder::new();
let axes = font.fontdrasil_axes()?;
for master in font.masters.iter() {
let pos = master.location.to_normalized(&axes);
let cap_height = master
.metrics
.get(&MetricType::CapHeight)
.copied()
.unwrap_or(700);
let x_height = master
.metrics
.get(&MetricType::XHeight)
.copied()
.unwrap_or(500);
let ascender = master
.metrics
.get(&MetricType::Ascender)
.copied()
.unwrap_or(800);
let descender = master
.metrics
.get(&MetricType::Descender)
.copied()
.unwrap_or(-200);
metrics.set_if_some(GlobalMetric::CapHeight, pos.clone(), Some(cap_height));
metrics.set_if_some(GlobalMetric::XHeight, pos.clone(), Some(x_height));
metrics.set(
GlobalMetric::VheaLineGap,
pos.clone(),
master
.metrics
.get(&MetricType::Custom("vheaLineGap".into()))
.map(|v| *v as f64)
.unwrap_or(font.upm as f64),
);
metrics.populate_defaults(
&pos,
static_metadata.units_per_em,
Some(x_height.into()),
Some(ascender.into()),
Some(descender.into()),
master
.metrics
.get(&MetricType::ItalicAngle)
.map(|v| -*v as f64),
);
}
context
.global_metrics
.set(metrics.build(&static_metadata.axes)?);
Ok(())
}
}
#[derive(Debug)]
struct FeatureWork {
font_info: Arc<FontInfo>,
font_file_path: Option<Arc<std::path::Path>>,
}
impl Work<Context, WorkId, Error> for FeatureWork {
fn id(&self) -> WorkId {
WorkId::Features
}
fn exec(&self, context: &Context) -> Result<(), Error> {
trace!("Generate features");
let font_info = self.font_info.as_ref();
let font = &font_info.font;
#[warn(clippy::expect_used)]
context.features.set(to_ir_features(
&Some(font.features.to_fea()),
self.font_file_path.as_ref().map(|path| {
path.canonicalize()
.expect("path cannot be canonicalized")
.parent()
.expect("the path must be in a directory")
.to_path_buf()
}),
)?);
Ok(())
}
}
#[derive(Debug)]
struct KerningGroupWork(Arc<FontInfo>);
#[derive(Debug)]
struct KerningInstanceWork {
font_info: Arc<FontInfo>,
location: NormalizedLocation,
}
fn kern_participant(
glyph_order: &GlyphOrder,
groups: &BTreeMap<KernGroup, BTreeSet<GlyphName>>,
raw_side: &str,
first: bool,
) -> Option<KernSide> {
if let Some(group) = raw_side.strip_prefix('@') {
let key = if first { KernGroup::Side1(group.into()) } else { KernGroup::Side2(group.into()) };
if groups.contains_key(&key) {
Some(KernSide::Group(key))
} else {
warn!("Invalid kern side: {raw_side}, no group {group:?}");
None
}
} else {
let name = GlyphName::from(raw_side);
if glyph_order.contains(&name) {
Some(KernSide::Glyph(name))
} else {
warn!("Invalid kern side: {raw_side}, no such glyph");
None
}
}
}
impl Work<Context, WorkId, Error> for KerningGroupWork {
fn id(&self) -> WorkId {
WorkId::KerningGroups
}
fn read_access(&self) -> Access<WorkId> {
Access::None
}
fn exec(&self, context: &Context) -> Result<(), Error> {
trace!("Generate IR for kerning groups");
let font_info = self.0.as_ref();
let font = &font_info.font;
let axes = font.fontdrasil_axes()?;
let mut groups = KerningGroups::default();
for (group, members) in font.first_kern_groups.iter() {
groups.groups.insert(
KernGroup::Side1(group.into()),
members.iter().map(GlyphName::new).collect(),
);
}
for (group, members) in font.second_kern_groups.iter() {
groups.groups.insert(
KernGroup::Side2(group.into()),
members.iter().map(GlyphName::new).collect(),
);
}
groups.locations = font
.masters
.iter()
.map(|master| master.location.clone())
.map(|l| l.to_normalized(&axes))
.collect();
context.kerning_groups.set(groups);
Ok(())
}
}
impl Work<Context, WorkId, Error> for KerningInstanceWork {
fn id(&self) -> WorkId {
WorkId::KernInstance(self.location.clone())
}
fn read_access(&self) -> Access<WorkId> {
AccessBuilder::new()
.variant(WorkId::GlyphOrder)
.variant(WorkId::KerningGroups)
.build()
}
fn exec(&self, context: &Context) -> Result<(), Error> {
trace!("Generate IR for kerning at {:?}", self.location);
let kerning_groups = context.kerning_groups.get();
let groups = &kerning_groups.groups;
let arc_glyph_order = context.glyph_order.get();
let glyph_order = arc_glyph_order.as_ref();
let font_info = self.font_info.as_ref();
let mut kerning = KerningInstance {
location: self.location.clone(),
..Default::default()
};
let Some(kern_pairs) = kerning_at_location(font_info, &self.location) else {
return Ok(());
};
kern_pairs
.iter()
.filter_map(|((side1, side2), pos_adjust)| {
let side1 = kern_participant(glyph_order, groups, side1, true);
let side2 = kern_participant(glyph_order, groups, side2, false);
side1.zip(side2).map(|side| (side, *pos_adjust))
})
.for_each(|(participants, value)| {
*kerning.kerns.entry(participants).or_default() = value;
});
context.kerning_at.set(kerning);
Ok(())
}
}
type Kerns = BTreeMap<(SmolStr, SmolStr), OrderedFloat<f64>>;
fn kerning_at_location<'a>(
font_info: &'a FontInfo,
location: &NormalizedLocation,
) -> Option<Cow<'a, Kerns>> {
let axes = font_info.font.fontdrasil_axes().ok()?;
let master = font_info
.font
.masters
.iter()
.find(|master| master.location.to_normalized(&axes) == *location)?;
Some(Cow::Owned(master.kerning.iter().map(
|((side1, side2), value)| {
((side1.into(), side2.into()), OrderedFloat(*value as f64))
},
).collect::<Kerns>()))
}
#[derive(Debug)]
struct GlyphIrWork {
glyph_name: GlyphName,
bracket_glyph_parent: Option<GlyphName>,
font_info: Arc<FontInfo>,
}
impl GlyphIrWork {
fn is_bracket_layer(&self) -> bool {
self.bracket_glyph_parent.is_some()
}
}
fn check_pos(
glyph_name: &GlyphName,
positions: &HashSet<NormalizedCoord>,
axis: &fontdrasil::types::Axis,
pos: &NormalizedCoord,
) -> Result<(), BadGlyph> {
if !positions.contains(pos) {
return Err(BadGlyph::new(
glyph_name.clone(),
BadGlyphKind::UndefinedAtNormalizedPosition {
axis: axis.tag,
pos: pos.to_owned(),
},
));
}
Ok(())
}
impl Work<Context, WorkId, Error> for GlyphIrWork {
fn id(&self) -> WorkId {
WorkId::Glyph(self.glyph_name.clone())
}
fn read_access(&self) -> Access<WorkId> {
AccessBuilder::new()
.variant(WorkId::StaticMetadata)
.variant(WorkId::GlobalMetrics)
.build()
}
fn write_access(&self) -> Access<WorkId> {
AccessBuilder::new()
.specific_instance(WorkId::Glyph(self.glyph_name.clone()))
.specific_instance(WorkId::Anchor(self.glyph_name.clone()))
.build()
}
fn also_completes(&self) -> Vec<WorkId> {
vec![WorkId::Anchor(self.glyph_name.clone())]
}
fn exec(&self, context: &Context) -> Result<(), Error> {
trace!("Generate IR for '{}'", self.glyph_name.as_str());
let font_info = self.font_info.as_ref();
let font = &font_info.font;
let static_metadata = context.static_metadata.get();
let axes = &static_metadata.all_source_axes;
let global_metrics = context.global_metrics.get();
let glyph = font
.glyphs
.get(
self.bracket_glyph_parent
.as_ref()
.unwrap_or(&self.glyph_name)
.as_str(),
)
.ok_or_else(|| Error::NoGlyphForName(self.glyph_name.clone()))?;
let mut ir_glyph = ir::GlyphBuilder::new(self.glyph_name.clone());
ir_glyph.emit_to_binary = glyph.exported;
ir_glyph.codepoints = if !self.is_bracket_layer() {
glyph.codepoints.iter().copied().collect()
} else {
Default::default()
};
let mut ir_anchors = AnchorBuilder::new(self.glyph_name.clone());
let layers: Vec<&Layer> =
{
glyph
.layers
.iter()
.filter(|l| l.location.is_none())
.collect()
};
let mut axis_positions: HashMap<Tag, HashSet<NormalizedCoord>> = HashMap::new();
let master_ids = font.masters.iter().map(|m| (m.id.clone(), m)).collect::<HashMap<_,_>>();
for layer in layers.iter() {
let master_id = &layer.id;
let master = master_id.as_ref().and_then(|id| master_ids.get(id));
let Some(design_location) = layer.location.as_ref().or_else(|| {
master.map(|m| &m.location)
}) else { continue };
let location = design_location.to_normalized(axes);
let (location, instance) = process_layer(glyph, &location, layer, font_info, &global_metrics)?;
for (tag, coord) in location.iter() {
axis_positions.entry(*tag).or_default().insert(*coord);
}
ir_glyph.try_add_source(&location, instance)?;
if glyph.exported {
for anchor in layer.anchors.iter() {
ir_anchors.add(
anchor.name.clone().into(),
location.clone(),
(anchor.x, anchor.y).into(),
)?;
}
}
}
let ir_glyph = ir_glyph.build()?;
let anchors = ir_anchors.build()?;
for axis in axes.iter() {
let default = axis.default.to_normalized(&axis.converter);
let positions = axis_positions.get(&axis.tag).ok_or_else(|| {
BadGlyph::new(&self.glyph_name, BadGlyphKind::NoAxisPosition(axis.tag))
})?;
check_pos(&self.glyph_name, positions, axis, &default)?;
}
context.anchors.set(anchors);
context.glyphs.set(ir_glyph);
Ok(())
}
}
fn process_layer(
glyph: &crate::Glyph,
location: &NormalizedLocation,
layer: &Layer,
_font_info: &FontInfo,
_global_metrics: &GlobalMetrics,
) -> Result<(NormalizedLocation, GlyphInstance), Error> {
let (contours, components) =
to_ir_contours_and_components(glyph.name.clone().into(), &layer.shapes)?;
let glyph_instance = GlyphInstance {
width:layer.width.into(),
height: None,
vertical_origin: None,
contours,
components,
};
Ok((location.clone(), glyph_instance))
}
#[derive(Debug)]
struct ColorPaletteWork {
_font_info: Arc<FontInfo>,
}
impl Work<Context, WorkId, Error> for ColorPaletteWork {
fn id(&self) -> WorkId {
WorkId::ColorPalettes
}
fn read_access(&self) -> Access<WorkId> {
Access::None
}
fn write_access(&self) -> Access<WorkId> {
Access::Variant(WorkId::ColorPalettes)
}
fn exec(&self, _context: &Context) -> Result<(), Error> {
Ok(())
}
}
#[derive(Debug)]
struct PaintGraphWork {
_font_info: Arc<FontInfo>,
}
impl Work<Context, WorkId, Error> for PaintGraphWork {
fn id(&self) -> WorkId {
WorkId::PaintGraph
}
fn read_access(&self) -> Access<WorkId> {
Access::None
}
fn write_access(&self) -> Access<WorkId> {
Access::Variant(WorkId::PaintGraph)
}
fn exec(&self, _context: &Context) -> Result<(), Error> {
debug!("TODO: actually create paint graph");
Ok(())
}
}
pub(crate) fn to_ir_contours_and_components(
glyph_name: GlyphName,
shapes: &[Shape],
) -> Result<(Vec<BezPath>, Vec<ir::Component>), BadGlyph> {
let mut contours = Vec::with_capacity(shapes.len());
let mut components = Vec::new();
for shape in shapes.iter() {
match shape {
Shape::Component(component) => {
components.push(to_ir_component(glyph_name.clone(), component))
}
Shape::Path(path) => contours.push(
to_ir_path(glyph_name.clone(), path)
.map_err(|e| BadGlyph::new(glyph_name.clone(), e))?,
),
}
}
Ok((contours, components))
}
fn to_ir_component(glyph_name: GlyphName, component: &Component) -> ir::Component {
trace!(
"{} reuses {} with transform {:?}",
glyph_name,
component.reference,
component.transform
);
ir::Component {
base: component.reference.as_str().into(),
transform: component.transform,
}
}
fn add_to_path<'a>(
path_builder: &'a mut GlyphPathBuilder,
nodes: impl Iterator<Item = &'a crate::Node>,
) -> Result<(), PathConversionError> {
for node in nodes {
match node.nodetype {
NodeType::Move => path_builder.move_to((node.x, node.y)),
NodeType::Line => path_builder.line_to((node.x, node.y)),
NodeType::Curve => path_builder.curve_to((node.x, node.y)),
NodeType::OffCurve => path_builder.offcurve((node.x, node.y)),
NodeType::QCurve => path_builder.qcurve_to((node.x, node.y)),
}?
}
Ok(())
}
fn to_ir_path(
glyph_name: GlyphName,
src_path: &crate::Path,
) -> Result<BezPath, PathConversionError> {
if src_path.nodes.is_empty() {
return Ok(BezPath::new());
}
let mut path_builder = GlyphPathBuilder::new(src_path.nodes.len());
if !src_path.closed {
if let Some(first) = src_path.nodes.first() {
if first.nodetype == NodeType::OffCurve {
return Err(PathConversionError::Parse(
"Open path starts with off-curve points".into(),
));
}
path_builder.move_to((first.x, first.y))?;
add_to_path(&mut path_builder, src_path.nodes[1..].iter())?;
}
} else if src_path
.nodes
.iter()
.any(|node| node.nodetype != NodeType::OffCurve)
{
let last_idx = src_path.nodes.len() - 1;
add_to_path(
&mut path_builder,
std::iter::once(&src_path.nodes[last_idx]).chain(&src_path.nodes[..last_idx]),
)?;
} else {
add_to_path(&mut path_builder, src_path.nodes.iter())?;
};
let path = path_builder.build()?;
trace!(
"Built a {} entry path for {glyph_name}",
path.elements().len(),
);
Ok(path)
}
pub(crate) fn to_ir_features(
features: &Option<String>,
include_dir: Option<std::path::PathBuf>,
) -> Result<ir::FeaturesSource, Error> {
Ok(ir::FeaturesSource::Memory {
fea_content: features.clone().unwrap_or_default(),
include_dir,
})
}
fn ir_axes(font: &Font) -> Result<fontdrasil::types::Axes, Error> {
font.fontdrasil_axes().map_err(|x| x.into())
}
#[derive(Debug)]
pub(crate) struct FontInfo {
pub font: Font,
pub axes: fontdrasil::types::Axes,
}
impl TryFrom<Font> for FontInfo {
type Error = Error;
fn try_from(font: Font) -> Result<Self, Self::Error> {
let axes = ir_axes(&font)?;
Ok(FontInfo {
font,
axes,
})
}
}