Skip to content

Commit

Permalink
[write] Add common util for computing searchRange
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
cmyr committed Nov 11, 2024
1 parent 4e55104 commit 80efb48
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 23 deletions.
15 changes: 6 additions & 9 deletions write-fonts/src/font_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,19 +34,14 @@ pub struct BuilderError {
impl TableDirectory {
pub fn from_table_records(table_records: Vec<TableRecord>) -> 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,
)
}
Expand Down
20 changes: 6 additions & 14 deletions write-fonts/src/tables/cmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
56 changes: 56 additions & 0 deletions write-fonts/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
/// <https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-4-segment-mapping-to-delta-values>
#[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)
)
}
}

0 comments on commit 80efb48

Please sign in to comment.