Skip to content

Commit

Permalink
[write] Handle more cmap fields in codegen
Browse files Browse the repository at this point in the history
There are a bunch of computed fields on some of these subtables, so we
should handle that in one place instead of asking the implementor to
compute them.
  • Loading branch information
cmyr committed Nov 11, 2024
1 parent 9de8e51 commit 2d4c742
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 93 deletions.
7 changes: 7 additions & 0 deletions resources/codegen_inputs/cmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,26 @@ table Cmap4 {
#[format = 4]
format: u16,
/// This is the length in bytes of the subtable.
#[compile(self.compute_length())]
length: u16,
/// For requirements on use of the language field, see “Use of
/// the language field in 'cmap' subtables” in this document.
language: u16,
/// 2 × segCount.
#[compile(2 * array_len($end_code))]
seg_count_x2: u16,
/// Maximum power of 2 less than or equal to segCount, times 2
/// ((2**floor(log2(segCount))) * 2, where “**” is an
/// exponentiation operator)
#[compile(self.compute_search_range())]
search_range: u16,
/// Log2 of the maximum power of 2 less than or equal to numTables
/// (log2(searchRange/2), which is equal to floor(log2(segCount)))
#[compile(self.compute_entry_selector())]
entry_selector: u16,
/// segCount times 2, minus searchRange ((segCount * 2) -
/// searchRange)
#[compile(self.compute_range_shift())]
range_shift: u16,
/// End characterCode for each segment, last=0xFFFF.
#[count(half($seg_count_x2))]
Expand Down Expand Up @@ -236,11 +241,13 @@ table Cmap12 {
#[compile(0)]
reserved: u16,
/// Byte length of this subtable (including the header)
#[compile(self.compute_length())]
length: u32,
/// For requirements on use of the language field, see “Use of
/// the language field in 'cmap' subtables” in this document.
language: u32,
/// Number of groupings which follow
#[compile(array_len($groups))]
num_groups: u32,
/// Array of SequentialMapGroup records.
#[count($num_groups)]
Expand Down
79 changes: 10 additions & 69 deletions write-fonts/generated/generated_cmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,27 +158,16 @@ impl CmapSubtable {
}

/// Construct a new `Cmap4` subtable
#[allow(clippy::too_many_arguments)]
pub fn format_4(
length: u16,
language: u16,
seg_count_x2: u16,
search_range: u16,
entry_selector: u16,
range_shift: u16,
end_code: Vec<u16>,
start_code: Vec<u16>,
id_delta: Vec<i16>,
id_range_offsets: Vec<u16>,
glyph_id_array: Vec<u16>,
) -> Self {
Self::Format4(Cmap4::new(
length,
language,
seg_count_x2,
search_range,
entry_selector,
range_shift,
end_code,
start_code,
id_delta,
Expand Down Expand Up @@ -233,13 +222,8 @@ impl CmapSubtable {
}

/// Construct a new `Cmap12` subtable
pub fn format_12(
length: u32,
language: u32,
num_groups: u32,
groups: Vec<SequentialMapGroup>,
) -> Self {
Self::Format12(Cmap12::new(length, language, num_groups, groups))
pub fn format_12(language: u32, groups: Vec<SequentialMapGroup>) -> Self {
Self::Format12(Cmap12::new(language, groups))
}

/// Construct a new `Cmap13` subtable
Expand Down Expand Up @@ -566,23 +550,9 @@ impl FromObjRef<read_fonts::tables::cmap::SubHeader> for SubHeader {
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Cmap4 {
/// This is the length in bytes of the subtable.
pub length: u16,
/// For requirements on use of the language field, see “Use of
/// the language field in 'cmap' subtables” in this document.
pub language: u16,
/// 2 × segCount.
pub seg_count_x2: u16,
/// Maximum power of 2 less than or equal to segCount, times 2
/// ((2**floor(log2(segCount))) * 2, where “**” is an
/// exponentiation operator)
pub search_range: u16,
/// Log2 of the maximum power of 2 less than or equal to numTables
/// (log2(searchRange/2), which is equal to floor(log2(segCount)))
pub entry_selector: u16,
/// segCount times 2, minus searchRange ((segCount * 2) -
/// searchRange)
pub range_shift: u16,
/// End characterCode for each segment, last=0xFFFF.
pub end_code: Vec<u16>,
/// Start character code for each segment.
Expand All @@ -597,27 +567,16 @@ pub struct Cmap4 {

impl Cmap4 {
/// Construct a new `Cmap4`
#[allow(clippy::too_many_arguments)]
pub fn new(
length: u16,
language: u16,
seg_count_x2: u16,
search_range: u16,
entry_selector: u16,
range_shift: u16,
end_code: Vec<u16>,
start_code: Vec<u16>,
id_delta: Vec<i16>,
id_range_offsets: Vec<u16>,
glyph_id_array: Vec<u16>,
) -> Self {
Self {
length,
language,
seg_count_x2,
search_range,
entry_selector,
range_shift,
end_code: end_code.into_iter().map(Into::into).collect(),
start_code: start_code.into_iter().map(Into::into).collect(),
id_delta: id_delta.into_iter().map(Into::into).collect(),
Expand All @@ -631,12 +590,12 @@ impl FontWrite for Cmap4 {
#[allow(clippy::unnecessary_cast)]
fn write_into(&self, writer: &mut TableWriter) {
(4 as u16).write_into(writer);
self.length.write_into(writer);
(self.compute_length() as u16).write_into(writer);
self.language.write_into(writer);
self.seg_count_x2.write_into(writer);
self.search_range.write_into(writer);
self.entry_selector.write_into(writer);
self.range_shift.write_into(writer);
(2 * array_len(&self.end_code).unwrap() as u16).write_into(writer);
(self.compute_search_range() as u16).write_into(writer);
(self.compute_entry_selector() as u16).write_into(writer);
(self.compute_range_shift() as u16).write_into(writer);
self.end_code.write_into(writer);
(0 as u16).write_into(writer);
self.start_code.write_into(writer);
Expand All @@ -657,12 +616,7 @@ impl<'a> FromObjRef<read_fonts::tables::cmap::Cmap4<'a>> for Cmap4 {
fn from_obj_ref(obj: &read_fonts::tables::cmap::Cmap4<'a>, _: FontData) -> Self {
let offset_data = obj.offset_data();
Cmap4 {
length: obj.length(),
language: obj.language(),
seg_count_x2: obj.seg_count_x2(),
search_range: obj.search_range(),
entry_selector: obj.entry_selector(),
range_shift: obj.range_shift(),
end_code: obj.end_code().to_owned_obj(offset_data),
start_code: obj.start_code().to_owned_obj(offset_data),
id_delta: obj.id_delta().to_owned_obj(offset_data),
Expand Down Expand Up @@ -985,29 +939,18 @@ impl<'a> FontRead<'a> for Cmap10 {
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Cmap12 {
/// Byte length of this subtable (including the header)
pub length: u32,
/// For requirements on use of the language field, see “Use of
/// the language field in 'cmap' subtables” in this document.
pub language: u32,
/// Number of groupings which follow
pub num_groups: u32,
/// Array of SequentialMapGroup records.
pub groups: Vec<SequentialMapGroup>,
}

impl Cmap12 {
/// Construct a new `Cmap12`
pub fn new(
length: u32,
language: u32,
num_groups: u32,
groups: Vec<SequentialMapGroup>,
) -> Self {
pub fn new(language: u32, groups: Vec<SequentialMapGroup>) -> Self {
Self {
length,
language,
num_groups,
groups: groups.into_iter().map(Into::into).collect(),
}
}
Expand All @@ -1018,9 +961,9 @@ impl FontWrite for Cmap12 {
fn write_into(&self, writer: &mut TableWriter) {
(12 as u16).write_into(writer);
(0 as u16).write_into(writer);
self.length.write_into(writer);
(self.compute_length() as u32).write_into(writer);
self.language.write_into(writer);
self.num_groups.write_into(writer);
(array_len(&self.groups).unwrap() as u32).write_into(writer);
self.groups.write_into(writer);
}
fn table_type(&self) -> TableType {
Expand All @@ -1045,9 +988,7 @@ impl<'a> FromObjRef<read_fonts::tables::cmap::Cmap12<'a>> for Cmap12 {
fn from_obj_ref(obj: &read_fonts::tables::cmap::Cmap12<'a>, _: FontData) -> Self {
let offset_data = obj.offset_data();
Cmap12 {
length: obj.length(),
language: obj.language(),
num_groups: obj.num_groups(),
groups: obj.groups().to_owned_obj(offset_data),
}
}
Expand Down
64 changes: 40 additions & 24 deletions write-fonts/src/tables/cmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,6 @@ const WINDOWS_FULL_REPERTOIRE_ENCODING: u16 = 10;
const UNICODE_BMP_ENCODING: u16 = 3;
const UNICODE_FULL_REPERTOIRE_ENCODING: u16 = 4;

fn size_of_cmap4(seg_count: u16, gid_count: u16) -> u16 {
// https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-4-segment-mapping-to-delta-values
8 * 2 // 8 uint16's
+ 2 * seg_count * 4 // 4 parallel arrays of len seg_count, 2 bytes per entry
+ 2 * gid_count // 2 bytes per gid in glyphIdArray
}

fn size_of_cmap12(num_groups: u32) -> u32 {
// https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-12-segmented-coverage
2 * 2 + 3 * 4 // 2 uint16's and 3 uint32's
+ num_groups * 3 * 4 // 3 unit32's per segment map group
}

impl CmapSubtable {
/// Create a new format 4 `CmapSubtable` from a list of `(char, GlyphId)` pairs.
///
Expand Down Expand Up @@ -97,17 +84,9 @@ impl CmapSubtable {
"uneven parallel arrays, very bad. Very very bad."
);

let seg_count: u16 = start_code.len().try_into().unwrap();

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,
computed.search_range,
computed.entry_selector,
computed.range_shift,
end_code,
start_code,
id_deltas,
Expand Down Expand Up @@ -148,15 +127,12 @@ impl CmapSubtable {
}
groups.push((start_char_code, last_char_code, start_glyph_id));

let num_groups: u32 = groups.len().try_into().unwrap();
let seq_map_groups = groups
.into_iter()
.map(|(start_char, end_char, gid)| SequentialMapGroup::new(start_char, end_char, gid))
.collect::<Vec<_>>();
CmapSubtable::format_12(
size_of_cmap12(num_groups),
0, // 'lang' set to zero for all 'cmap' subtables whose platform IDs are other than Macintosh
num_groups,
seq_map_groups,
)
}
Expand Down Expand Up @@ -262,6 +238,46 @@ impl Cmap {
}
}

impl Cmap4 {
fn compute_length(&self) -> u16 {
// https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-4-segment-mapping-to-delta-values
// there are always 8 u16 fields
const FIXED_SIZE: usize = 8 * u16::RAW_BYTE_LEN;
const PER_SEGMENT_LEN: usize = 4 * u16::RAW_BYTE_LEN;

let segment_len = self.end_code.len() * PER_SEGMENT_LEN;
let gid_len = self.glyph_id_array.len() * u16::RAW_BYTE_LEN;

(FIXED_SIZE + segment_len + gid_len)
.try_into()
.expect("cmap4 overflow")
}

fn compute_search_range(&self) -> u16 {
SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).search_range
}

fn compute_entry_selector(&self) -> u16 {
SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).entry_selector
}

fn compute_range_shift(&self) -> u16 {
SearchRange::compute(self.end_code.len(), u16::RAW_BYTE_LEN).range_shift
}
}

impl Cmap12 {
fn compute_length(&self) -> u32 {
// https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-12-segmented-coverage
const FIXED_SIZE: usize = 2 * u16::RAW_BYTE_LEN + 3 * u32::RAW_BYTE_LEN;
const PER_SEGMENT_LEN: usize = 3 * u32::RAW_BYTE_LEN;

(FIXED_SIZE + PER_SEGMENT_LEN * self.groups.len())
.try_into()
.unwrap()
}
}

#[cfg(test)]
mod tests {
use font_types::GlyphId;
Expand Down

0 comments on commit 2d4c742

Please sign in to comment.