use std::{
collections::{BTreeMap, BTreeSet, HashMap, HashSet, hash_map::RandomState},
fmt::{Debug, Display},
io::Read,
path::PathBuf,
};
use indexmap::{IndexMap, IndexSet};
use kurbo::{Affine, BezPath, PathEl, Point};
use log::{log_enabled, trace, warn};
use ordered_float::OrderedFloat;
use serde::{Deserialize, Serialize, de::Error as _};
use smol_str::SmolStr;
use write_fonts::{
OtRound,
types::{GlyphId16, NameId, Tag},
};
use fontdrasil::{
coords::NormalizedLocation,
types::{Axes, GlyphName},
variations::{ModelDeltas, VariationModel},
};
use crate::{
error::{BadAnchor, BadAnchorReason, BadGlyph, BadGlyphKind, Error},
orchestration::{IdAware, Persistable, WorkId},
};
mod erase_open_corners;
mod path_builder;
mod static_metadata;
pub use erase_open_corners::erase_open_corners;
pub use path_builder::GlyphPathBuilder;
pub use static_metadata::{
Condition, ConditionSet, GdefCategories, MetaTableValues, MiscMetadata, NameKey, NamedInstance,
Panose, PostscriptNames, Rule, StaticMetadata, Substitution, VariableFeature,
};
pub const DEFAULT_VENDOR_ID: &str = "NONE";
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct GlyphOrder(IndexSet<GlyphName>);
impl Extend<GlyphName> for GlyphOrder {
fn extend<T: IntoIterator<Item = GlyphName>>(&mut self, iter: T) {
self.0.extend(iter)
}
}
impl FromIterator<GlyphName> for GlyphOrder {
fn from_iter<T: IntoIterator<Item = GlyphName>>(iter: T) -> Self {
GlyphOrder(iter.into_iter().collect::<IndexSet<_>>())
}
}
impl Eq for GlyphOrder {}
impl PartialEq for GlyphOrder {
fn eq(&self, other: &Self) -> bool {
self.len() == other.len() && self.iter().zip(other.iter()).all(|(a, b)| a == b)
}
}
impl GlyphOrder {
pub fn new() -> Self {
GlyphOrder(IndexSet::new())
}
pub fn glyph_id<Q>(&self, name: &Q) -> Option<GlyphId16>
where
Q: std::hash::Hash + indexmap::Equivalent<GlyphName> + ?Sized,
{
self.0.get_index_of(name).map(|i| GlyphId16::new(i as _))
}
pub fn glyph_name(&self, index: usize) -> Option<&GlyphName> {
self.0.get_index(index)
}
pub fn contains<Q>(&self, name: &Q) -> bool
where
Q: std::hash::Hash + indexmap::Equivalent<GlyphName> + ?Sized,
{
self.0.contains(name)
}
pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, &GlyphName)> {
self.0
.iter()
.enumerate()
.map(|(i, name)| (GlyphId16::new(i as _), name))
}
pub fn names(&self) -> impl Iterator<Item = &GlyphName> {
self.0.iter()
}
pub fn remove(&mut self, name: &GlyphName) -> bool {
self.0.shift_remove(name)
}
pub fn difference<'a>(
&'a self,
other: &'a GlyphOrder,
) -> indexmap::set::Difference<'a, GlyphName, RandomState> {
self.0.difference(&other.0)
}
pub fn insert(&mut self, name: GlyphName) -> bool {
self.0.insert(name)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub(crate) fn set_glyph_id(&mut self, name: &GlyphName, new_index: usize) {
match self.0.get_index_of(name) {
Some(index) if index == new_index => (), Some(index) => self.0.move_index(index, new_index),
None => {
self.insert(name.clone());
self.set_glyph_id(name, new_index)
}
}
}
}
impl IntoIterator for GlyphOrder {
type Item = GlyphName;
type IntoIter = <IndexSet<GlyphName> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
pub type KernPair = (KernSide, KernSide);
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
pub struct KerningGroups {
pub groups: BTreeMap<KernGroup, BTreeSet<GlyphName>>,
pub locations: BTreeSet<NormalizedLocation>,
pub old_to_new_group_names: BTreeMap<KernGroup, KernGroup>,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
pub struct KerningInstance {
pub location: NormalizedLocation,
pub kerns: BTreeMap<KernPair, OrderedFloat<f64>>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum KernGroup {
Side1(SmolStr),
Side2(SmolStr),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum KernSide {
Glyph(GlyphName),
Group(KernGroup),
}
impl Display for KernSide {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
KernSide::Glyph(g) => Display::fmt(g, f),
KernSide::Group(name) => write!(f, "@{name}"),
}
}
}
impl Display for KernGroup {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
KernGroup::Side1(name) => write!(f, "side1.{name}"),
KernGroup::Side2(name) => write!(f, "side2.{name}"),
}
}
}
impl Serialize for KernGroup {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
KernGroup::Side1(name) => serializer.serialize_str(&format!("side1.{name}")),
KernGroup::Side2(name) => serializer.serialize_str(&format!("side2.{name}")),
}
}
}
impl<'de> Deserialize<'de> for KernGroup {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.strip_prefix("side1.")
.map(|s| KernGroup::Side1(s.into()))
.or_else(|| s.strip_prefix("side2.").map(|s| KernGroup::Side2(s.into())))
.ok_or_else(|| D::Error::custom(format!("missing side1/side2 prefix: {s}")))
}
}
impl KernSide {
#[inline]
pub fn is_glyph(&self) -> bool {
matches!(self, KernSide::Glyph(_))
}
pub fn glyph_name(&self) -> Option<&GlyphName> {
match self {
KernSide::Glyph(glyph_name) => Some(glyph_name),
KernSide::Group(_) => None,
}
}
#[inline]
pub fn is_group(&self) -> bool {
matches!(self, KernSide::Group(_))
}
}
impl KernGroup {
pub fn flip(self) -> Self {
match self {
KernGroup::Side1(name) => KernGroup::Side2(name),
KernGroup::Side2(name) => KernGroup::Side1(name),
}
}
}
impl From<GlyphName> for KernSide {
fn from(src: GlyphName) -> KernSide {
KernSide::Glyph(src)
}
}
impl From<KernGroup> for KernSide {
fn from(src: KernGroup) -> KernSide {
KernSide::Group(src)
}
}
#[derive(Default, Debug)]
pub struct GlobalMetricsBuilder(
HashMap<GlobalMetric, HashMap<NormalizedLocation, OrderedFloat<f64>>>,
);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct GlobalMetrics(HashMap<GlobalMetric, ModelDeltas<f64>>);
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum GlobalMetric {
Ascender,
Descender,
HheaAscender,
HheaDescender,
HheaLineGap,
VheaAscender,
VheaDescender,
VheaLineGap,
Os2TypoAscender,
Os2TypoDescender,
Os2TypoLineGap,
Os2WinAscent,
Os2WinDescent,
CapHeight,
CaretSlopeRise,
CaretSlopeRun,
CaretOffset,
VheaCaretSlopeRise,
VheaCaretSlopeRun,
VheaCaretOffset,
UnderlineThickness,
UnderlinePosition,
XHeight,
StrikeoutPosition,
StrikeoutSize,
SubscriptXOffset,
SubscriptXSize,
SubscriptYOffset,
SubscriptYSize,
SuperscriptXOffset,
SuperscriptXSize,
SuperscriptYOffset,
SuperscriptYSize,
}
impl GlobalMetric {
pub fn mvar_tag(&self) -> Option<Tag> {
match self {
GlobalMetric::Os2TypoAscender => Some(Tag::new(b"hasc")),
GlobalMetric::Os2TypoDescender => Some(Tag::new(b"hdsc")),
GlobalMetric::Os2TypoLineGap => Some(Tag::new(b"hlgp")),
GlobalMetric::Os2WinAscent => Some(Tag::new(b"hcla")),
GlobalMetric::Os2WinDescent => Some(Tag::new(b"hcld")),
GlobalMetric::VheaAscender => Some(Tag::new(b"vasc")),
GlobalMetric::VheaDescender => Some(Tag::new(b"vdsc")),
GlobalMetric::VheaLineGap => Some(Tag::new(b"vlgp")),
GlobalMetric::CaretSlopeRise => Some(Tag::new(b"hcrs")),
GlobalMetric::CaretSlopeRun => Some(Tag::new(b"hcrn")),
GlobalMetric::CaretOffset => Some(Tag::new(b"hcof")),
GlobalMetric::VheaCaretSlopeRise => Some(Tag::new(b"vcrs")),
GlobalMetric::VheaCaretSlopeRun => Some(Tag::new(b"vcrn")),
GlobalMetric::VheaCaretOffset => Some(Tag::new(b"vcof")),
GlobalMetric::XHeight => Some(Tag::new(b"xhgt")),
GlobalMetric::CapHeight => Some(Tag::new(b"cpht")),
GlobalMetric::SubscriptXSize => Some(Tag::new(b"sbxs")),
GlobalMetric::SubscriptYSize => Some(Tag::new(b"sbys")),
GlobalMetric::SubscriptXOffset => Some(Tag::new(b"sbxo")),
GlobalMetric::SubscriptYOffset => Some(Tag::new(b"sbyo")),
GlobalMetric::SuperscriptXSize => Some(Tag::new(b"spxs")),
GlobalMetric::SuperscriptYSize => Some(Tag::new(b"spys")),
GlobalMetric::SuperscriptXOffset => Some(Tag::new(b"spxo")),
GlobalMetric::SuperscriptYOffset => Some(Tag::new(b"spyo")),
GlobalMetric::StrikeoutSize => Some(Tag::new(b"strs")),
GlobalMetric::StrikeoutPosition => Some(Tag::new(b"stro")),
GlobalMetric::UnderlineThickness => Some(Tag::new(b"unds")),
GlobalMetric::UnderlinePosition => Some(Tag::new(b"undo")),
_ => None,
}
}
}
fn adjust_offset(offset: f64, angle: f64) -> f64 {
if angle.abs() >= f64::EPSILON {
offset * (-angle).to_radians().tan()
} else {
0.0
}
}
impl GlobalMetricsBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn populate_defaults(
&mut self,
pos: &NormalizedLocation,
units_per_em: u16,
x_height: Option<f64>,
ascender: Option<f64>,
descender: Option<f64>,
italic_angle: Option<f64>,
) {
let units_per_em = units_per_em as f64;
let mut set_if_absent = |metric, value| self.set_if_absent(metric, pos.clone(), value);
let ascender = ascender.unwrap_or(0.8 * units_per_em);
let descender = descender.unwrap_or(-0.2 * units_per_em);
set_if_absent(GlobalMetric::Ascender, ascender);
set_if_absent(GlobalMetric::Descender, descender);
let typo_line_gap = units_per_em * 1.2 + descender - ascender;
let typo_line_gap = if typo_line_gap > 0.0 {
typo_line_gap
} else {
0.0
};
set_if_absent(GlobalMetric::Os2TypoLineGap, typo_line_gap);
set_if_absent(GlobalMetric::Os2TypoAscender, ascender);
set_if_absent(GlobalMetric::Os2TypoDescender, descender);
set_if_absent(GlobalMetric::HheaAscender, ascender + typo_line_gap);
set_if_absent(GlobalMetric::HheaDescender, descender);
set_if_absent(GlobalMetric::HheaLineGap, 0.0);
set_if_absent(GlobalMetric::Os2WinAscent, ascender + typo_line_gap);
set_if_absent(GlobalMetric::Os2WinDescent, descender.abs());
set_if_absent(GlobalMetric::CapHeight, 0.7 * units_per_em);
let x_height = x_height.unwrap_or(0.5 * units_per_em);
set_if_absent(GlobalMetric::XHeight, x_height);
let italic_angle = italic_angle.unwrap_or(0.0);
set_if_absent(GlobalMetric::CaretSlopeRise, units_per_em);
set_if_absent(
GlobalMetric::CaretSlopeRun,
adjust_offset(units_per_em, italic_angle),
);
set_if_absent(GlobalMetric::CaretOffset, 0.0);
let subscript_x_size = units_per_em * 0.65;
let subscript_y_size = units_per_em * 0.60;
let subscript_y_offset = units_per_em * 0.075;
let superscript_y_offset = units_per_em * 0.35;
set_if_absent(GlobalMetric::SubscriptXSize, subscript_x_size);
set_if_absent(GlobalMetric::SubscriptYSize, subscript_y_size);
set_if_absent(
GlobalMetric::SubscriptXOffset,
adjust_offset(-subscript_y_offset, italic_angle),
);
set_if_absent(GlobalMetric::SubscriptYOffset, subscript_y_offset);
set_if_absent(GlobalMetric::SuperscriptXSize, subscript_x_size);
set_if_absent(GlobalMetric::SuperscriptYSize, subscript_y_size);
set_if_absent(
GlobalMetric::SuperscriptXOffset,
adjust_offset(superscript_y_offset, italic_angle),
);
set_if_absent(GlobalMetric::SuperscriptYOffset, superscript_y_offset);
let underline_thickness =
set_if_absent(GlobalMetric::UnderlineThickness, 0.05 * units_per_em);
set_if_absent(GlobalMetric::UnderlinePosition, -0.1 * units_per_em);
set_if_absent(GlobalMetric::StrikeoutSize, underline_thickness.0);
set_if_absent(
GlobalMetric::StrikeoutPosition,
if x_height != 0. {
x_height * 0.6
} else {
units_per_em * 0.22
},
);
set_if_absent(GlobalMetric::VheaCaretSlopeRise, 0.0);
set_if_absent(GlobalMetric::VheaCaretSlopeRun, 1.0);
set_if_absent(GlobalMetric::VheaCaretOffset, 0.0);
set_if_absent(GlobalMetric::VheaAscender, 0.0);
set_if_absent(GlobalMetric::VheaDescender, 0.0);
set_if_absent(GlobalMetric::VheaLineGap, 0.0);
}
fn values_mut(
&mut self,
metric: GlobalMetric,
) -> &mut HashMap<NormalizedLocation, OrderedFloat<f64>> {
self.0.entry(metric).or_default()
}
pub fn set(
&mut self,
metric: GlobalMetric,
pos: NormalizedLocation,
value: impl Into<OrderedFloat<f64>>,
) {
self.values_mut(metric).insert(pos, value.into());
}
pub fn set_if_absent(
&mut self,
metric: GlobalMetric,
pos: NormalizedLocation,
value: impl Into<OrderedFloat<f64>>,
) -> OrderedFloat<f64> {
*self.values_mut(metric).entry(pos).or_insert(value.into())
}
pub fn set_if_some(
&mut self,
metric: GlobalMetric,
pos: NormalizedLocation,
maybe_value: Option<impl Into<OrderedFloat<f64>>>,
) {
if let Some(value) = maybe_value {
self.set(metric, pos, value.into().into_inner());
}
}
pub fn build(self, axes: &Axes) -> Result<GlobalMetrics, Error> {
let deltas = self
.0
.into_iter()
.map(|(tag, values)| -> Result<_, Error> {
let model =
VariationModel::new(values.keys().cloned().collect(), axes.axis_order());
let sources = values
.into_iter()
.map(|(loc, value)| (loc, vec![OtRound::<f64>::ot_round(value.into_inner())]))
.collect();
let deltas = model
.deltas(&sources)
.map_err(|e| Error::MetricDeltaError(tag, e))?;
Ok((tag, deltas))
})
.collect::<Result<HashMap<_, _>, _>>()?;
Ok(GlobalMetrics(deltas))
}
}
impl GlobalMetrics {
pub fn deltas(&self, metric: GlobalMetric) -> &ModelDeltas<f64> {
self.0.get(&metric).unwrap()
}
pub fn get(&self, metric: GlobalMetric, pos: &NormalizedLocation) -> OrderedFloat<f64> {
let deltas = self.deltas(metric);
VariationModel::empty()
.interpolate_from_deltas(pos, deltas)
.iter()
.map(|float| OrderedFloat::<f64>::from(*float))
.sum()
}
pub fn at(&self, pos: &NormalizedLocation) -> GlobalMetricsInstance {
GlobalMetricsInstance {
pos: pos.clone(),
ascender: self.get(GlobalMetric::Ascender, pos),
descender: self.get(GlobalMetric::Descender, pos),
caret_slope_rise: self.get(GlobalMetric::CaretSlopeRise, pos),
caret_slope_run: self.get(GlobalMetric::CaretSlopeRun, pos),
caret_offset: self.get(GlobalMetric::CaretOffset, pos),
cap_height: self.get(GlobalMetric::CapHeight, pos),
x_height: self.get(GlobalMetric::XHeight, pos),
subscript_x_size: self.get(GlobalMetric::SubscriptXSize, pos),
subscript_y_size: self.get(GlobalMetric::SubscriptYSize, pos),
subscript_x_offset: self.get(GlobalMetric::SubscriptXOffset, pos),
subscript_y_offset: self.get(GlobalMetric::SubscriptYOffset, pos),
superscript_x_size: self.get(GlobalMetric::SuperscriptXSize, pos),
superscript_y_size: self.get(GlobalMetric::SuperscriptYSize, pos),
superscript_x_offset: self.get(GlobalMetric::SuperscriptXOffset, pos),
superscript_y_offset: self.get(GlobalMetric::SuperscriptYOffset, pos),
strikeout_size: self.get(GlobalMetric::StrikeoutSize, pos),
strikeout_position: self.get(GlobalMetric::StrikeoutPosition, pos),
os2_typo_ascender: self.get(GlobalMetric::Os2TypoAscender, pos),
os2_typo_descender: self.get(GlobalMetric::Os2TypoDescender, pos),
os2_typo_line_gap: self.get(GlobalMetric::Os2TypoLineGap, pos),
os2_win_ascent: self.get(GlobalMetric::Os2WinAscent, pos),
os2_win_descent: self.get(GlobalMetric::Os2WinDescent, pos),
hhea_ascender: self.get(GlobalMetric::HheaAscender, pos),
hhea_descender: self.get(GlobalMetric::HheaDescender, pos),
hhea_line_gap: self.get(GlobalMetric::HheaLineGap, pos),
vhea_ascender: self.get(GlobalMetric::VheaAscender, pos),
vhea_descender: self.get(GlobalMetric::VheaDescender, pos),
vhea_line_gap: self.get(GlobalMetric::VheaLineGap, pos),
vhea_caret_slope_rise: self.get(GlobalMetric::VheaCaretSlopeRise, pos),
vhea_caret_slope_run: self.get(GlobalMetric::VheaCaretSlopeRun, pos),
vhea_caret_offset: self.get(GlobalMetric::VheaCaretOffset, pos),
underline_thickness: self.get(GlobalMetric::UnderlineThickness, pos),
underline_position: self.get(GlobalMetric::UnderlinePosition, pos),
}
}
pub fn iter(&self) -> impl Iterator<Item = (&GlobalMetric, &ModelDeltas<f64>)> + '_ {
self.0.iter()
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct GlobalMetricsInstance {
pub pos: NormalizedLocation,
pub ascender: OrderedFloat<f64>,
pub descender: OrderedFloat<f64>,
pub caret_slope_rise: OrderedFloat<f64>,
pub caret_slope_run: OrderedFloat<f64>,
pub caret_offset: OrderedFloat<f64>,
pub cap_height: OrderedFloat<f64>,
pub x_height: OrderedFloat<f64>,
pub os2_typo_ascender: OrderedFloat<f64>,
pub os2_typo_descender: OrderedFloat<f64>,
pub os2_typo_line_gap: OrderedFloat<f64>,
pub os2_win_ascent: OrderedFloat<f64>,
pub os2_win_descent: OrderedFloat<f64>,
pub hhea_ascender: OrderedFloat<f64>,
pub hhea_descender: OrderedFloat<f64>,
pub hhea_line_gap: OrderedFloat<f64>,
pub vhea_ascender: OrderedFloat<f64>,
pub vhea_descender: OrderedFloat<f64>,
pub vhea_line_gap: OrderedFloat<f64>,
pub vhea_caret_slope_rise: OrderedFloat<f64>,
pub vhea_caret_slope_run: OrderedFloat<f64>,
pub vhea_caret_offset: OrderedFloat<f64>,
pub strikeout_position: OrderedFloat<f64>,
pub strikeout_size: OrderedFloat<f64>,
pub subscript_x_offset: OrderedFloat<f64>,
pub subscript_x_size: OrderedFloat<f64>,
pub subscript_y_offset: OrderedFloat<f64>,
pub subscript_y_size: OrderedFloat<f64>,
pub superscript_x_offset: OrderedFloat<f64>,
pub superscript_x_size: OrderedFloat<f64>,
pub superscript_y_offset: OrderedFloat<f64>,
pub superscript_y_size: OrderedFloat<f64>,
pub underline_thickness: OrderedFloat<f64>,
pub underline_position: OrderedFloat<f64>,
}
#[doc(hidden)]
pub mod test_helpers {
use ordered_float::OrderedFloat;
use super::GlobalMetricsInstance;
pub trait Round2 {
fn round2(self) -> Self;
}
impl Round2 for OrderedFloat<f64> {
fn round2(self) -> Self {
OrderedFloat((self.0 * 100.0).round() / 100.0)
}
}
impl Round2 for GlobalMetricsInstance {
fn round2(self) -> Self {
GlobalMetricsInstance {
pos: self.pos.clone(),
ascender: self.ascender.round2(),
descender: self.descender.round2(),
caret_slope_rise: self.caret_slope_rise.round2(),
vhea_caret_slope_rise: self.vhea_caret_slope_rise.round2(),
cap_height: self.cap_height.round2(),
x_height: self.x_height.round2(),
subscript_x_size: self.subscript_x_size.round2(),
subscript_y_size: self.subscript_y_size.round2(),
subscript_y_offset: self.subscript_y_offset.round2(),
superscript_x_size: self.superscript_x_size.round2(),
superscript_y_size: self.superscript_y_size.round2(),
superscript_y_offset: self.superscript_y_offset.round2(),
strikeout_position: self.strikeout_position.round2(),
strikeout_size: self.strikeout_size.round2(),
os2_typo_ascender: self.os2_typo_ascender.round2(),
os2_typo_descender: self.os2_typo_descender.round2(),
os2_typo_line_gap: self.os2_typo_line_gap.round2(),
os2_win_ascent: self.os2_win_ascent.round2(),
os2_win_descent: self.os2_win_descent.round2(),
hhea_ascender: self.hhea_ascender.round2(),
hhea_descender: self.hhea_descender.round2(),
hhea_line_gap: self.hhea_line_gap.round2(),
vhea_ascender: self.vhea_ascender.round2(),
vhea_descender: self.vhea_descender.round2(),
vhea_line_gap: self.vhea_line_gap.round2(),
underline_thickness: self.underline_thickness.round2(),
underline_position: self.underline_position.round2(),
caret_slope_run: self.caret_slope_run.round2(),
caret_offset: self.caret_offset.round2(),
vhea_caret_slope_run: self.vhea_caret_slope_run.round2(),
vhea_caret_offset: self.vhea_caret_offset.round2(),
subscript_x_offset: self.subscript_x_offset.round2(),
superscript_x_offset: self.superscript_x_offset.round2(),
}
}
}
}
#[derive(Default)]
pub struct NameBuilder {
names: HashMap<NameKey, String>,
name_to_key: HashMap<NameId, NameKey>,
version_major: i32,
version_minor: u32,
}
fn is_ribbi(style_name: &str) -> bool {
matches!(
style_name.to_lowercase().as_str(),
"regular" | "italic" | "bold" | "bold italic"
)
}
impl NameBuilder {
pub fn make_family_name(family: &str, subfamily: &str, drop_rbbi_suffix: bool) -> String {
let mut family = vec![family];
family.extend(subfamily.split_ascii_whitespace());
if drop_rbbi_suffix {
while let Some(last) = family.last().copied() {
if matches!(last, "Regular" | "Bold" | "Italic") {
family.pop();
} else {
break;
}
}
}
family.join(" ")
}
pub fn add(&mut self, name_id: NameId, value: String) {
let key = NameKey::new(name_id, &value);
self.names.insert(key, value);
self.name_to_key.insert(name_id, key);
}
pub fn remove(&mut self, name_id: NameId) {
if let Some(key) = self.name_to_key.remove(&name_id) {
self.names.remove(&key);
}
}
pub fn add_if_present(&mut self, name_id: NameId, value: &Option<String>) {
if let Some(value) = value.as_ref().cloned() {
self.add(name_id, value);
}
}
pub fn apply_fallback(&mut self, name_id: NameId, fallbacks: &[NameId]) {
if self.contains_key(name_id) {
return;
}
if let Some(fallback) = self.get_fallback_or_default(name_id, fallbacks) {
self.add(name_id, fallback.to_string());
}
}
pub fn contains_key(&self, name_id: NameId) -> bool {
self.name_to_key.contains_key(&name_id)
}
pub fn get(&self, name_id: NameId) -> Option<&str> {
self.name_to_key
.get(&name_id)
.and_then(|key| self.names.get(key).map(|s| s.as_str()))
}
pub fn into_inner(self) -> HashMap<NameKey, String> {
self.names
}
pub fn get_fallback_or_default(&self, name_id: NameId, fallbacks: &[NameId]) -> Option<&str> {
fallbacks
.iter()
.find_map(|n| {
let key = self.name_to_key.get(n)?;
self.names.get(key).map(|s| s.as_str())
})
.or_else(|| default_value(name_id))
}
fn fallback_string(&self, name_id: NameId, fallback: NameId) -> String {
self.get_fallback_or_default(name_id, &[fallback])
.unwrap()
.to_string()
}
pub fn apply_default_fallbacks(&mut self, vendor_id: &str) {
let family_suffix = if self.contains_key(NameId::SUBFAMILY_NAME) {
None
} else {
let fallback_subfamily =
self.fallback_string(NameId::SUBFAMILY_NAME, NameId::TYPOGRAPHIC_SUBFAMILY_NAME);
let (fallback_subfamily, family_suffix) = if is_ribbi(&fallback_subfamily) {
(fallback_subfamily, None)
} else {
("Regular".to_string(), Some(fallback_subfamily))
};
self.add(NameId::SUBFAMILY_NAME, fallback_subfamily);
family_suffix
};
if !self.contains_key(NameId::FAMILY_NAME) {
let mut fallback_family =
self.fallback_string(NameId::FAMILY_NAME, NameId::TYPOGRAPHIC_FAMILY_NAME);
if let Some(family_suffix) = family_suffix.as_ref() {
fallback_family.push(' ');
fallback_family.push_str(family_suffix);
}
self.add(NameId::FAMILY_NAME, fallback_family);
};
self.apply_fallback(NameId::TYPOGRAPHIC_FAMILY_NAME, &[NameId::FAMILY_NAME]);
self.apply_fallback(
NameId::TYPOGRAPHIC_SUBFAMILY_NAME,
&[NameId::SUBFAMILY_NAME],
);
if !self.contains_key(NameId::VERSION_STRING) {
let major = self.version_major;
let minor = self.version_minor;
self.add(
NameId::VERSION_STRING,
format!("Version {major}.{minor:0>3}"),
);
}
if !self.contains_key(NameId::FULL_NAME) {
self.add(
NameId::FULL_NAME,
NameBuilder::make_family_name(
self.get(NameId::TYPOGRAPHIC_FAMILY_NAME)
.unwrap_or_default(),
self.get(NameId::TYPOGRAPHIC_SUBFAMILY_NAME)
.unwrap_or_default(),
false,
),
);
}
if !self.contains_key(NameId::POSTSCRIPT_NAME) {
let mut family = self
.get(NameId::TYPOGRAPHIC_FAMILY_NAME)
.unwrap_or_default()
.replace(" ", "");
let subfamily = self
.get(NameId::TYPOGRAPHIC_SUBFAMILY_NAME)
.unwrap_or_default();
if !subfamily.is_empty() {
family += "-";
}
let mut value = NameBuilder::make_family_name(&family, subfamily, false);
normalize_for_postscript(&mut value, false);
self.add(NameId::POSTSCRIPT_NAME, value);
}
if !self.contains_key(NameId::UNIQUE_ID) {
let version = self
.get(NameId::VERSION_STRING)
.unwrap()
.replace("Version ", "");
let postscript_name = self.get(NameId::POSTSCRIPT_NAME).unwrap();
self.add(
NameId::UNIQUE_ID,
format!("{version};{vendor_id};{postscript_name}"),
);
}
let family = self.get(NameId::FAMILY_NAME);
let sub_family = self.get(NameId::SUBFAMILY_NAME);
if (family.is_some() && sub_family.is_some())
&& (family == self.get(NameId::TYPOGRAPHIC_FAMILY_NAME)
&& sub_family == self.get(NameId::TYPOGRAPHIC_SUBFAMILY_NAME))
{
self.remove(NameId::TYPOGRAPHIC_FAMILY_NAME);
self.remove(NameId::TYPOGRAPHIC_SUBFAMILY_NAME);
}
}
pub fn set_version(&mut self, major: i32, minor: u32) {
self.version_major = major;
self.version_minor = minor;
}
}
fn normalize_for_postscript(value: &mut String, allow_spaces: bool) {
value.retain(|c| {
if !allow_spaces && c.is_ascii_whitespace() {
return false;
}
if "[](){}<>/%".contains(c) {
return false;
}
if !(33..127).contains(&(c as u32)) {
warn!("fontmake performs decomposition, we just ignore the character");
return false;
}
true
});
}
pub fn default_value(name: NameId) -> Option<&'static str> {
match name {
NameId::FAMILY_NAME => Some("New Font"),
NameId::SUBFAMILY_NAME => Some("Regular"),
_ => None,
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum FeaturesSource {
Empty,
File {
fea_file: PathBuf,
include_dir: Option<PathBuf>,
},
Memory {
fea_content: String,
include_dir: Option<PathBuf>,
},
}
impl FeaturesSource {
pub fn empty() -> FeaturesSource {
FeaturesSource::Empty
}
pub fn from_file(fea_file: PathBuf, include_dir: Option<PathBuf>) -> FeaturesSource {
FeaturesSource::File {
fea_file,
include_dir,
}
}
pub fn from_string(fea_content: String) -> FeaturesSource {
FeaturesSource::Memory {
fea_content,
include_dir: None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct GlyphAnchors {
pub glyph_name: GlyphName,
pub anchors: Vec<Anchor>,
}
impl GlyphAnchors {
pub fn new(glyph_name: GlyphName, anchors: Vec<Anchor>) -> Self {
GlyphAnchors {
glyph_name,
anchors,
}
}
pub fn contains_marks(&self) -> bool {
self.anchors
.iter()
.any(|a| matches!(a.kind, AnchorKind::Mark(_)))
}
}
pub type GroupName = SmolStr;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum AnchorKind {
Base(GroupName),
Mark(GroupName),
Ligature {
group_name: GroupName,
index: usize,
},
ComponentMarker(usize),
Caret(usize),
VCaret(usize),
CursiveEntry,
CursiveExit,
}
impl AnchorKind {
pub fn new(name: impl AsRef<str>) -> Result<AnchorKind, BadAnchorReason> {
let name = name.as_ref();
if name == "entry" {
return Ok(AnchorKind::CursiveEntry);
}
if name == "exit" {
return Ok(AnchorKind::CursiveExit);
}
if let Some(suffix) = name
.strip_prefix("caret_")
.or_else(|| name.strip_prefix("vcaret_"))
{
if let Ok(index) = suffix.parse::<usize>() {
if index == 0 {
return Err(BadAnchorReason::ZeroIndex);
} else if name.starts_with('v') {
return Ok(AnchorKind::VCaret(index));
} else {
return Ok(AnchorKind::Caret(index));
}
} else {
if !suffix.is_empty() {
log::warn!("anchor '{name}' will be treated as a caret");
}
if name.starts_with('v') {
return Ok(AnchorKind::VCaret(1));
} else {
return Ok(AnchorKind::Caret(1));
}
}
}
if let Some(suffix) = name.strip_prefix('_') {
if let Ok(index) = suffix.parse::<usize>() {
if index == 0 {
return Err(BadAnchorReason::ZeroIndex);
} else {
return Ok(AnchorKind::ComponentMarker(index));
}
}
if suffix.is_empty() {
return Err(BadAnchorReason::NilMarkGroup);
}
if let Some((_, suffix)) = suffix.rsplit_once('_')
&& suffix.parse::<usize>().is_ok()
{
return Err(BadAnchorReason::NumberedMarkAnchor);
}
return Ok(AnchorKind::Mark(suffix.into()));
} else if let Some((name, suffix)) = name.rsplit_once('_') {
if let Ok(index) = suffix.parse::<usize>() {
if index == 0 {
return Err(BadAnchorReason::ZeroIndex);
} else {
return Ok(AnchorKind::Ligature {
group_name: name.into(),
index,
});
}
}
}
Ok(AnchorKind::Base(name.into()))
}
pub fn is_attaching(&self) -> bool {
matches!(self, AnchorKind::Base(_) | AnchorKind::Ligature { .. })
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Anchor {
pub kind: AnchorKind,
pub positions: HashMap<NormalizedLocation, Point>,
}
impl Anchor {
pub fn default_pos(&self) -> Point {
self.positions
.iter()
.find(|(loc, _)| loc.is_default())
.map(|(_, p)| *p)
.unwrap()
}
pub fn mark_group_name(&self) -> Option<&SmolStr> {
match &self.kind {
AnchorKind::Base(group_name)
| AnchorKind::Mark(group_name)
| AnchorKind::Ligature { group_name, .. } => Some(group_name),
_ => None,
}
}
pub fn is_mark(&self) -> bool {
matches!(self.kind, AnchorKind::Mark(_))
}
pub fn is_component_marker(&self) -> bool {
matches!(self.kind, AnchorKind::ComponentMarker(_))
}
pub fn is_cursive(&self) -> bool {
matches!(self.kind, AnchorKind::CursiveEntry)
|| matches!(self.kind, AnchorKind::CursiveExit)
}
pub fn ligature_index(&self) -> Option<usize> {
match self.kind {
AnchorKind::Ligature { index, .. } | AnchorKind::ComponentMarker(index) => Some(index),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct AnchorBuilder {
glyph_name: GlyphName,
anchors: HashMap<SmolStr, HashMap<NormalizedLocation, Point>>,
}
impl AnchorBuilder {
pub fn new(glyph_name: GlyphName) -> Self {
Self {
glyph_name,
anchors: Default::default(),
}
}
pub fn add(
&mut self,
anchor_name: SmolStr,
loc: NormalizedLocation,
pos: Point,
) -> Result<(), BadGlyph> {
if self
.anchors
.entry(anchor_name.clone())
.or_default()
.insert(loc.clone(), pos)
.is_some()
{
return Err(BadGlyph::new(
self.glyph_name.clone(),
BadAnchor::new(anchor_name, BadAnchorReason::Ambiguous(loc)),
));
}
Ok(())
}
pub fn build(self) -> Result<GlyphAnchors, BadGlyph> {
for (anchor, positions) in &self.anchors {
if !positions.keys().any(|loc| !loc.has_any_non_zero()) {
return Err(BadGlyph::new(
self.glyph_name.clone(),
BadAnchor::new(anchor.clone(), BadAnchorReason::NoDefault),
));
}
}
let anchors = self
.anchors
.into_iter()
.map(|(name, positions)| {
AnchorKind::new(&name)
.map(|type_| Anchor {
kind: type_,
positions,
})
.map_err(|reason| BadAnchor::new(name, reason))
})
.collect::<Result<_, _>>()
.map_err(|e| BadGlyph::new(self.glyph_name.clone(), e))?;
Ok(GlyphAnchors::new(self.glyph_name, anchors))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Glyph {
pub name: GlyphName,
pub emit_to_binary: bool,
pub codepoints: HashSet<u32>, default_location: NormalizedLocation,
sources: HashMap<NormalizedLocation, GlyphInstance>,
has_consistent_2x2_transforms: bool,
has_overflowing_2x2_transforms: bool,
}
fn has_consistent_2x2_transforms(
name: &GlyphName,
sources: &HashMap<NormalizedLocation, GlyphInstance>,
) -> bool {
let mut instances = sources.values();
let Some(first) = instances.next() else {
return true; };
let consistent = instances.all(|inst| {
if first.components.len() != inst.components.len() {
return false;
}
first
.components
.iter()
.zip(inst.components.iter())
.all(|(c1, c2)| {
c1.base == c2.base && c1.transform.as_coeffs()[..4] == c2.transform.as_coeffs()[..4]
})
});
if log_enabled!(log::Level::Trace) && !consistent {
trace!("{name} has inconsistent component names or 2x2 transforms");
}
consistent
}
fn has_overflowing_2x2_transforms(
name: &GlyphName,
sources: &HashMap<NormalizedLocation, GlyphInstance>,
) -> bool {
let overflow_info = sources.iter().find_map(|(location, instance)| {
instance.components.iter().find_map(|component| {
component.transform.as_coeffs()[..4]
.iter()
.find(|&value| !(-2.0..=2.0).contains(value))
.map(|&value| (location.clone(), component.base.as_str(), value))
})
});
if log_enabled!(log::Level::Trace)
&& let Some((location, component_name, value)) = overflow_info.as_ref()
{
trace!(
"{} at location {:?} has component '{}' \
with a transform value ({}) overflowing [-2.0, 2.0] range",
name, location, component_name, value
);
}
overflow_info.is_some()
}
impl Glyph {
pub fn new(
name: GlyphName,
emit_to_binary: bool,
codepoints: HashSet<u32>,
instances: HashMap<NormalizedLocation, GlyphInstance>,
) -> Result<Self, BadGlyph> {
if instances.is_empty() {
return Err(BadGlyph::new(name, BadGlyphKind::NoInstances));
}
let mut defaults = instances
.keys()
.filter(|loc| !loc.iter().any(|(_, c)| c.into_inner() != 0.0));
let default_location = match (defaults.next(), defaults.next()) {
(None, _) => return Err(BadGlyph::new(name, BadGlyphKind::NoDefaultLocation)),
(Some(_), Some(_)) => {
return Err(BadGlyph::new(name, BadGlyphKind::MultipleDefaultLocations));
}
(Some(pos), None) => pos.to_owned(),
};
let has_consistent_2x2_transforms = has_consistent_2x2_transforms(&name, &instances);
let has_overflowing_2x2_transforms = has_overflowing_2x2_transforms(&name, &instances);
Ok(Glyph {
name,
emit_to_binary,
codepoints,
default_location,
sources: instances,
has_consistent_2x2_transforms,
has_overflowing_2x2_transforms,
})
}
pub fn default_instance(&self) -> &GlyphInstance {
self.sources.get(&self.default_location).unwrap()
}
#[cfg(test)]
pub fn default_instance_mut(&mut self) -> &mut GlyphInstance {
self.sources.get_mut(&self.default_location).unwrap()
}
pub fn sources(&self) -> &HashMap<NormalizedLocation, GlyphInstance> {
&self.sources
}
pub fn sources_mut(&mut self) -> &mut HashMap<NormalizedLocation, GlyphInstance> {
&mut self.sources
}
pub fn source_mut(&mut self, loc: &NormalizedLocation) -> Option<&mut GlyphInstance> {
self.sources.get_mut(loc)
}
pub(crate) fn component_names(&self) -> impl Iterator<Item = &GlyphName> {
self.sources
.values()
.flat_map(|inst| inst.components.iter().map(|comp| &comp.base))
}
pub(crate) fn has_mixed_contours_and_components(&self) -> bool {
self.sources()
.values()
.any(|inst| !inst.components.is_empty() && !inst.contours.is_empty())
}
#[inline]
pub(crate) fn has_consistent_components(&self) -> bool {
self.has_consistent_2x2_transforms
}
pub(crate) fn has_nonidentity_2x2(&self) -> bool {
self.sources
.values()
.flat_map(|inst| inst.components.iter())
.any(Component::has_nonidentity_2x2)
}
pub(crate) fn has_overflowing_component_transforms(&self) -> bool {
self.has_overflowing_2x2_transforms
}
}
impl IdAware<WorkId> for Glyph {
fn id(&self) -> WorkId {
WorkId::Glyph(self.name.clone())
}
}
impl Persistable for Glyph {
fn read(from: &mut dyn Read) -> Self {
serde_yaml::from_reader(from).unwrap()
}
fn write(&self, to: &mut dyn std::io::Write) {
serde_yaml::to_writer(to, self).unwrap();
}
}
impl Persistable for GlyphOrder {
fn read(from: &mut dyn Read) -> Self {
serde_yaml::from_reader(from).unwrap()
}
fn write(&self, to: &mut dyn std::io::Write) {
serde_yaml::to_writer(to, self).unwrap();
}
}
impl Persistable for GlobalMetrics {
fn read(from: &mut dyn Read) -> Self {
serde_yaml::from_reader(from).unwrap()
}
fn write(&self, to: &mut dyn std::io::Write) {
serde_yaml::to_writer(to, self).unwrap();
}
}
impl Persistable for FeaturesSource {
fn read(from: &mut dyn Read) -> Self {
serde_yaml::from_reader(from).unwrap()
}
fn write(&self, to: &mut dyn std::io::Write) {
serde_yaml::to_writer(to, self).unwrap();
}
}
impl Persistable for KerningGroups {
fn read(from: &mut dyn Read) -> Self {
serde_yaml::from_reader(from).unwrap()
}
fn write(&self, to: &mut dyn std::io::Write) {
serde_yaml::to_writer(to, self).unwrap();
}
}
impl Persistable for KerningInstance {
fn read(from: &mut dyn Read) -> Self {
serde_yaml::from_reader(from).unwrap()
}
fn write(&self, to: &mut dyn std::io::Write) {
serde_yaml::to_writer(to, self).unwrap();
}
}
impl IdAware<WorkId> for KerningInstance {
fn id(&self) -> WorkId {
WorkId::KernInstance(self.location.clone())
}
}
impl IdAware<WorkId> for GlyphAnchors {
fn id(&self) -> WorkId {
WorkId::Anchor(self.glyph_name.clone())
}
}
impl Persistable for GlyphAnchors {
fn read(from: &mut dyn Read) -> Self {
serde_yaml::from_reader(from).unwrap()
}
fn write(&self, to: &mut dyn std::io::Write) {
serde_yaml::to_writer(to, self).unwrap();
}
}
impl Persistable for ColorPalettes {
fn read(from: &mut dyn Read) -> Self {
serde_yaml::from_reader(from).unwrap()
}
fn write(&self, to: &mut dyn std::io::Write) {
serde_yaml::to_writer(to, self).unwrap();
}
}
impl Persistable for ColorGlyphs {
fn read(from: &mut dyn Read) -> Self {
serde_yaml::from_reader(from).unwrap()
}
fn write(&self, to: &mut dyn std::io::Write) {
serde_yaml::to_writer(to, self).unwrap();
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GlyphBuilder {
pub name: GlyphName,
pub emit_to_binary: bool,
pub codepoints: HashSet<u32>, pub sources: HashMap<NormalizedLocation, GlyphInstance>,
}
impl GlyphBuilder {
pub fn new(name: GlyphName) -> Self {
Self {
name,
emit_to_binary: true,
codepoints: HashSet::new(),
sources: HashMap::new(),
}
}
pub fn try_add_source(
&mut self,
unique_location: &NormalizedLocation,
source: GlyphInstance,
) -> Result<(), BadGlyph> {
if self.sources.contains_key(unique_location) {
return Err(BadGlyph::new(
self.name.clone(),
BadGlyphKind::DuplicateLocation(unique_location.clone()),
));
}
self.sources.insert(unique_location.clone(), source);
Ok(())
}
pub fn clear_components(&mut self) {
for src in self.sources.values_mut() {
src.components.clear();
}
}
pub fn build(self) -> Result<Glyph, BadGlyph> {
Glyph::new(
self.name,
self.emit_to_binary,
self.codepoints,
self.sources,
)
}
}
impl From<Glyph> for GlyphBuilder {
fn from(value: Glyph) -> Self {
Self {
name: value.name,
emit_to_binary: value.emit_to_binary,
codepoints: value.codepoints,
sources: value.sources,
}
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
pub struct GlyphInstance {
pub width: f64,
pub height: Option<f64>,
pub vertical_origin: Option<f64>,
pub contours: Vec<BezPath>,
pub components: Vec<Component>,
}
impl GlyphInstance {
pub fn path_elements(&self) -> String {
fn path_el_type(el: &PathEl) -> &'static str {
match el {
PathEl::MoveTo(..) => "M",
PathEl::LineTo(..) => "L",
PathEl::QuadTo(..) => "Q",
PathEl::CurveTo(..) => "C",
PathEl::ClosePath => "Z",
}
}
self.contours
.iter()
.flat_map(|c| c.elements().iter().map(path_el_type))
.collect()
}
pub fn height(&self, metrics: &GlobalMetricsInstance) -> u16 {
self.height
.unwrap_or_else(|| {
metrics.os2_typo_ascender.into_inner() - metrics.os2_typo_descender.into_inner()
})
.ot_round()
}
pub fn vertical_origin(&self, metrics: &GlobalMetricsInstance) -> i16 {
self.vertical_origin
.unwrap_or(metrics.os2_typo_ascender.into_inner())
.ot_round()
}
pub fn add_phantom_points(
&self,
metrics: &GlobalMetricsInstance,
build_vertical: bool,
points: &mut Vec<Point>,
) {
let advance_width: u16 = self.width.ot_round();
points.push(Point::new(0.0, 0.0)); points.push(Point::new(advance_width as f64, 0.0));
let (top, bottom) = if build_vertical {
let top = self.vertical_origin(metrics) as f64;
let bottom = top - self.height(metrics) as f64;
(top, bottom)
} else {
Default::default()
};
points.push(Point::new(0.0, top));
points.push(Point::new(0.0, bottom));
}
pub(crate) fn values_for_interpolation(&self) -> Vec<f64> {
fn iter_pathel_points(seg: PathEl) -> impl Iterator<Item = f64> {
match seg {
PathEl::MoveTo(p0) | PathEl::LineTo(p0) => [Some(p0), None, None],
PathEl::QuadTo(p0, p1) => [Some(p0), Some(p1), None],
PathEl::CurveTo(p0, p1, p2) => [Some(p0), Some(p1), Some(p2)],
PathEl::ClosePath => [None, None, None],
}
.into_iter()
.flatten()
.flat_map(|pt| [pt.x, pt.y])
}
self.contours
.iter()
.flat_map(BezPath::iter)
.flat_map(iter_pathel_points)
.chain(
self.components
.iter()
.flat_map(|comp| comp.transform.as_coeffs()),
)
.chain(Some(self.width))
.chain(self.height)
.chain(self.vertical_origin)
.collect()
}
pub(crate) fn new_with_interpolated_values(&self, mut values: &[f64]) -> Self {
let mut contours = Vec::with_capacity(self.contours.len());
for contour in &self.contours {
let (contour, remaining) = copy_points_to_contour(contour, values);
contours.push(contour);
values = remaining;
}
let components = self
.components
.iter()
.zip(values.chunks_exact(6))
.map(|(comp, coeffs)| Component {
base: comp.base.clone(),
transform: Affine::new(coeffs.try_into().unwrap()),
})
.collect();
values = &values[self.components.len() * 6..];
let width = values[0];
values = &values[1..];
let height = self.height.and_then(|_| values.first().copied());
if height.is_some() {
values = &values[1..];
}
let vertical_origin = self.vertical_origin.and_then(|_| values.first().copied());
if vertical_origin.is_some() {
values = &values[1..];
}
assert!(
values.is_empty(),
"this fn can only be passed exactly the number of values required"
);
GlyphInstance {
width,
height,
vertical_origin,
contours,
components,
}
}
}
fn copy_points_to_contour<'a>(contour: &BezPath, mut points: &'a [f64]) -> (BezPath, &'a [f64]) {
let mut new_els = Vec::with_capacity(contour.elements().len());
for el in contour.elements() {
let (new_el, remaining) = copy_ponts_to_el(el, points);
points = remaining;
new_els.push(new_el);
}
(BezPath::from_vec(new_els), points)
}
fn copy_ponts_to_el<'a>(el: &PathEl, vals: &'a [f64]) -> (PathEl, &'a [f64]) {
let (el, n) = match el {
PathEl::MoveTo(_) => (PathEl::MoveTo((vals[0], vals[1]).into()), 2),
PathEl::LineTo(_) => (PathEl::LineTo((vals[0], vals[1]).into()), 2),
PathEl::QuadTo(_, _) => (
PathEl::QuadTo((vals[0], vals[1]).into(), (vals[2], vals[3]).into()),
4,
),
PathEl::CurveTo(_, _, _) => (
PathEl::CurveTo(
(vals[0], vals[1]).into(),
(vals[2], vals[3]).into(),
(vals[4], vals[5]).into(),
),
6,
),
PathEl::ClosePath => (PathEl::ClosePath, 0),
};
(el, &vals[n..])
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Component {
pub base: GlyphName,
pub transform: Affine,
}
impl Component {
pub(crate) fn has_nonidentity_2x2(&self) -> bool {
self.transform.as_coeffs()[..4] != [1.0, 0.0, 0.0, 1.0]
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
pub struct ColorPalettes {
pub palettes: Vec<Vec<Color>>,
}
impl ColorPalettes {
pub fn new(mut palettes: Vec<Vec<Color>>) -> Result<Option<Self>, Error> {
if !palettes.iter().any(|p| !p.is_empty()) {
return Ok(None);
}
if let Some(bad_idx) = palettes.iter().position(|p| p.len() != palettes[0].len()) {
return Err(Error::InconsistentPaletteLength {
size_0: palettes[0].len(),
n: bad_idx,
size_n: palettes[bad_idx].len(),
});
}
if let [one_palette] = palettes.as_mut_slice() {
let mut seen = HashSet::new();
let mut delme = Vec::new();
for (i, c) in one_palette.iter().enumerate() {
if !seen.insert(c) {
delme.push(i);
}
}
for i in delme.into_iter().rev() {
one_palette.remove(i);
}
}
Ok(Some(Self { palettes }))
}
pub fn index_of(&self, color: Color) -> Option<usize> {
if self.palettes.is_empty() {
return None;
}
self.palettes[0].iter().position(|c| *c == color)
}
}
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ColorStop {
pub offset: OrderedFloat<f32>,
pub color: Color,
pub alpha: OrderedFloat<f32>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ColorGlyphs {
pub base_glyphs: IndexMap<GlyphName, Paint>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum Paint {
Glyph(Box<PaintGlyph>),
Solid(Box<PaintSolid>),
LinearGradient(Box<PaintLinearGradient>),
RadialGradient(Box<PaintRadialGradient>),
Layers(Box<Vec<Paint>>),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct PaintGlyph {
pub name: GlyphName,
pub paint: Paint,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct PaintSolid {
pub color: Color,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct PaintLinearGradient {
pub color_line: Vec<ColorStop>,
pub p0: Point,
pub p1: Point,
pub p2: Option<Point>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct PaintRadialGradient {
pub color_line: Vec<ColorStop>,
pub p0: Point,
pub r0: Option<OrderedFloat<f32>>,
pub p1: Point,
pub r1: Option<OrderedFloat<f32>>,
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use fontdrasil::types::Axis;
use write_fonts::{OtRound, types::NameId};
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn anchor_names() {
assert_eq!(AnchorKind::new("top"), Ok(AnchorKind::Base("top".into())));
assert_eq!(AnchorKind::new("top_"), Ok(AnchorKind::Base("top_".into())));
assert_eq!(AnchorKind::new("top1"), Ok(AnchorKind::Base("top1".into())));
assert_eq!(
AnchorKind::new("_bottom"),
Ok(AnchorKind::Mark("bottom".into()))
);
assert_eq!(
AnchorKind::new("bottom_2"),
Ok(AnchorKind::Ligature {
group_name: "bottom".into(),
index: 2
})
);
assert_eq!(
AnchorKind::new("top_right_1"),
Ok(AnchorKind::Ligature {
group_name: "top_right".into(),
index: 1
})
);
}
#[test]
fn caret_anchor_names() {
assert_eq!(AnchorKind::new("caret_1"), Ok(AnchorKind::Caret(1)));
assert_eq!(AnchorKind::new("vcaret_1"), Ok(AnchorKind::VCaret(1)));
assert_eq!(AnchorKind::new("vcaret_0"), Err(BadAnchorReason::ZeroIndex));
assert_eq!(AnchorKind::new("caret_"), Ok(AnchorKind::Caret(1)));
assert_eq!(AnchorKind::new("vcaret_"), Ok(AnchorKind::VCaret(1)));
}
#[test]
fn ligature_empty_component_anchor_name() {
assert_eq!(AnchorKind::new("_3"), Ok(AnchorKind::ComponentMarker(3)));
assert_eq!(AnchorKind::new("_0"), Err(BadAnchorReason::ZeroIndex));
}
#[test]
fn bad_anchor_names() {
assert_eq!(
AnchorKind::new("_top_2"),
Err(BadAnchorReason::NumberedMarkAnchor)
);
assert_eq!(AnchorKind::new("_"), Err(BadAnchorReason::NilMarkGroup));
assert_eq!(AnchorKind::new("top_0"), Err(BadAnchorReason::ZeroIndex));
}
fn assert_names(expected: &[(NameId, &str)], actual: HashMap<NameKey, String>) {
let mut actual: Vec<_> = actual
.iter()
.map(|(k, v)| (k.name_id, v.as_str()))
.collect();
actual.sort_by_key(|(k, _)| *k);
assert_eq!(expected, actual.as_slice());
}
fn assert_name(names: &HashMap<NameKey, String>, expected: &str, name_id: NameId) {
assert_eq!(
Some(expected),
names
.get(&NameKey::new(name_id, expected))
.map(String::as_str)
);
}
#[test]
fn empty_name_builder_default_fallbacks() {
let mut builder = NameBuilder::default();
builder.apply_default_fallbacks(DEFAULT_VENDOR_ID);
let names = builder.into_inner();
assert_names(
&[
(NameId::FAMILY_NAME, "New Font"),
(NameId::SUBFAMILY_NAME, "Regular"),
(NameId::UNIQUE_ID, "0.000;NONE;NewFont-Regular"),
(NameId::FULL_NAME, "New Font Regular"),
(NameId::VERSION_STRING, "Version 0.000"),
(NameId::POSTSCRIPT_NAME, "NewFont-Regular"),
],
names,
)
}
#[test]
fn fallback_version_and_unique_id() {
let mut builder = NameBuilder::default();
builder.set_version(1, 2);
builder.apply_default_fallbacks(DEFAULT_VENDOR_ID);
let names = builder.into_inner();
assert_name(&names, "Version 1.002", NameId::VERSION_STRING);
assert_name(&names, "1.002;NONE;NewFont-Regular", NameId::UNIQUE_ID);
}
#[test]
fn fallbacks_from_ribbi_typographic_names() {
let mut builder = NameBuilder::default();
builder.add(NameId::TYPOGRAPHIC_FAMILY_NAME, "Family".into());
builder.add(NameId::TYPOGRAPHIC_SUBFAMILY_NAME, "Bold Italic".into());
builder.apply_default_fallbacks(DEFAULT_VENDOR_ID);
let names = builder.into_inner();
assert_names(
&[
(NameId::FAMILY_NAME, "Family"),
(NameId::SUBFAMILY_NAME, "Bold Italic"),
(NameId::UNIQUE_ID, "0.000;NONE;Family-BoldItalic"),
(NameId::FULL_NAME, "Family Bold Italic"),
(NameId::VERSION_STRING, "Version 0.000"),
(NameId::POSTSCRIPT_NAME, "Family-BoldItalic"),
],
names,
)
}
#[test]
fn fallbacks_from_non_ribbi_typographic_names() {
let mut builder = NameBuilder::default();
builder.add(NameId::TYPOGRAPHIC_FAMILY_NAME, "Family".into());
builder.add(NameId::TYPOGRAPHIC_SUBFAMILY_NAME, "Subfamily".into());
builder.apply_default_fallbacks(DEFAULT_VENDOR_ID);
let names = builder.into_inner();
assert_names(
&[
(NameId::FAMILY_NAME, "Family Subfamily"),
(NameId::SUBFAMILY_NAME, "Regular"),
(NameId::UNIQUE_ID, "0.000;NONE;Family-Subfamily"),
(NameId::FULL_NAME, "Family Subfamily"),
(NameId::VERSION_STRING, "Version 0.000"),
(NameId::POSTSCRIPT_NAME, "Family-Subfamily"),
(NameId::TYPOGRAPHIC_FAMILY_NAME, "Family"),
(NameId::TYPOGRAPHIC_SUBFAMILY_NAME, "Subfamily"),
],
names,
)
}
#[test]
fn both_legacy_and_typo_names_defined_and_distinct() {
let mut builder = NameBuilder::default();
builder.add(NameId::FAMILY_NAME, "Legacy Family".into());
builder.add(NameId::SUBFAMILY_NAME, "Regular".into());
builder.add(NameId::TYPOGRAPHIC_FAMILY_NAME, "Family".into());
builder.add(NameId::TYPOGRAPHIC_SUBFAMILY_NAME, "Subfamily".into());
builder.apply_default_fallbacks(DEFAULT_VENDOR_ID);
let names = builder.into_inner();
assert_names(
&[
(NameId::FAMILY_NAME, "Legacy Family"),
(NameId::SUBFAMILY_NAME, "Regular"),
(NameId::UNIQUE_ID, "0.000;NONE;Family-Subfamily"),
(NameId::FULL_NAME, "Family Subfamily"),
(NameId::VERSION_STRING, "Version 0.000"),
(NameId::POSTSCRIPT_NAME, "Family-Subfamily"),
(NameId::TYPOGRAPHIC_FAMILY_NAME, "Family"),
(NameId::TYPOGRAPHIC_SUBFAMILY_NAME, "Subfamily"),
],
names,
)
}
#[test]
fn both_legacy_and_typo_names_defined_and_same() {
let mut builder = NameBuilder::default();
builder.add(NameId::FAMILY_NAME, "Family".into());
builder.add(NameId::SUBFAMILY_NAME, "Subfamily".into());
builder.add(NameId::TYPOGRAPHIC_FAMILY_NAME, "Family".into());
builder.add(NameId::TYPOGRAPHIC_SUBFAMILY_NAME, "Subfamily".into());
builder.apply_default_fallbacks(DEFAULT_VENDOR_ID);
let names = builder.into_inner();
assert_names(
&[
(NameId::FAMILY_NAME, "Family"),
(NameId::SUBFAMILY_NAME, "Subfamily"),
(NameId::UNIQUE_ID, "0.000;NONE;Family-Subfamily"),
(NameId::FULL_NAME, "Family Subfamily"),
(NameId::VERSION_STRING, "Version 0.000"),
(NameId::POSTSCRIPT_NAME, "Family-Subfamily"),
],
names,
)
}
#[test]
fn only_legacy_family_and_subfamily_defined() {
let mut builder = NameBuilder::default();
builder.add(NameId::FAMILY_NAME, "Family".into());
builder.add(NameId::SUBFAMILY_NAME, "Italic".into());
builder.apply_default_fallbacks(DEFAULT_VENDOR_ID);
let names = builder.into_inner();
assert_names(
&[
(NameId::FAMILY_NAME, "Family"),
(NameId::SUBFAMILY_NAME, "Italic"),
(NameId::UNIQUE_ID, "0.000;NONE;Family-Italic"),
(NameId::FULL_NAME, "Family Italic"),
(NameId::VERSION_STRING, "Version 0.000"),
(NameId::POSTSCRIPT_NAME, "Family-Italic"),
],
names,
)
}
#[test]
fn round_like_py() {
let pos = NormalizedLocation::for_pos(&[("wght", 0.0)]);
let mut metrics = GlobalMetricsBuilder::new();
metrics.populate_defaults(&pos, 1290, None, None, None, None);
let rounded: i16 = metrics
.build(&Axes::default())
.unwrap()
.get(GlobalMetric::SuperscriptYOffset, &pos)
.0
.ot_round();
assert_eq!(451, rounded);
}
#[test]
fn only_clear_preferred_names_if_both_identical() {
fn make_builder(names: &[(NameId, &str)]) -> NameBuilder {
let mut builder = NameBuilder::default();
for (id, name) in names {
builder.add(*id, name.to_string());
}
builder.apply_default_fallbacks("cmyr");
builder
}
let identical = make_builder(&[
(NameId::FAMILY_NAME, "Derp"),
(NameId::TYPOGRAPHIC_FAMILY_NAME, "Derp"),
(NameId::SUBFAMILY_NAME, "Regular"),
(NameId::TYPOGRAPHIC_SUBFAMILY_NAME, "Regular"),
]);
assert!(identical.get(NameId::TYPOGRAPHIC_FAMILY_NAME).is_none());
assert!(identical.get(NameId::TYPOGRAPHIC_SUBFAMILY_NAME).is_none());
let different_subfamily = make_builder(&[
(NameId::FAMILY_NAME, "Derp"),
(NameId::TYPOGRAPHIC_FAMILY_NAME, "Derp"),
(NameId::SUBFAMILY_NAME, "Regular"),
(NameId::TYPOGRAPHIC_SUBFAMILY_NAME, "Regular Italic"),
]);
assert!(
different_subfamily
.get(NameId::TYPOGRAPHIC_FAMILY_NAME)
.is_some()
);
assert!(
different_subfamily
.get(NameId::TYPOGRAPHIC_SUBFAMILY_NAME)
.is_some()
);
}
#[test]
fn default_strikeout_when_xheight_none() {
let pos = NormalizedLocation::for_pos(&[("wght", 0.0)]);
let mut metrics = GlobalMetricsBuilder::new();
metrics.populate_defaults(&pos, 1000, None, None, None, None);
let default = metrics
.build(&Axes::default())
.unwrap()
.get(GlobalMetric::StrikeoutPosition, &pos);
assert_eq!(default.into_inner(), 300.);
}
#[test]
fn default_strikeout_when_xheight_zero() {
let pos = NormalizedLocation::for_pos(&[("wght", 0.0)]);
let mut metrics = GlobalMetricsBuilder::new();
metrics.populate_defaults(&pos, 1000, Some(0.), None, None, None);
let default = metrics
.build(&Axes::default())
.unwrap()
.get(GlobalMetric::StrikeoutPosition, &pos);
assert_eq!(default.into_inner(), 220.);
}
#[test]
fn interpolated_metrics() {
let regular = NormalizedLocation::for_pos(&[("wght", 0.0)]);
let bold = NormalizedLocation::for_pos(&[("wght", 1.0)]);
let bold_ish = NormalizedLocation::for_pos(&[("wght", 0.5)]);
let mut builder = GlobalMetricsBuilder::new();
builder.populate_defaults(®ular, 1000, None, None, None, None);
builder.set(GlobalMetric::StrikeoutSize, regular, 10.);
builder.set(GlobalMetric::StrikeoutSize, bold, 20.);
let metrics = builder
.build(&Axes::new(vec![Axis::for_test("wght")]))
.unwrap();
assert_eq!(15., *metrics.get(GlobalMetric::StrikeoutSize, &bold_ish));
}
fn make_glyph_order<'a>(raw: impl IntoIterator<Item = &'a str>) -> GlyphOrder {
raw.into_iter().map(GlyphName::from).collect()
}
#[test]
fn glyphorder_equality() {
assert_eq!(make_glyph_order(["a", "b"]), make_glyph_order(["a", "b"]));
}
#[test]
fn glyphorder_non_equality() {
assert_ne!(make_glyph_order(["a", "b"]), make_glyph_order(["b", "a"]));
}
#[test]
fn instance_from_deltas() {
let z = Point::ZERO;
let mut path1 = BezPath::new();
path1.move_to(z);
path1.line_to(z);
path1.quad_to(z, z);
path1.curve_to(z, z, z);
let mut path2 = BezPath::new();
path2.move_to(z);
path2.line_to(z);
path2.close_path();
let contours = vec![path1, path2];
let components = vec![Component {
base: "derp".into(),
transform: Affine::IDENTITY,
}];
let instance = GlyphInstance {
width: 600.,
height: None,
vertical_origin: Some(42.),
contours,
components,
};
let deltas = (0..9)
.flat_map(|i| [i as f64, i as f64])
.chain([-1f64; 6])
.chain([600., 420.]) .collect::<Vec<_>>();
let new_instance = instance.new_with_interpolated_values(&deltas);
assert_eq!(new_instance.contours.len(), 2);
assert_eq!(new_instance.components.len(), 1);
assert_eq!(new_instance.width, 600.);
assert_eq!(new_instance.height, None);
assert_eq!(new_instance.vertical_origin, Some(420.));
for (old, new) in instance.contours.iter().zip(new_instance.contours.iter()) {
assert_eq!(old.elements().len(), new.elements().len());
for (a, b) in old.elements().iter().zip(new.elements()) {
match (a, b) {
(PathEl::MoveTo(_), PathEl::MoveTo(_))
| (PathEl::LineTo(_), PathEl::LineTo(_))
| (PathEl::QuadTo(_, _), PathEl::QuadTo(_, _))
| (PathEl::CurveTo(_, _, _), PathEl::CurveTo(_, _, _))
| (PathEl::ClosePath, PathEl::ClosePath) => (),
(no, good) => panic!("element mismatch: {no:?} != {good:?}"),
}
}
}
assert_eq!(new_instance.components[0].transform.as_coeffs(), [-1.; 6]);
}
}