Skip to content

Commit

Permalink
Test at multiple locations, from cartesian product of coords in fvar
Browse files Browse the repository at this point in the history
  • Loading branch information
Hoolean authored and RickyDaMa committed Jan 28, 2025
1 parent a8d4389 commit 2bd8ef2
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 37 deletions.
41 changes: 41 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ repository = "https://github.com/googlefonts/fontheight"
[dependencies]
anyhow = "1"
clap = { version = "4.5", features = ["derive"] }
itertools = "0.14.0"
kurbo = "0.11.1"
log = "0.4.25"
ordered-float = "4.6"
rustybuzz = "0.20.1"
skrifa = "0.26.5"

Expand Down
65 changes: 65 additions & 0 deletions src/locations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use std::collections::{BTreeSet, HashMap};

use itertools::Itertools;
use ordered_float::OrderedFloat;
use skrifa::{raw::collections::int_set::Domain, MetadataProvider};

#[derive(Debug)]
pub struct Location {
user_coords: HashMap<skrifa::Tag, f32>,
}

impl Location {
pub fn to_skrifa(
&self,
font: &skrifa::FontRef,
) -> skrifa::instance::Location {
font.axes().location(
self.user_coords.iter().map(|(tag, coord)| (*tag, *coord)),
)
}

pub fn to_rustybuzz(&self) -> Vec<rustybuzz::Variation> {
self.user_coords
.iter()
.map(|(tag, coord)| rustybuzz::Variation {
tag: rustybuzz::ttf_parser::Tag(tag.to_u32()),
value: *coord,
})
.collect()
}
}

/// Gets the cartesian product of axis coordinates seen in named instances, axis
/// extremes, and defaults.
pub(crate) fn interesting_locations(font: &skrifa::FontRef) -> Vec<Location> {
let mut axis_coords =
vec![BTreeSet::<OrderedFloat<f32>>::new(); font.axes().len()];

font.named_instances()
.iter()
.flat_map(|instance| instance.user_coords().enumerate())
.for_each(|(axis, coord)| {
axis_coords[axis].insert(coord.into());
});

font.axes().iter().for_each(|axis| {
axis_coords[axis.index()].extend(&[
axis.default_value().into(),
axis.min_value().into(),
axis.max_value().into(),
]);
});

axis_coords
.iter()
.multi_cartesian_product()
.map(|coords| Location {
user_coords: coords
.into_iter()
.zip(font.axes().iter())
.map(|(coord, axis)| (axis.tag(), From::from(*coord)))
.collect(),
})
.collect()
}
93 changes: 56 additions & 37 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod locations;
mod pens;
mod word_lists;

Expand All @@ -7,7 +8,8 @@ use anyhow::{anyhow, Context};
use clap::Parser;
use env_logger::Env;
use kurbo::Shape;
use log::{error, LevelFilter};
use locations::{interesting_locations, Location};
use log::{error, info, LevelFilter};
use rustybuzz::UnicodeBuffer;
use skrifa::{outline::DrawSettings, prelude::Size, MetadataProvider};

Expand Down Expand Up @@ -44,44 +46,58 @@ fn _main() -> anyhow::Result<()> {
let font_bytes =
fs::read(&args.font_path).context("failed to read font file")?;

let font_face = rustybuzz::Face::from_slice(&font_bytes, 0)
let mut font_face = rustybuzz::Face::from_slice(&font_bytes, 0)
.context("rustybuzz could not parse font")?;

let skrifa_font = skrifa::FontRef::new(&font_bytes)
.context("skrifa could not parse font")?;
let instance_extremes = InstanceExtremes::new(
skrifa_font,
skrifa::instance::LocationRef::default(),
)?;

let test_words = WordList::define("test", ["hello", "apple"]);
test_words.iter().for_each(|word| {
let mut buffer = UnicodeBuffer::new();
buffer.push_str(word);
buffer.guess_segment_properties();
let glyph_buffer = rustybuzz::shape(&font_face, &[], buffer);
// TODO: remove empty glyphs and/or .notdef?
let _word_extremes = glyph_buffer
.glyph_infos()
.iter()
.zip(glyph_buffer.glyph_positions())
.map(|(info, pos)| {
let y_offset = pos.y_offset;
let heights = instance_extremes.get(info.glyph_id).unwrap();

(
y_offset as f64 + heights.lowest,
y_offset as f64 + heights.highest,
)
})
.fold(VerticalExtremes::default(), |extremes, (low, high)| {
let VerticalExtremes { highest, lowest } = extremes;
VerticalExtremes {
highest: highest.max(high),
lowest: lowest.min(low),
}

let locations = interesting_locations(&skrifa_font);

info!("testing font at {} locations", locations.len());
locations
.iter()
.try_for_each(|location| -> anyhow::Result<()> {
font_face.set_variations(&location.to_rustybuzz());

let instance_extremes =
InstanceExtremes::new(&skrifa_font, location)?;

let test_words = WordList::define("test", ["hello", "apple"]);
test_words.iter().for_each(|word| {
let mut buffer = UnicodeBuffer::new();
buffer.push_str(word);
buffer.guess_segment_properties();
let glyph_buffer = rustybuzz::shape(&font_face, &[], buffer);
// TODO: remove empty glyphs and/or .notdef?
let _word_extremes = glyph_buffer
.glyph_infos()
.iter()
.zip(glyph_buffer.glyph_positions())
.map(|(info, pos)| {
let y_offset = pos.y_offset;
let heights =
instance_extremes.get(info.glyph_id).unwrap();

(
y_offset as f64 + heights.lowest,
y_offset as f64 + heights.highest,
)
})
.fold(
VerticalExtremes::default(),
|extremes, (low, high)| {
let VerticalExtremes { highest, lowest } = extremes;
VerticalExtremes {
highest: highest.max(high),
lowest: lowest.min(low),
}
},
);
});
});

Ok(())
})?;

Ok(())
}
Expand All @@ -91,8 +107,8 @@ struct InstanceExtremes(HashMap<u32, VerticalExtremes>);

impl InstanceExtremes {
pub fn new(
font: skrifa::FontRef,
location: skrifa::instance::LocationRef,
font: &skrifa::FontRef,
location: &Location,
) -> anyhow::Result<Self> {
let instance_extremes = font
.outline_glyphs()
Expand All @@ -101,7 +117,10 @@ impl InstanceExtremes {
let mut bez_pen = BezierPen::default();
outline
.draw(
DrawSettings::unhinted(Size::unscaled(), location),
DrawSettings::unhinted(
Size::unscaled(),
&location.to_skrifa(font),
),
&mut bez_pen,
)
.map_err(|err| {
Expand Down

0 comments on commit 2bd8ef2

Please sign in to comment.