#![deny(unsafe_code)]
#![deny(missing_docs)]
mod cff;
#[cfg(feature = "variable-fonts")]
mod cff2;
mod glyf;
mod head;
mod hmtx;
mod interjector;
mod maxp;
mod name;
mod post;
mod read;
mod remapper;
mod write;
use crate::interjector::Interjector;
use crate::maxp::MaxpData;
use crate::read::{Readable, Reader};
pub use crate::remapper::GlyphRemapper;
use crate::write::{Writeable, Writer};
use crate::Error::{MalformedFont, Unimplemented, UnknownKind};
use std::array::TryFromSliceError;
use std::borrow::Cow;
use std::fmt::{self, Debug, Display, Formatter};
use std::marker::PhantomData;
use std::str::FromStr;
pub fn subset(data: &[u8], index: u32, mapper: &GlyphRemapper) -> Result<Vec<u8>> {
subset_inner(data, index, &[], false, mapper)
}
#[cfg(feature = "variable-fonts")]
pub fn subset_with_variations(
data: &[u8],
index: u32,
variation_coordinates: &[(Tag, f32)],
mapper: &GlyphRemapper,
) -> Result<Vec<u8>> {
subset_inner(data, index, variation_coordinates, true, mapper)
}
fn subset_inner(
data: &[u8],
index: u32,
variation_coordinates: &[(Tag, f32)],
allow_cff2: bool,
mapper: &GlyphRemapper,
) -> Result<Vec<u8>> {
let mapper = mapper.clone();
let context =
prepare_context(data, index, variation_coordinates, allow_cff2, mapper)?;
_subset(context)
}
fn prepare_context<'a>(
data: &'a [u8],
index: u32,
#[cfg_attr(not(feature = "variable-fonts"), allow(unused))]
variation_coordinates: &[(Tag, f32)],
allow_cff2: bool,
mut gid_remapper: GlyphRemapper,
) -> Result<Context<'a>> {
let face = parse(data, index)?;
let flavor = if face.table(Tag::GLYF).is_some() {
FontFlavor::TrueType
} else if face.table(Tag::CFF).is_some() {
FontFlavor::Cff
} else if face.table(Tag::CFF2).is_some() {
if allow_cff2 {
FontFlavor::Cff2
} else {
return Err(Unimplemented);
}
} else {
return Err(UnknownKind);
};
if flavor == FontFlavor::TrueType {
glyf::closure(&face, &mut gid_remapper)?;
}
let _ = variation_coordinates;
#[cfg(not(feature = "variable-fonts"))]
let interjector = Interjector::Dummy(PhantomData);
#[cfg(feature = "variable-fonts")]
let interjector = if (variation_coordinates.is_empty()
&& flavor == FontFlavor::TrueType)
|| flavor == FontFlavor::Cff
{
Interjector::Dummy(PhantomData)
} else {
Interjector::Skrifa(
interjector::skrifa::SkrifaInterjector::new(
data,
index,
variation_coordinates,
)
.ok_or(MalformedFont)?,
)
};
Ok(Context {
face,
mapper: gid_remapper,
interjector,
custom_maxp_data: None,
flavor,
tables: vec![],
long_loca: false,
})
}
fn _subset(mut ctx: Context) -> Result<Vec<u8>> {
if ctx.flavor == FontFlavor::TrueType {
ctx.process(Tag::GLYF)?;
ctx.process(Tag::CVT)?; ctx.process(Tag::FPGM)?; ctx.process(Tag::PREP)?; } else if ctx.flavor == FontFlavor::Cff {
ctx.process(Tag::CFF)?;
} else if ctx.flavor == FontFlavor::Cff2 {
ctx.process(Tag::CFF2)?;
}
ctx.process(Tag::HEAD)?;
ctx.process(Tag::HMTX)?;
ctx.process(Tag::MAXP)?;
ctx.process(Tag::NAME)?;
ctx.process(Tag::POST)?;
Ok(construct(ctx))
}
fn parse(data: &[u8], index: u32) -> Result<Face<'_>> {
let mut r = Reader::new(data);
let mut kind = r.read::<FontKind>().ok_or(UnknownKind)?;
if kind == FontKind::Collection {
r = Reader::new_at(data, 12 + 4 * (index as usize));
let offset = r.read::<u32>().ok_or(MalformedFont)?;
let subdata = data.get(offset as usize..).ok_or(MalformedFont)?;
r = Reader::new(subdata);
kind = r.read::<FontKind>().ok_or(MalformedFont)?;
if kind == FontKind::Collection {
return Err(MalformedFont);
}
}
let count = r.read::<u16>().ok_or(MalformedFont)?;
r.read::<u16>().ok_or(MalformedFont)?;
r.read::<u16>().ok_or(MalformedFont)?;
r.read::<u16>().ok_or(MalformedFont)?;
let mut records = vec![];
for _ in 0..count {
records.push(r.read::<TableRecord>().ok_or(MalformedFont)?);
}
Ok(Face { data, records })
}
fn construct(mut ctx: Context) -> Vec<u8> {
ctx.tables.sort_by_key(|&(tag, _)| tag);
let mut w = Writer::new();
w.write(ctx.flavor);
let count = ctx.tables.len() as u16;
let entry_selector = (count as f32).log2().floor() as u16;
let search_range = 2u16.pow(u32::from(entry_selector)) * 16;
let range_shift = count * 16 - search_range;
w.write(count);
w.write(search_range);
w.write(entry_selector);
w.write(range_shift);
let mut checksum_adjustment_offset = None;
let mut offset = 12 + ctx.tables.len() * 16;
for (tag, data) in &mut ctx.tables {
if *tag == Tag::HEAD {
data.to_mut()[8..12].fill(0);
checksum_adjustment_offset = Some(offset + 8);
}
let len = data.len();
w.write(TableRecord {
tag: *tag,
checksum: checksum(data),
offset: offset as u32,
length: len as u32,
});
offset += len;
while offset % 4 != 0 {
offset += 1;
}
}
for (_, data) in &ctx.tables {
w.extend(data);
w.align(4);
}
let mut data = w.finish();
if let Some(i) = checksum_adjustment_offset {
let sum = checksum(&data);
let val = 0xB1B0AFBA_u32.wrapping_sub(sum);
data[i..i + 4].copy_from_slice(&val.to_be_bytes());
}
data
}
fn checksum(data: &[u8]) -> u32 {
let mut sum = 0u32;
for chunk in data.chunks(4) {
let mut bytes = [0; 4];
bytes[..chunk.len()].copy_from_slice(chunk);
sum = sum.wrapping_add(u32::from_be_bytes(bytes));
}
sum
}
struct Context<'a> {
face: Face<'a>,
mapper: GlyphRemapper,
flavor: FontFlavor,
tables: Vec<(Tag, Cow<'a, [u8]>)>,
interjector: Interjector<'a>,
pub(crate) custom_maxp_data: Option<MaxpData>,
long_loca: bool,
}
impl<'a> Context<'a> {
fn expect_table(&self, tag: Tag) -> Option<&'a [u8]> {
self.face.table(tag)
}
fn process(&mut self, tag: Tag) -> Result<()> {
let data = match self.face.table(tag) {
Some(data) => data,
None => return Ok(()),
};
match tag {
Tag::GLYF => glyf::subset(self)?,
Tag::LOCA => panic!("handled by glyf"),
Tag::CFF => cff::subset(self)?,
#[cfg(feature = "variable-fonts")]
Tag::CFF2 => cff2::subset(self)?,
#[cfg(not(feature = "variable-fonts"))]
Tag::CFF2 => return Err(Unimplemented),
Tag::HEAD => head::subset(self)?,
Tag::HHEA => panic!("handled by hmtx"),
Tag::HMTX => hmtx::subset(self)?,
Tag::POST => post::subset(self)?,
Tag::MAXP => maxp::subset(self)?,
Tag::NAME => name::subset(self)?,
_ => self.push(tag, data),
}
Ok(())
}
fn push(&mut self, tag: Tag, table: impl Into<Cow<'a, [u8]>>) {
debug_assert!(
!self.tables.iter().any(|&(prev, _)| prev == tag),
"duplicate {tag} table"
);
self.tables.push((tag, table.into()));
}
}
struct Face<'a> {
data: &'a [u8],
records: Vec<TableRecord>,
}
impl<'a> Face<'a> {
fn table(&self, tag: Tag) -> Option<&'a [u8]> {
let i = self.records.binary_search_by(|record| record.tag.cmp(&tag)).ok()?;
let record = self.records.get(i)?;
let start = record.offset as usize;
let end = start + (record.length as usize);
self.data.get(start..end)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum FontKind {
Collection,
Single,
}
impl Readable<'_> for FontKind {
const SIZE: usize = u32::SIZE;
fn read(r: &mut Reader) -> Option<Self> {
match r.read::<u32>()? {
0x00010000 | 0x74727565 => Some(FontKind::Single),
0x4F54544F => Some(FontKind::Single),
0x74746366 => Some(FontKind::Collection),
_ => None,
}
}
}
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
enum FontFlavor {
TrueType,
Cff,
Cff2,
}
impl Writeable for FontFlavor {
fn write(&self, w: &mut Writer) {
w.write::<u32>(match self {
FontFlavor::TrueType | FontFlavor::Cff2 => 0x00010000,
FontFlavor::Cff => 0x4F54544F,
})
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Tag([u8; 4]);
impl Tag {
pub fn new(tag: &[u8; 4]) -> Self {
Self(*tag)
}
pub fn get(&self) -> &[u8; 4] {
&self.0
}
}
impl FromStr for Tag {
type Err = TryFromSliceError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
s.as_bytes().try_into().map(Self)
}
}
#[allow(unused)]
impl Tag {
const CMAP: Self = Self(*b"cmap");
const HEAD: Self = Self(*b"head");
const HHEA: Self = Self(*b"hhea");
const HMTX: Self = Self(*b"hmtx");
const MAXP: Self = Self(*b"maxp");
const NAME: Self = Self(*b"name");
const OS2: Self = Self(*b"OS/2");
const POST: Self = Self(*b"post");
const GLYF: Self = Self(*b"glyf");
const LOCA: Self = Self(*b"loca");
const PREP: Self = Self(*b"prep");
const FPGM: Self = Self(*b"fpgm");
const CVT: Self = Self(*b"cvt ");
const GASP: Self = Self(*b"gasp");
const CFF: Self = Self(*b"CFF ");
const CFF2: Self = Self(*b"CFF2");
const VORG: Self = Self(*b"VORG");
const EBDT: Self = Self(*b"EBDT");
const EBLC: Self = Self(*b"EBLC");
const EBSC: Self = Self(*b"EBSC");
const COLR: Self = Self(*b"COLR");
const CPAL: Self = Self(*b"CPAL");
const CBDT: Self = Self(*b"CBDT");
const CBLC: Self = Self(*b"CBLC");
const SBIX: Self = Self(*b"sbix");
const SVG: Self = Self(*b"SVG ");
}
impl Readable<'_> for Tag {
const SIZE: usize = u8::SIZE * 4;
fn read(r: &mut Reader) -> Option<Self> {
r.read::<[u8; 4]>().map(Self)
}
}
impl Writeable for Tag {
fn write(&self, w: &mut Writer) {
w.write::<[u8; 4]>(self.0)
}
}
impl Debug for Tag {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Tag({self})")
}
}
impl Display for Tag {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(std::str::from_utf8(&self.0).unwrap_or("..."))
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
struct TableRecord {
tag: Tag,
checksum: u32,
offset: u32,
length: u32,
}
impl Readable<'_> for TableRecord {
const SIZE: usize = Tag::SIZE + u32::SIZE + u32::SIZE + u32::SIZE;
fn read(r: &mut Reader) -> Option<Self> {
Some(TableRecord {
tag: r.read::<Tag>()?,
checksum: r.read::<u32>()?,
offset: r.read::<u32>()?,
length: r.read::<u32>()?,
})
}
}
impl Writeable for TableRecord {
fn write(&self, w: &mut Writer) {
w.write::<Tag>(self.tag);
w.write::<u32>(self.checksum);
w.write::<u32>(self.offset);
w.write::<u32>(self.length);
}
}
struct F2Dot14(u16);
impl Readable<'_> for F2Dot14 {
const SIZE: usize = u16::SIZE;
fn read(r: &mut Reader) -> Option<Self> {
r.read::<u16>().map(Self)
}
}
impl Writeable for F2Dot14 {
fn write(&self, w: &mut Writer) {
w.write::<u16>(self.0)
}
}
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Error {
UnknownKind,
MalformedFont,
Unimplemented,
SubsetError,
OverflowError,
CFFError,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::UnknownKind => f.write_str("unknown font kind"),
Self::MalformedFont => f.write_str("malformed font"),
Self::Unimplemented => f.write_str("unsupported feature in font"),
Self::SubsetError => f.write_str("subsetting of font failed"),
Self::OverflowError => f.write_str("overflow occurred"),
Self::CFFError => f.write_str("processing CFF table failed"),
}
}
}
impl std::error::Error for Error {}