1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
//! `MVAR` Metrics Variations Table
//!
//! <https://learn.microsoft.com/en-us/typography/opentype/spec/mvar>
use crate::binary::read::{ReadArray, ReadBinary, ReadCtxt, ReadFrom, ReadUnchecked};
use crate::binary::{U16Be, U32Be};
use crate::error::ParseError;
use crate::tables::variable_fonts::{DeltaSetIndexMapEntry, ItemVariationStore, OwnedTuple};
/// `MVAR` Metrics Variations Table
pub struct MvarTable<'a> {
/// Major version number of the metrics variations table.
pub major_version: u16,
/// Minor version number of the metrics variations table.
pub minor_version: u16,
/// The item variation data, `None` if `value_records.len()` is zero.
item_variation_store: Option<ItemVariationStore<'a>>,
/// 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.
value_records: ReadArray<'a, ValueRecord>,
}
/// Identifies target items by tag their associated delta-set index.
#[derive(Copy, Clone)]
pub struct ValueRecord {
/// Four-byte tag identifying a font-wide measure.
pub value_tag: u32,
/// A delta-set outer index.
///
/// Used to select an item variation data sub-table within the item
/// variation store.
delta_set_outer_index: u16,
/// A delta-set inner index.
///
/// Used to select a delta-set row within an item variation data sub-table.
delta_set_inner_index: u16,
}
impl<'a> MvarTable<'a> {
/// Retrieve the delta for the supplied
/// [value tag](https://learn.microsoft.com/en-us/typography/opentype/spec/mvar#value-tags).
pub fn lookup(&self, tag: u32, instance: &OwnedTuple) -> Option<f32> {
let item_variation_store = self.item_variation_store.as_ref()?;
let value_record = self
.value_records
.binary_search_by(|record| record.value_tag.cmp(&tag))
.ok()
.and_then(|index| self.value_records.get_item(index))?;
// To compute the interpolated instance value for a given target item, the
// application first obtains the delta-set index for that item. It uses
// the outer-level index portion to select an item variation data
// sub-table within the item variation store, and the inner-level index
// portion to select a delta-set row within that sub-table.
//
// The delta set contains one delta for each region referenced by the sub-table,
// in order of the region indices given in the regionIndices array. The
// application uses the regionIndices array for that sub-table to
// identify applicable regions and to compute a scalar for each of these
// regions based on the selected instance. Each of the scalars is
// then applied to the corresponding delta within the delta set to derive a
// scaled adjustment. The scaled adjustments for the row are then
// combined to obtain the overall adjustment for the item.
item_variation_store
.adjustment(value_record.into(), instance)
.ok()
}
/// Iterator over the [ValueRecords][ValueRecord] in this `MVAR` table.
pub fn value_records(&self) -> impl Iterator<Item = ValueRecord> + 'a {
self.value_records.iter()
}
/// The number of [ValueRecords][ValueRecord] in this `MVAR` table.
pub fn value_records_len(&self) -> u16 {
// NOTE(cast): Safe as value_records was contructed from u16 value_record_count.
self.value_records.len() as u16
}
}
impl ReadBinary for MvarTable<'_> {
type HostType<'a> = MvarTable<'a>;
fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
let scope = ctxt.scope();
let major_version = ctxt.read_u16be()?;
ctxt.check_version(major_version == 1)?;
let minor_version = ctxt.read_u16be()?;
let _reserved = ctxt.read_u16be()?;
let value_record_size = ctxt.read_u16be()?;
let value_record_count = ctxt.read_u16be()?;
let item_variation_store_offset = ctxt.read_u16be()?;
let value_records = if value_record_count > 0 {
// The spec says that value_record_size must be greater than zero but a font
// was encountered (DecovarAlpha) where it was zero. However the count was also
// zero so we accept this.
ctxt.check(usize::from(value_record_size) >= ValueRecord::SIZE)?;
ctxt.read_array_stride::<ValueRecord>(
usize::from(value_record_count),
usize::from(value_record_size),
)?
} else {
ReadArray::empty()
};
let item_variation_store = (item_variation_store_offset > 0)
.then(|| {
scope
.offset(usize::from(item_variation_store_offset))
.read::<ItemVariationStore<'_>>()
})
.transpose()?;
Ok(MvarTable {
major_version,
minor_version,
item_variation_store,
value_records,
})
}
}
impl ReadFrom for ValueRecord {
type ReadType = (U32Be, U16Be, U16Be);
fn read_from(
(value_tag, delta_set_outer_index, delta_set_inner_index): (u32, u16, u16),
) -> Self {
ValueRecord {
value_tag,
delta_set_outer_index,
delta_set_inner_index,
}
}
}
impl From<ValueRecord> for DeltaSetIndexMapEntry {
fn from(record: ValueRecord) -> DeltaSetIndexMapEntry {
DeltaSetIndexMapEntry {
outer_index: record.delta_set_outer_index,
inner_index: record.delta_set_inner_index,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::binary::read::ReadScope;
use crate::font_data::FontData;
use crate::tables::variable_fonts::fvar::FvarTable;
use crate::tables::{Fixed, FontTableProvider};
use crate::tag;
use crate::tests::{assert_close, read_fixture};
#[test]
fn lookup_value() {
let buffer = read_fixture("tests/fonts/opentype/NotoSans-VF.abc.ttf");
let scope = ReadScope::new(&buffer);
let font_file = scope
.read::<FontData<'_>>()
.expect("unable to parse font file");
let table_provider = font_file
.table_provider(0)
.expect("unable to create font provider");
let fvar_data = table_provider
.read_table_data(tag::FVAR)
.expect("unable to read fvar table data");
let fvar = ReadScope::new(&fvar_data).read::<FvarTable<'_>>().unwrap();
let mvar_data = table_provider
.read_table_data(tag::MVAR)
.expect("unable to read mvar table data");
let mvar = ReadScope::new(&mvar_data).read::<MvarTable<'_>>().unwrap();
// axis="wght" value="900.0", axis="wdth" value="62.5", axis="CTGR"
// value="100.0"
let user_tuple = [Fixed::from(900), Fixed::from(62.5), Fixed::from(100)];
let instance = fvar.normalize(user_tuple.iter().copied(), None).unwrap();
let val = mvar.lookup(tag!(b"xhgt"), &instance).unwrap();
// Value verified by creating a static instance of the font with fonttools,
// dumping it with ttx and then observing the OS/2.sxHeight = 553, which
// is 17 more than the default of 536 in the original. fonttools
// invocation: fonttools varLib.mutator
// src/fonts/allsorts/tests/fonts/opentype/NotoSans-VF.abc.ttf wght=900
// wdth=62.5 CTGR=100
assert_close(val, 17.0);
}
}