Skip to content

Commit

Permalink
Merge pull request #720 from googlefonts/write-mvar
Browse files Browse the repository at this point in the history
[write-fonts] use codegen tool to generate MVAR modules
  • Loading branch information
anthrotype authored Nov 27, 2023
2 parents 150c427 + 8c02930 commit d6c04c6
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 0 deletions.
1 change: 1 addition & 0 deletions resources/codegen_inputs/mvar.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![parse_module(read_fonts::tables::mvar)]

/// The [MVAR (Metrics Variations)](https://docs.microsoft.com/en-us/typography/opentype/spec/mvar) table
#[skip_constructor]
#[tag = "MVAR"]
table Mvar {
/// Major version number of the horizontal metrics variations table — set to 1.
Expand Down
5 changes: 5 additions & 0 deletions resources/codegen_plan.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ mode = "parse"
source = "resources/codegen_inputs/mvar.rs"
target = "read-fonts/generated/generated_mvar.rs"

[[generate]]
mode = "compile"
source = "resources/codegen_inputs/mvar.rs"
target = "write-fonts/generated/generated_mvar.rs"

[[generate]]
mode = "parse"
source = "resources/codegen_inputs/layout.rs"
Expand Down
127 changes: 127 additions & 0 deletions write-fonts/generated/generated_mvar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// THIS FILE IS AUTOGENERATED.
// Any changes to this file will be overwritten.
// For more information about how codegen works, see font-codegen/README.md

#[allow(unused_imports)]
use crate::codegen_prelude::*;

/// The [MVAR (Metrics Variations)](https://docs.microsoft.com/en-us/typography/opentype/spec/mvar) table
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Mvar {
/// Major version number of the horizontal metrics variations table — set to 1.
/// Minor version number of the horizontal metrics variations table — set to 0.
pub version: MajorMinor,
/// The size in bytes of each value record — must be greater than zero.
pub value_record_size: u16,
/// The number of value records — may be zero.
pub value_record_count: u16,
/// Offset in bytes from the start of this table to the item variation store table. If valueRecordCount is zero, set to zero; if valueRecordCount is greater than zero, must be greater than zero.
pub item_variation_store: NullableOffsetMarker<ItemVariationStore, WIDTH_32>,
/// Array of value records that identify target items and the associated delta-set index for each. The valueTag records must be in binary order of their valueTag field.
pub value_records: Vec<ValueRecord>,
}

impl FontWrite for Mvar {
#[allow(clippy::unnecessary_cast)]
fn write_into(&self, writer: &mut TableWriter) {
self.version.write_into(writer);
(0 as u16).write_into(writer);
self.value_record_size.write_into(writer);
self.value_record_count.write_into(writer);
self.item_variation_store.write_into(writer);
self.value_records.write_into(writer);
}
fn table_type(&self) -> TableType {
TableType::TopLevel(Mvar::TAG)
}
}

impl Validate for Mvar {
fn validate_impl(&self, ctx: &mut ValidationCtx) {
ctx.in_table("Mvar", |ctx| {
ctx.in_field("item_variation_store", |ctx| {
self.item_variation_store.validate_impl(ctx);
});
ctx.in_field("value_records", |ctx| {
if self.value_records.len() > (u16::MAX as usize) {
ctx.report("array exceeds max length");
}
self.value_records.validate_impl(ctx);
});
})
}
}

impl TopLevelTable for Mvar {
const TAG: Tag = Tag::new(b"MVAR");
}

impl<'a> FromObjRef<read_fonts::tables::mvar::Mvar<'a>> for Mvar {
fn from_obj_ref(obj: &read_fonts::tables::mvar::Mvar<'a>, _: FontData) -> Self {
let offset_data = obj.offset_data();
Mvar {
version: obj.version(),
value_record_size: obj.value_record_size(),
value_record_count: obj.value_record_count(),
item_variation_store: obj.item_variation_store().to_owned_table(),
value_records: obj.value_records().to_owned_obj(offset_data),
}
}
}

impl<'a> FromTableRef<read_fonts::tables::mvar::Mvar<'a>> for Mvar {}

impl<'a> FontRead<'a> for Mvar {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
<read_fonts::tables::mvar::Mvar as FontRead>::read(data).map(|x| x.to_owned_table())
}
}

/// [ValueRecord](https://learn.microsoft.com/en-us/typography/opentype/spec/mvar#table-formats) metrics variation record
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ValueRecord {
/// Four-byte tag identifying a font-wide measure.
pub value_tag: Tag,
/// A delta-set outer index — used to select an item variation data subtable within the item variation store.
pub delta_set_outer_index: u16,
/// A delta-set inner index — used to select a delta-set row within an item variation data subtable.
pub delta_set_inner_index: u16,
}

impl ValueRecord {
/// Construct a new `ValueRecord`
pub fn new(value_tag: Tag, delta_set_outer_index: u16, delta_set_inner_index: u16) -> Self {
Self {
value_tag,
delta_set_outer_index,
delta_set_inner_index,
}
}
}

impl FontWrite for ValueRecord {
fn write_into(&self, writer: &mut TableWriter) {
self.value_tag.write_into(writer);
self.delta_set_outer_index.write_into(writer);
self.delta_set_inner_index.write_into(writer);
}
fn table_type(&self) -> TableType {
TableType::Named("ValueRecord")
}
}

impl Validate for ValueRecord {
fn validate_impl(&self, _ctx: &mut ValidationCtx) {}
}

