blob: 999fdf2645a1dd24fa2e282da0c505491c581a70 [file] [edit]
//! impl subset() for glyf and loca
use crate::{
estimate_subset_table_size,
serialize::Serializer,
Plan, Subset,
SubsetError::{self, SubsetTableError},
SubsetFlags,
};
use write_fonts::{
read::{
tables::{
glyf::{
CompositeGlyph, CompositeGlyphFlags, Glyf,
Glyph::{self, Composite, Simple},
SimpleGlyph, SimpleGlyphFlags,
},
head::Head,
loca::Loca,
},
types::GlyphId,
FontRef, TableProvider, TopLevelTable,
},
FontBuilder,
};
// reference: subset() for glyf/loca/head in harfbuzz
// https://github.com/harfbuzz/harfbuzz/blob/a070f9ebbe88dc71b248af9731dd49ec93f4e6e6/src/OT/glyf/glyf.hh#L77
impl Subset for Glyf<'_> {
fn subset(
&self,
plan: &Plan,
font: &FontRef,
s: &mut Serializer,
builder: &mut FontBuilder,
) -> Result<(), SubsetError> {
let loca = font.loca(None).or(Err(SubsetTableError(Loca::TAG)))?;
let head = font.head().or(Err(SubsetTableError(Head::TAG)))?;
let num_output_glyphs = plan.num_output_glyphs;
let mut subset_glyphs = Vec::with_capacity(num_output_glyphs);
let mut max_offset: u32 = 0;
for (new_gid, old_gid) in &plan.new_to_old_gid_list {
match loca.get_glyf(*old_gid, self) {
Ok(g) => {
if *old_gid == GlyphId::NOTDEF
&& *new_gid == GlyphId::NOTDEF
&& !plan
.subset_flags
.contains(SubsetFlags::SUBSET_FLAGS_NOTDEF_OUTLINE)
{
subset_glyphs.push(Vec::new());
continue;
}
let Some(glyph) = g else {
subset_glyphs.push(Vec::new());
continue;
};
let subset_glyph = subset_glyph(&glyph, plan);
let trimmed_len = subset_glyph.len();
max_offset += padded_size(trimmed_len) as u32;
subset_glyphs.push(subset_glyph);
}
_ => {
return Err(SubsetTableError(Glyf::TAG));
}
}
}
//TODO: support force_long_loca in the plan
let loca_format: u8 = if max_offset < 0x1FFFF { 0 } else { 1 };
let loca_out = write_glyf_loca(font, plan, s, loca_format, &subset_glyphs)?;
let head_out = subset_head(&head, loca_format);
builder.add_raw(Loca::TAG, loca_out);
builder.add_raw(Head::TAG, head_out);
Ok(())
}
}
fn padded_size(len: usize) -> usize {
len + len % 2
}
// glyf data is written into the serializer, returning loca data to be added by FontBuilder
fn write_glyf_loca(
font: &FontRef,
plan: &Plan,
s: &mut Serializer,
loca_format: u8,
subset_glyphs: &[Vec<u8>],
) -> Result<Vec<u8>, SubsetError> {
let loca_cap = estimate_subset_table_size(font, Loca::TAG, plan);
let mut loca_out: Vec<u8> = Vec::with_capacity(loca_cap);
if loca_format == 0 {
loca_out.extend_from_slice(&0_u16.to_be_bytes());
} else {
loca_out.extend_from_slice(&0_u32.to_be_bytes());
}
let init_len = s.length();
let mut last: u32 = 0;
if loca_format == 0 {
let mut offset: u16 = 0;
let mut value = 0_u16.to_be_bytes();
for ((new_gid, _), i) in plan.new_to_old_gid_list.iter().zip(0u16..) {
let gid = new_gid.to_u32();
while last < gid {
loca_out.extend_from_slice(&value);
last += 1;
}
let g = &subset_glyphs[i as usize];
let padded_len = padded_size(g.len());
offset += padded_len as u16;
value = (offset >> 1).to_be_bytes();
loca_out.extend_from_slice(&value);
s.embed_bytes(g)
.map_err(|_| SubsetError::SubsetTableError(Glyf::TAG))?;
if padded_len > g.len() {
s.embed_bytes(&[0])
.map_err(|_| SubsetError::SubsetTableError(Glyf::TAG))?;
}
last += 1;
}
while last < plan.num_output_glyphs as u32 {
loca_out.extend_from_slice(&value);
last += 1;
}
} else {
let mut offset: u32 = 0;
let mut value = 0_u32.to_be_bytes();
for ((new_gid, _), i) in plan.new_to_old_gid_list.iter().zip(0u16..) {
let gid = new_gid.to_u32();
while last < gid {
loca_out.extend_from_slice(&value);
last += 1;
}
let g = &subset_glyphs[i as usize];
let padded_len = padded_size(g.len());
offset += padded_len as u32;
value = offset.to_be_bytes();
loca_out.extend_from_slice(&value);
s.embed_bytes(g)
.map_err(|_| SubsetError::SubsetTableError(Glyf::TAG))?;
last += 1;
}
while last < plan.num_output_glyphs as u32 {
loca_out.extend_from_slice(&value);
last += 1;
}
}
// As a special case when all glyph in the font are empty, add a zero byte to the table,
// so that OTS doesn’t reject it, and to make the table work on Windows as well.
// See https://github.com/khaledhosny/ots/issues/52
if init_len == s.length() {
s.embed_bytes(&[0])
.map_err(|_| SubsetError::SubsetTableError(Glyf::TAG))?;
}
Ok(loca_out)
}
fn subset_glyph(glyph: &Glyph, plan: &Plan) -> Vec<u8> {
//TODO: support set_overlaps_flag and drop_hints
match glyph {
Composite(comp_g) => subset_composite_glyph(comp_g, plan),
Simple(simple_g) => subset_simple_glyph(simple_g, plan),
}
}
// TODO: drop_hints and set_overlaps_flag
fn subset_simple_glyph(g: &SimpleGlyph, plan: &Plan) -> Vec<u8> {
let mut out = Vec::with_capacity(g.offset_data().len());
let Some(num_coords) = g.end_pts_of_contours().last() else {
return out;
};
let num_coords = num_coords.get() + 1;
let glyph_data = g.glyph_data();
let i = trim_simple_glyph_padding(glyph_data, num_coords);
if i == 0 {
return out;
}
let glyph_bytes = g.offset_data().as_bytes();
let header_len = 10 + 2 * (g.number_of_contours() as usize) + 2;
let Some(header_slice) = glyph_bytes.get(0..header_len) else {
return out;
};
out.extend_from_slice(header_slice);
if plan
.subset_flags
.contains(SubsetFlags::SUBSET_FLAGS_NO_HINTING)
{
// drop hints: set instructionLength field to 0
out[header_len - 2] = 0;
out[header_len - 1] = 0;
} else {
let instruction_end = header_len + g.instruction_length() as usize;
let Some(instruction_slice) = glyph_bytes.get(header_len..instruction_end) else {
return Vec::new();
};
out.extend_from_slice(instruction_slice);
}
let Some(trimmed_slice) = glyph_data.get(0..i) else {
return Vec::new();
};
let first_flag_index = out.len();
out.extend_from_slice(trimmed_slice);
if plan
.subset_flags
.contains(SubsetFlags::SUBSET_FLAGS_SET_OVERLAPS_FLAG)
{
out[first_flag_index] |= SimpleGlyphFlags::OVERLAP_SIMPLE.bits();
}
out
}
fn subset_composite_glyph(g: &CompositeGlyph, plan: &Plan) -> Vec<u8> {
let mut out = g.offset_data().as_bytes().to_owned();
let mut more = true;
let mut we_have_instructions = false;
let mut i: usize = 10;
let len: usize = out.len();
while more {
if i + 3 >= len {
return Vec::new();
}
let flags = u16::from_be_bytes([out[i], out[i + 1]]);
let mut flags = CompositeGlyphFlags::from_bits_truncate(flags);
if flags.contains(CompositeGlyphFlags::WE_HAVE_INSTRUCTIONS) {
we_have_instructions = true;
if plan
.subset_flags
.contains(SubsetFlags::SUBSET_FLAGS_NO_HINTING)
{
flags.remove(CompositeGlyphFlags::WE_HAVE_INSTRUCTIONS);
out.get_mut(i..i + 2)
.unwrap()
.copy_from_slice(&flags.bits().to_be_bytes());
}
}
// only set overlaps flag on the first component
if plan
.subset_flags
.contains(SubsetFlags::SUBSET_FLAGS_SET_OVERLAPS_FLAG)
&& i == 10
{
flags.insert(CompositeGlyphFlags::OVERLAP_COMPOUND);
out.get_mut(i..i + 2)
.unwrap()
.copy_from_slice(&flags.bits().to_be_bytes());
}
let old_gid = u16::from_be_bytes([out[i + 2], out[i + 3]]);
let Some(new_gid) = plan.glyph_map.get(&GlyphId::from(old_gid)) else {
return Vec::new();
};
let new_gid = new_gid.to_u32() as u16;
out[i + 2] = (new_gid >> 8) as u8;
out[i + 3] = (new_gid & 0xFF) as u8;
i += 4;
if flags.contains(CompositeGlyphFlags::ARG_1_AND_2_ARE_WORDS) {
i += 4;
} else {
i += 2;
}
if flags.contains(CompositeGlyphFlags::WE_HAVE_A_SCALE) {
i += 2;
} else if flags.contains(CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE) {
i += 4;
} else if flags.contains(CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO) {
i += 8;
}
more = flags.contains(CompositeGlyphFlags::MORE_COMPONENTS);
}
if we_have_instructions
&& !plan
.subset_flags
.contains(SubsetFlags::SUBSET_FLAGS_NO_HINTING)
{
if i + 1 >= len {
return Vec::new();
}
let instruction_len = u16::from_be_bytes([out[i], out[i + 1]]);
i += 2 + instruction_len as usize;
}
out.truncate(i);
out
}
// trim padding bytes for simple glyphs, return trimmed length of the raw data for flags & x/y coordinates
fn trim_simple_glyph_padding(glyph_data: &[u8], num_coords: u16) -> usize {
let mut coord_bytes: usize = 0;
let mut coords_with_flags: u16 = 0;
let length = glyph_data.len();
let mut i: usize = 0;
while i < length {
let flag = SimpleGlyphFlags::from_bits_truncate(glyph_data[i]);
i += 1;
let mut repeat: u8 = 1;
if flag.contains(SimpleGlyphFlags::REPEAT_FLAG) {
if i >= length {
return 0;
}
repeat = glyph_data[i] + 1;
i += 1;
}
let mut x_bytes: u8 = 0;
let mut y_bytes: u8 = 0;
if flag.contains(SimpleGlyphFlags::X_SHORT_VECTOR) {
x_bytes = 1;
} else if !flag.contains(SimpleGlyphFlags::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) {
x_bytes = 2;
}
if flag.contains(SimpleGlyphFlags::Y_SHORT_VECTOR) {
y_bytes = 1;
} else if !flag.contains(SimpleGlyphFlags::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) {
y_bytes = 2;
}
coord_bytes += ((x_bytes + y_bytes) * repeat) as usize;
coords_with_flags += repeat as u16;
if coords_with_flags >= num_coords {
break;
}
}
if num_coords != coords_with_flags {
return 0;
}
i += coord_bytes;
i
}
fn subset_head(head: &Head, loca_format: u8) -> Vec<u8> {
let mut out = head.offset_data().as_bytes().to_owned();
out.get_mut(50..52)
.unwrap()
.copy_from_slice(&[0, loca_format]);
out
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_subset_simple_glyph_trim_padding() {
let plan = Plan::default();
let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
let loca = font.loca(None).unwrap();
let glyf = font.glyf().unwrap();
let glyph = loca.get_glyf(GlyphId::from(1_u16), &glyf).unwrap().unwrap();
let subset_output = subset_glyph(&glyph, &plan);
assert_eq!(subset_output.len(), 23);
assert_eq!(
subset_output,
[
0x0, 0x1, 0x0, 0xfa, 0x0, 0x32, 0x1, 0x77, 0x0, 0x64, 0x0, 0x3, 0x0, 0x0, 0x37,
0x33, 0x15, 0x23, 0xfa, 0x7d, 0x7d, 0x64, 0x32
]
);
}
#[test]
fn test_subset_composite_glyph_trim_padding() {
let mut plan = Plan::default();
let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
let loca = font.loca(None).unwrap();
let glyf = font.glyf().unwrap();
let glyph = loca.get_glyf(GlyphId::from(4_u16), &glyf).unwrap().unwrap();
plan.glyph_map
.insert(GlyphId::from(1_u16), GlyphId::from(2_u16));
let subset_glyph = subset_glyph(&glyph, &plan);
assert_eq!(subset_glyph.len(), 20);
assert_eq!(
subset_glyph,
[
0xff, 0xff, 0x2, 0x26, 0x0, 0x7d, 0x3, 0x20, 0x0, 0xc8, 0x0, 0x46, 0x0, 0x2, 0x32,
0x32, 0x7f, 0xff, 0x60, 0x0
]
);
}
}