mod argstack;
mod charset;
mod charstring;
mod cid_font;
mod dict;
mod index;
mod number;
mod operator;
mod remapper;
mod sid_font;
mod subroutines;
use super::*;
use crate::cff::charset::rewrite_charset;
use crate::cff::charstring::Decompiler;
use crate::cff::cid_font::{rewrite_fd_index, CIDMetadata};
use crate::cff::dict::font_dict::{generate_font_dict_index, rewrite_font_dict_index};
use crate::cff::dict::private_dict::{rewrite_cid_private_dicts, rewrite_private_dict};
use crate::cff::dict::top_dict::{
parse_top_dict_index, rewrite_top_dict_index, TopDictData,
};
use crate::cff::index::{create_index, parse_index, skip_index, Index, OwnedIndex};
use crate::cff::remapper::{FontDictRemapper, SidRemapper};
use crate::cff::sid_font::SIDMetadata;
use crate::cff::subroutines::{SubroutineCollection, SubroutineContainer};
use crate::Error::{OverflowError, SubsetError};
use number::{IntegerNumber, StringId};
use sid_font::generate_fd_index;
use std::cmp::PartialEq;
use std::collections::BTreeSet;
#[derive(Clone, Debug)]
pub(crate) enum FontKind<'a> {
Sid(SIDMetadata<'a>),
Cid(CIDMetadata<'a>),
}
#[derive(Clone)]
pub struct Table<'a> {
names: &'a [u8],
top_dict_data: TopDictData,
strings: Index<'a>,
global_subrs: Index<'a>,
char_strings: Index<'a>,
font_kind: FontKind<'a>,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
struct DeferredOffset {
location: usize,
value: IntegerNumber,
}
const DUMMY_VALUE: IntegerNumber = IntegerNumber(0);
const DUMMY_OFFSET: DeferredOffset = DeferredOffset { location: 0, value: DUMMY_VALUE };
impl DeferredOffset {
fn update_value(&mut self, value: usize) -> Result<()> {
self.value = IntegerNumber(i32::try_from(value).map_err(|_| OverflowError)?);
Ok(())
}
fn adjust_location(&mut self, delta: usize) {
if *self != DUMMY_OFFSET {
self.location += delta;
}
}
fn update_location(&mut self, location: usize) {
self.location = location;
}
fn write_into(&self, buffer: &mut [u8]) -> Result<()> {
let mut w = Writer::new();
self.value.write_as_5_bytes(&mut w);
let encoded = w.finish();
let pos = buffer.get_mut(self.location..self.location + 5).ok_or(SubsetError)?;
pos.copy_from_slice(&encoded);
Ok(())
}
}
#[derive(Debug)]
struct Offsets {
charset_offset: DeferredOffset,
char_strings_offset: DeferredOffset,
private_dicts_lens: Vec<DeferredOffset>,
private_dicts_offsets: Vec<DeferredOffset>,
fd_array_offset: DeferredOffset,
fd_select_offset: DeferredOffset,
}
impl Offsets {
pub fn new_cid(num_font_dicts: u8) -> Self {
Self {
char_strings_offset: DUMMY_OFFSET,
charset_offset: DUMMY_OFFSET,
private_dicts_lens: vec![DUMMY_OFFSET; num_font_dicts as usize],
private_dicts_offsets: vec![DUMMY_OFFSET; num_font_dicts as usize],
fd_select_offset: DUMMY_OFFSET,
fd_array_offset: DUMMY_OFFSET,
}
}
pub fn new_sid() -> Self {
Self::new_cid(1)
}
}
pub fn subset(ctx: &mut Context<'_>) -> Result<()> {
let table = Table::parse(ctx).unwrap();
let (char_strings, fd_remapper) = subset_charstrings(&table, &ctx.mapper)?;
let sid_remapper = get_sid_remapper(&table, &fd_remapper).ok_or(SubsetError)?;
let mut offsets = match &table.font_kind {
FontKind::Sid(_) => Offsets::new_sid(),
FontKind::Cid(_) => Offsets::new_cid(fd_remapper.len()),
};
let mut subsetted_font = {
let mut w = Writer::new();
w.write([1u8, 0, 4, 4]);
w.write(table.names);
rewrite_top_dict_index(
&table.top_dict_data,
&mut offsets,
&sid_remapper,
&mut w,
)?;
let index = create_index(
sid_remapper
.sorted_strings()
.map(|s| Vec::from(s.as_ref()))
.collect::<Vec<_>>(),
)?;
w.extend(&index.data);
w.write(OwnedIndex::default());
offsets.charset_offset.update_value(w.len())?;
rewrite_charset(&ctx.mapper, &mut w)?;
match &table.font_kind {
FontKind::Sid(sid) => {
rewrite_private_dict(&mut offsets, sid.private_dict_data, &mut w, 0)?;
}
FontKind::Cid(cid) => {
rewrite_cid_private_dicts(&fd_remapper, &mut offsets, cid, &mut w)?;
}
}
offsets.fd_select_offset.update_value(w.len())?;
match &table.font_kind {
FontKind::Sid(_) => generate_fd_index(&ctx.mapper, &mut w)?,
FontKind::Cid(cid_metadata) => rewrite_fd_index(
&ctx.mapper,
cid_metadata.fd_select,
&fd_remapper,
&mut w,
)?,
};
offsets.fd_array_offset.update_value(w.len())?;
match &table.font_kind {
FontKind::Sid(_) => generate_font_dict_index(&mut offsets, &mut w)?,
FontKind::Cid(cid_metadata) => rewrite_font_dict_index(
&fd_remapper,
&sid_remapper,
&mut offsets,
cid_metadata,
&mut w,
table.top_dict_data.font_matrix.is_none(),
)?,
}
offsets.char_strings_offset.update_value(w.len())?;
w.extend(&create_index(char_strings)?.data);
w.finish()
};
update_offsets(&offsets, subsetted_font.as_mut_slice())?;
ctx.push(Tag::CFF, subsetted_font);
Ok(())
}
fn update_offsets(offsets: &Offsets, buffer: &mut [u8]) -> Result<()> {
let mut write = |offset: DeferredOffset| {
if offset != DUMMY_OFFSET {
offset.write_into(buffer)?;
}
Ok(())
};
write(offsets.charset_offset)?;
write(offsets.char_strings_offset)?;
write(offsets.fd_select_offset)?;
write(offsets.fd_array_offset)?;
Ok(())
}
fn subset_charstrings(
table: &Table,
remapper: &GlyphRemapper,
) -> Result<(Vec<Vec<u8>>, FontDictRemapper)> {
let gsubrs = {
let subroutines = table.global_subrs.into_iter().collect::<Vec<_>>();
SubroutineContainer::new(subroutines)
};
let lsubrs = {
match &table.font_kind {
FontKind::Cid(cid) => {
let subroutines = cid
.font_dicts
.iter()
.map(|font_dict| {
font_dict.local_subrs.into_iter().collect::<Vec<_>>()
})
.collect::<Vec<_>>();
SubroutineCollection::new(subroutines)
}
FontKind::Sid(sid) => {
let subroutines = sid.local_subrs.into_iter().collect::<Vec<_>>();
SubroutineCollection::new(vec![subroutines])
}
}
};
let mut used_fds = BTreeSet::new();
let mut char_strings = vec![];
for old_gid in remapper.remapped_gids() {
let fd_index = match &table.font_kind {
FontKind::Cid(ref cid) => {
let fd_index =
cid.fd_select.font_dict_index(old_gid).ok_or(MalformedFont)?;
used_fds.insert(fd_index);
fd_index
}
FontKind::Sid(_) => 0,
};
let decompiler = Decompiler::new(
gsubrs.get_handler(),
lsubrs.get_handler(fd_index).ok_or(MalformedFont)?,
);
let charstring = table.char_strings.get(old_gid as u32).ok_or(MalformedFont)?;
char_strings.push(decompiler.decompile(charstring)?);
}
let mut fd_remapper = FontDictRemapper::new();
for fd in used_fds {
fd_remapper.remap(fd);
}
Ok((char_strings.iter().map(|p| p.compile()).collect(), fd_remapper))
}
fn get_sid_remapper<'a>(
table: &Table<'a>,
fd_remapper: &FontDictRemapper,
) -> Option<SidRemapper<'a>> {
let mut sid_remapper = SidRemapper::new();
sid_remapper.remap(&b"Adobe"[..]);
sid_remapper.remap(&b"Identity"[..]);
let mut remap_sid = |sid: StringId| {
if sid.is_standard_string() {
Some(())
} else {
let string =
table.strings.get((sid.0 - StringId::STANDARD_STRING_LEN) as u32)?;
sid_remapper.remap_with_old_sid(sid, Cow::Borrowed(string));
Some(())
}
};
if let Some(copyright) = table.top_dict_data.copyright {
remap_sid(copyright)?;
}
if let Some(font_name) = table.top_dict_data.font_name {
remap_sid(font_name)?;
}
if let Some(notice) = table.top_dict_data.notice {
remap_sid(notice)?;
}
if let FontKind::Cid(ref cid) = table.font_kind {
for font_dict in fd_remapper.sorted_iter() {
let font_dict = cid.font_dicts.get(font_dict as usize)?;
if let Some(font_name) = font_dict.font_name {
remap_sid(font_name)?;
}
}
}
Some(sid_remapper)
}
impl<'a> Table<'a> {
pub fn parse(ctx: &mut Context<'a>) -> Result<Self> {
let cff = ctx.expect_table(Tag::CFF).ok_or(MalformedFont)?;
let mut r = Reader::new(cff);
let major = r.read::<u8>().ok_or(MalformedFont)?;
if major != 1 {
return Err(Error::CFFError);
}
r.skip::<u8>(); let header_size = r.read::<u8>().ok_or(MalformedFont)?;
r.jump(header_size as usize);
let names_start = r.offset();
skip_index::<u16>(&mut r).ok_or(MalformedFont)?;
let names = cff.get(names_start..r.offset()).ok_or(MalformedFont)?;
let top_dict_data = parse_top_dict_index(&mut r).ok_or(MalformedFont)?;
let strings = parse_index::<u16>(&mut r).ok_or(MalformedFont)?;
let global_subrs = parse_index::<u16>(&mut r).ok_or(MalformedFont)?;
let char_strings_offset = top_dict_data.char_strings.ok_or(MalformedFont)?;
let char_strings = {
let mut r = Reader::new_at(cff, char_strings_offset);
parse_index::<u16>(&mut r).ok_or(MalformedFont)?
};
let number_of_glyphs = u16::try_from(char_strings.len())
.ok()
.filter(|n| *n > 0)
.ok_or(MalformedFont)?;
let font_kind = if top_dict_data.has_ros {
FontKind::Cid(
cid_font::parse_cid_metadata(cff, &top_dict_data, number_of_glyphs)
.ok_or(MalformedFont)?,
)
} else {
FontKind::Sid(sid_font::parse_sid_metadata(cff, &top_dict_data))
};
Ok(Self {
names,
top_dict_data,
strings,
global_subrs,
char_strings,
font_kind,
})
}
}