impl FromObjRef<read_fonts::tables::mvar::ValueRecord> for ValueRecord {
fn from_obj_ref(obj: &read_fonts::tables::mvar::ValueRecord, _: FontData) -> Self {
ValueRecord {
value_tag: obj.value_tag(),
delta_set_outer_index: obj.delta_set_outer_index(),
delta_set_inner_index: obj.delta_set_inner_index(),
}
}
}
1 change: 1 addition & 0 deletions write-fonts/src/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub mod hvar;
pub mod layout;
pub mod loca;
pub mod maxp;
pub mod mvar;
pub mod name;
pub mod os2;
pub mod post;
Expand Down
138 changes: 138 additions & 0 deletions write-fonts/src/tables/mvar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! The [MVAR](https://learn.microsoft.com/en-us/typography/opentype/spec/mvar) table
include!("../../generated/generated_mvar.rs");

use super::variations::ItemVariationStore;
use std::mem::size_of;

impl Mvar {
/// Construct a new `MVAR` table.
pub fn new(
version: MajorMinor,
item_variation_store: Option<ItemVariationStore>,
value_records: Vec<ValueRecord>,
) -> Self {
Self {
version,
value_record_size: size_of::<ValueRecord>() as u16,
value_record_count: value_records.len() as u16,
item_variation_store: item_variation_store.into(),
value_records: value_records.into_iter().map(Into::into).collect(),
}
}
}

#[cfg(test)]
mod tests {
use font_types::{F2Dot14, Tag};
use read_fonts::tables::mvar as read_mvar;

use crate::dump_table;
use crate::tables::variations::{
ivs_builder::VariationStoreBuilder, RegionAxisCoordinates, VariationRegion,
};

use super::*;

#[test]
fn empty_smoke_test() {
let table = Mvar::new(MajorMinor::new(1, 0), None, vec![]);

let bytes = dump_table(&table).unwrap();
let read = read_mvar::Mvar::read(FontData::new(&bytes)).unwrap();

assert_eq!(read.version(), table.version);
assert_eq!(read.value_record_count(), 0);
assert_eq!(read.value_record_size(), 8);
assert!(read.item_variation_store().is_none());
assert_eq!(read.value_records().len(), 0);
}

fn reg_coords(min: f32, default: f32, max: f32) -> RegionAxisCoordinates {
RegionAxisCoordinates {
start_coord: F2Dot14::from_f32(min),
peak_coord: F2Dot14::from_f32(default),
end_coord: F2Dot14::from_f32(max),
}
}

fn test_regions() -> [VariationRegion; 3] {
[
VariationRegion::new(vec![reg_coords(0.0, 1.0, 1.0)]),
VariationRegion::new(vec![reg_coords(0.0, 0.5, 1.0)]),
VariationRegion::new(vec![reg_coords(0.5, 1.0, 1.0)]),
]
}

fn read_metric_delta(mvar: &read_mvar::Mvar, tag: &[u8; 4], coords: &[f32]) -> f64 {
let coords = coords
.iter()
.map(|c| F2Dot14::from_f32(*c))
.collect::<Vec<_>>();
mvar.metric_delta(Tag::new(tag), &coords).unwrap().to_f64()
}

fn assert_value_record(actual: &read_mvar::ValueRecord, expected: ValueRecord) {
assert_eq!(actual.value_tag(), expected.value_tag);
assert_eq!(
actual.delta_set_outer_index(),
expected.delta_set_outer_index
);
assert_eq!(
actual.delta_set_inner_index(),
expected.delta_set_inner_index
);
}

#[test]
fn simple_smoke_test() {
let [r1, r2, r3] = test_regions();
let mut builder = VariationStoreBuilder::new();
let delta_ids = vec![
// deltas for horizontal ascender 'hasc' only defined for 1 region
builder.add_deltas(vec![(r1, 10)]),
// deltas for horizontal descender 'hdsc' defined for 2 regions
builder.add_deltas(vec![(r2, -20), (r3, -30)]),
];
let (varstore, index_map) = builder.build();

let mut value_records = Vec::new();
for (tag, temp_id) in [b"hasc", b"hdsc"].into_iter().zip(delta_ids.into_iter()) {
let varidx = index_map.get(temp_id).unwrap();
let value_record = ValueRecord::new(
Tag::new(tag),
varidx.delta_set_outer_index,
varidx.delta_set_inner_index,
);
value_records.push(value_record);
}

let table = Mvar::new(MajorMinor::new(1, 0), Some(varstore), value_records);

let bytes = dump_table(&table).unwrap();
let read = read_mvar::Mvar::read(FontData::new(&bytes)).unwrap();

assert_eq!(read.version(), table.version);
assert_eq!(read.value_record_count(), 2);
assert_eq!(read.value_record_size(), 8);
assert!(read.item_variation_store().is_some());

assert_value_record(
&read.value_records()[0],
ValueRecord::new(Tag::new(b"hasc"), 0, 1),
);
assert_eq!(read_metric_delta(&read, b"hasc", &[0.0]), 0.0);
// at axis coord 0.5, the interpolated delta will be half of r1's delta
assert_eq!(read_metric_delta(&read, b"hasc", &[0.5]), 5.0);
assert_eq!(read_metric_delta(&read, b"hasc", &[1.0]), 10.0);

assert_value_record(
&read.value_records()[1],
ValueRecord::new(Tag::new(b"hdsc"), 0, 0),
);
assert_eq!(read_metric_delta(&read, b"hdsc", &[0.0]), 0.0);
// this coincides with the peak of intermediate region r2, hence != 30.0/2
assert_eq!(read_metric_delta(&read, b"hdsc", &[0.5]), -20.0);
assert_eq!(read_metric_delta(&read, b"hdsc", &[1.0]), -30.0);
}
}

0 comments on commit d6c04c6

Please sign in to comment.