-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[write-fonts] Use short offsets in gvar where appropriate #639
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -123,8 +123,16 @@ impl Gvar { | |
} | ||
|
||
fn compute_flags(&self) -> GvarFlags { | ||
//TODO: use short offsets sometimes | ||
GvarFlags::LONG_OFFSETS | ||
let max_offset = self | ||
.glyph_variation_data_offsets | ||
.iter() | ||
.fold(0, |acc, val| acc + val.length + val.length % 2); | ||
|
||
if max_offset / 2 <= (u16::MAX as u32) { | ||
GvarFlags::default() | ||
} else { | ||
GvarFlags::LONG_OFFSETS | ||
} | ||
} | ||
|
||
fn compute_glyph_count(&self) -> u16 { | ||
|
@@ -254,11 +262,15 @@ impl GlyphVariations { | |
.map(|tup| tup.build(shared_tuple_map, shared_points.as_ref())) | ||
.unzip(); | ||
|
||
GlyphVariationData { | ||
let mut this = GlyphVariationData { | ||
tuple_variation_headers: tuple_headers, | ||
shared_point_numbers: shared_points, | ||
per_tuple_data: tuple_data, | ||
} | ||
length: 0, | ||
}; | ||
|
||
this.length = this.compute_size(); | ||
this | ||
} | ||
} | ||
|
||
|
@@ -417,6 +429,11 @@ pub struct GlyphVariationData { | |
// optional; present if multiple variations have the same point numbers | ||
shared_point_numbers: Option<PackedPointNumbers>, | ||
per_tuple_data: Vec<GlyphTupleVariationData>, | ||
/// calculated length required to store this data | ||
/// | ||
/// we compute this once up front because we need to know it in a bunch | ||
/// of different places | ||
length: u32, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. usize seems the more natural type, I suppose you eventually want to assign it somewhere u32. Perhaps note why it's u32 here? |
||
} | ||
|
||
/// The serializable representation of a single glyph tuple variation data | ||
|
@@ -457,19 +474,37 @@ struct GlyphDataWriter<'a> { | |
|
||
impl FontWrite for GlyphDataWriter<'_> { | ||
fn write_into(&self, writer: &mut TableWriter) { | ||
assert!(self.long_offsets, "short offset logic not implemented"); | ||
let mut last = 0u32; | ||
last.write_into(writer); | ||
if self.long_offsets { | ||
let mut last = 0u32; | ||
last.write_into(writer); | ||
|
||
// write all the offsets | ||
for glyph in self.data { | ||
last += glyph.compute_size(); | ||
// write all the offsets | ||
for glyph in self.data { | ||
last += glyph.compute_size(); | ||
last.write_into(writer); | ||
} | ||
} else { | ||
// for short offsets we divide the real offset by two; this means | ||
// we will have to add padding if necessary | ||
let mut last = 0u16; | ||
last.write_into(writer); | ||
|
||
// write all the offsets | ||
for glyph in self.data { | ||
let size = glyph.compute_size(); | ||
// ensure we're always rounding up to the next 2 | ||
let short_size = (size / 2) + size % 2; | ||
last += short_size as u16; | ||
last.write_into(writer); | ||
} | ||
} | ||
// then write the actual data | ||
for glyph in self.data { | ||
if !glyph.is_empty() { | ||
glyph.write_into(writer); | ||
if !self.long_offsets && glyph.length % 2 != 0 { | ||
writer.pad_to_2byte_aligned(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You dont' need && glyph.length % 2 here as pad_to_2byte_aligned will happily nop if htat's true? |
||
} | ||
} | ||
} | ||
|
@@ -934,4 +969,120 @@ mod tests { | |
Some(PackedPointNumbers::Some(vec![1, 3])) | ||
); | ||
} | ||
|
||
fn make_39_bytes_of_variation_data() -> Vec<GlyphDeltas> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does it matter that it's 39 bytes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. guess because it's % 2 != 0 so must be 2-byte aligned |
||
vec![ | ||
GlyphDeltas::new( | ||
Tuple::new(vec![F2Dot14::from_f32(-1.0), F2Dot14::from_f32(-1.0)]), | ||
vec![ | ||
GlyphDelta::optional(0, 0), | ||
GlyphDelta::required(35, 0), | ||
GlyphDelta::optional(0, 0), | ||
GlyphDelta::required(-24, 0), | ||
GlyphDelta::optional(0, 0), | ||
GlyphDelta::optional(0, 0), | ||
], | ||
None, | ||
), | ||
GlyphDeltas::new( | ||
Tuple::new(vec![F2Dot14::from_f32(1.0), F2Dot14::from_f32(1.0)]), | ||
vec![ | ||
GlyphDelta::optional(0, 0), | ||
GlyphDelta::required(26, 15), | ||
GlyphDelta::optional(0, 0), | ||
GlyphDelta::required(46, 0), | ||
GlyphDelta::optional(0, 0), | ||
GlyphDelta::required(1, 0), | ||
], | ||
None, | ||
), | ||
] | ||
} | ||
|
||
// sanity checking my input data for two subsequent tests | ||
#[test] | ||
fn who_tests_the_testers() { | ||
let variations = GlyphVariations::new(GlyphId::NOTDEF, make_39_bytes_of_variation_data()); | ||
// to get our real size we need to mock up the shared tuples: | ||
let mut tupl_map = HashMap::new(); | ||
tupl_map.insert(&variations.variations[0].peak_tuple, 1); | ||
tupl_map.insert(&variations.variations[1].peak_tuple, 2); | ||
|
||
let built = variations.clone().build(&tupl_map); | ||
// we need an odd number for this to make sense | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. explain why |
||
assert_eq!(built.length, 31); | ||
} | ||
|
||
#[test] | ||
fn prefer_short_offsets() { | ||
let _ = env_logger::builder().is_test(true).try_init(); | ||
const N_GLYPHS: u16 = 5; | ||
let test_data = make_39_bytes_of_variation_data(); | ||
let a_small_number_of_variations = (0..N_GLYPHS) | ||
.map(|i| GlyphVariations::new(GlyphId::new(i), test_data.clone())) | ||
.collect(); | ||
|
||
let gvar = Gvar::new(a_small_number_of_variations).unwrap(); | ||
assert_eq!(gvar.compute_flags(), GvarFlags::empty()); | ||
|
||
let writer = gvar.compile_variation_data(); | ||
let mut sink = TableWriter::default(); | ||
writer.write_into(&mut sink); | ||
|
||
let bytes = sink.into_data().bytes; | ||
let expected_len = (N_GLYPHS + 1) as usize * u16::RAW_BYTE_LEN // offsets | ||
+ 32 * N_GLYPHS as usize; // rounded size of each glyph, * 5 glyphs | ||
assert_eq!(bytes.len(), expected_len); | ||
|
||
let dumped = crate::dump_table(&gvar).unwrap(); | ||
let loaded = read_fonts::tables::gvar::Gvar::read(FontData::new(&dumped)).unwrap(); | ||
|
||
assert_eq!(loaded.glyph_count(), N_GLYPHS); | ||
assert_eq!(loaded.flags(), GvarFlags::empty()); | ||
assert_eq!( | ||
loaded | ||
.glyph_variation_data_offsets() | ||
.iter() | ||
.map(|off| off.unwrap().get()) | ||
.collect::<Vec<_>>(), | ||
vec![0u32, 32, 64, 96, 128, 160] | ||
); | ||
} | ||
|
||
#[test] | ||
fn use_long_offsets_when_necessary() { | ||
// 2**16 * 2 / (31 + 1 padding) (bytes per tuple) = 4096 so this just overflows | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest you test the most possible short offsets as well, for example loop over N_GLYPHS 4099, 4100 and confirm that their respective flags are SHORT_OFFSETS, LONG_OFFSETS with the expected sizes |
||
const N_GLYPHS: u16 = 4100; | ||
let _ = env_logger::builder().is_test(true).try_init(); | ||
let test_data = make_39_bytes_of_variation_data(); | ||
let a_large_number_of_variations = (0..N_GLYPHS) | ||
.map(|i| GlyphVariations::new(GlyphId::new(i), test_data.clone())) | ||
.collect(); | ||
|
||
let gvar = Gvar::new(a_large_number_of_variations).unwrap(); | ||
assert_eq!(gvar.compute_flags(), GvarFlags::LONG_OFFSETS); | ||
|
||
let writer = gvar.compile_variation_data(); | ||
let mut sink = TableWriter::default(); | ||
writer.write_into(&mut sink); | ||
|
||
let bytes = sink.into_data().bytes; | ||
let expected_len = (N_GLYPHS + 1) as usize * u32::RAW_BYTE_LEN + 31 * N_GLYPHS as usize; | ||
assert_eq!(bytes.len(), expected_len); | ||
|
||
let dumped = crate::dump_table(&gvar).unwrap(); | ||
let loaded = read_fonts::tables::gvar::Gvar::read(FontData::new(&dumped)).unwrap(); | ||
|
||
assert_eq!(loaded.glyph_count(), N_GLYPHS); | ||
assert_eq!(loaded.flags(), GvarFlags::LONG_OFFSETS); | ||
assert_eq!( | ||
loaded | ||
.glyph_variation_data_offsets() | ||
.iter() | ||
.take(6) | ||
.map(|off| off.unwrap().get()) | ||
.collect::<Vec<_>>(), | ||
vec![0u32, 31, 62, 93, 124, 155] | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The majority of prefer_short_offsets and use_long_offsets_when_necessary is duplicative noise; if you diff them they differ only in a few key values. Make a helper, e.g. That will make testing the maximum number of short offsets as it's own test trivial too. |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: the name "this" is perhaps suboptimal to readers with a C++ or Java background.