From 80efb48ef709d467cec6fbb0b49a5a573d1dec9d Mon Sep 17 00:00:00 2001 From: Colin Rofls Date: Fri, 18 Oct 2024 19:45:32 -0400 Subject: [PATCH] [write] Add common util for computing searchRange This is shared in a few places, so it makes sense to have a single implementation. (It is used in the kern table, which is coming up) --- write-fonts/src/font_builder.rs | 15 ++++----- write-fonts/src/tables/cmap.rs | 20 ++++-------- write-fonts/src/util.rs | 56 +++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/write-fonts/src/font_builder.rs b/write-fonts/src/font_builder.rs index 0c56c6322..c3459e06c 100644 --- a/write-fonts/src/font_builder.rs +++ b/write-fonts/src/font_builder.rs @@ -6,6 +6,8 @@ use std::{borrow::Cow, fmt::Display}; use read_fonts::{FontRef, TableProvider}; use types::{Tag, TT_SFNT_VERSION}; +use crate::util::SearchRange; + include!("../generated/generated_font.rs"); const TABLE_RECORD_LEN: usize = 16; @@ -32,19 +34,14 @@ pub struct BuilderError { impl TableDirectory { pub fn from_table_records(table_records: Vec) -> TableDirectory { assert!(table_records.len() <= u16::MAX as usize); - // See https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory - // Computation works at the largest allowable num tables so don't stress the as u16's - let entry_selector = (table_records.len() as f64).log2().floor() as u16; - let search_range = (2.0_f64.powi(entry_selector as i32) * 16.0) as u16; - // The result doesn't really make sense with 0 tables but ... let's at least not fail - let range_shift = (table_records.len() * 16).saturating_sub(search_range as usize) as u16; + let computed = SearchRange::compute(table_records.len(), TABLE_RECORD_LEN); TableDirectory::new( TT_SFNT_VERSION, - search_range, - entry_selector, - range_shift, + computed.search_range, + computed.entry_selector, + computed.range_shift, table_records, ) } diff --git a/write-fonts/src/tables/cmap.rs b/write-fonts/src/tables/cmap.rs index b64928700..a993efe52 100644 --- a/write-fonts/src/tables/cmap.rs +++ b/write-fonts/src/tables/cmap.rs @@ -6,6 +6,8 @@ include!("../../generated/generated_cmap.rs"); use std::collections::HashMap; +use crate::util::SearchRange; + // https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#windows-platform-platform-id--3 const WINDOWS_BMP_ENCODING: u16 = 1; const WINDOWS_FULL_REPERTOIRE_ENCODING: u16 = 10; @@ -96,26 +98,16 @@ impl CmapSubtable { ); let seg_count: u16 = start_code.len().try_into().unwrap(); - // Spec: Log2 of the maximum power of 2 less than or equal to segCount (log2(searchRange/2), - // which is equal to floor(log2(segCount))) - let entry_selector = (seg_count as f32).log2().floor(); - - // Spec: Maximum power of 2 less than or equal to segCount, times 2 - // ((2**floor(log2(segCount))) * 2, where “**” is an exponentiation operator) - let search_range = 2u16.pow(entry_selector as u32).checked_mul(2).unwrap(); - - // if 2^entry_selector*2 is a u16 then so is entry_selector - let entry_selector = entry_selector as u16; - let range_shift = seg_count * 2 - search_range; + let computed = SearchRange::compute(seg_count as _, u16::RAW_BYTE_LEN); let id_range_offsets = vec![0; id_deltas.len()]; Some(CmapSubtable::format_4( size_of_cmap4(seg_count, 0), 0, // 'lang' set to zero for all 'cmap' subtables whose platform IDs are other than Macintosh seg_count * 2, - search_range, - entry_selector, - range_shift, + computed.search_range, + computed.entry_selector, + computed.range_shift, end_code, start_code, id_deltas, diff --git a/write-fonts/src/util.rs b/write-fonts/src/util.rs index 8b7ceba18..b31bf48ca 100644 --- a/write-fonts/src/util.rs +++ b/write-fonts/src/util.rs @@ -100,3 +100,59 @@ impl Default for FloatComparator { pub fn isclose(a: f64, b: f64) -> bool { FloatComparator::default().isclose(a, b) } + +/// Search range values used in various tables +#[derive(Clone, Copy, Debug)] +pub struct SearchRange { + pub search_range: u16, + pub entry_selector: u16, + pub range_shift: u16, +} + +impl SearchRange { + //https://github.com/fonttools/fonttools/blob/729b3d2960ef/Lib/fontTools/ttLib/ttFont.py#L1147 + /// calculate searchRange, entrySelector, and rangeShift + /// + /// these values are used in various places, such as [the base table directory] + /// and [cmap format 4]. + /// + /// [the base table directory]: https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory + /// [cmap format 4]: https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-4-segment-mapping-to-delta-values + pub fn compute(n_items: usize, item_size: usize) -> Self { + let entry_selector = (n_items as f64).log2().floor() as usize; + let search_range = (2.0_f64.powi(entry_selector as i32) * item_size as f64) as usize; + // The result doesn't really make sense with 0 tables but ... let's at least not fail + let range_shift = (n_items * item_size).saturating_sub(search_range); + SearchRange { + search_range: search_range.try_into().unwrap(), + entry_selector: entry_selector.try_into().unwrap(), + range_shift: range_shift.try_into().unwrap(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// based on example at + /// + #[test] + fn simple_search_range() { + let SearchRange { + search_range, + entry_selector, + range_shift, + } = SearchRange::compute(39, 2); + assert_eq!((search_range, entry_selector, range_shift), (64, 5, 14)); + } + + #[test] + fn search_range_no_crashy() { + let foo = SearchRange::compute(0, 0); + assert_eq!( + (foo.search_range, foo.entry_selector, foo.range_shift), + (0, 0, 0) + ) + } +}