mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Refactor subsetter 🧰
This commit is contained in:
parent
7b2a4aa040
commit
030d301f0c
@ -272,8 +272,7 @@ impl PdfFont {
|
|||||||
// Subset the font using the selected characters.
|
// Subset the font using the selected characters.
|
||||||
let subsetted = font.subsetted(
|
let subsetted = font.subsetted(
|
||||||
chars.iter().cloned(),
|
chars.iter().cloned(),
|
||||||
&["head", "hhea", "maxp", "hmtx", "loca", "glyf"][..],
|
&["head", "hhea", "hmtx", "maxp", "cmap", "cvt ", "fpgm", "prep", "loca", "glyf"][..]
|
||||||
&["cvt ", "prep", "fpgm", /* "OS/2", "cmap", "name", "post" */][..],
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Specify flags for the font.
|
// Specify flags for the font.
|
||||||
|
@ -44,6 +44,27 @@ pub struct Font {
|
|||||||
pub metrics: FontMetrics,
|
pub metrics: FontMetrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Font metrics relevant to the typesetting or exporting processes.
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct FontMetrics {
|
||||||
|
/// Whether the font is italic.
|
||||||
|
pub italic: bool,
|
||||||
|
/// Whether font is monospace.
|
||||||
|
pub monospace: bool,
|
||||||
|
/// The angle of text in italics.
|
||||||
|
pub italic_angle: f32,
|
||||||
|
/// The glyph bounding box: [x_min, y_min, x_max, y_max],
|
||||||
|
pub bounding_box: [Size; 4],
|
||||||
|
/// The typographics ascender.
|
||||||
|
pub ascender: Size,
|
||||||
|
/// The typographics descender.
|
||||||
|
pub descender: Size,
|
||||||
|
/// The approximate height of capital letters.
|
||||||
|
pub cap_height: Size,
|
||||||
|
/// The weight class of the font.
|
||||||
|
pub weight_class: u16,
|
||||||
|
}
|
||||||
|
|
||||||
impl Font {
|
impl Font {
|
||||||
/// Create a new font from a raw font program.
|
/// Create a new font from a raw font program.
|
||||||
pub fn new(program: Vec<u8>) -> FontResult<Font> {
|
pub fn new(program: Vec<u8>) -> FontResult<Font> {
|
||||||
@ -118,42 +139,14 @@ impl Font {
|
|||||||
|
|
||||||
/// Generate a subsetted version of this font including only the chars listed in `chars`.
|
/// Generate a subsetted version of this font including only the chars listed in `chars`.
|
||||||
///
|
///
|
||||||
/// All needed tables will be included (returning an error if a table was not present in the
|
/// The filter functions decides which tables to keep and which not based on their tag.
|
||||||
/// source font) and optional tables will be included if they were present in the source font.
|
|
||||||
/// All other tables will be dropped.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn subsetted<C, I, S>(&self, chars: C, needed_tables: I, optional_tables: I)
|
pub fn subsetted<C, I, S>(&self, chars: C, tables: I) -> Result<Font, FontError>
|
||||||
-> Result<Font, FontError>
|
where C: IntoIterator<Item=char>, I: IntoIterator<Item=S>, S: AsRef<str> {
|
||||||
where
|
Subsetter::subset(self, chars, tables)
|
||||||
C: IntoIterator<Item=char>,
|
|
||||||
I: IntoIterator<Item=S>,
|
|
||||||
S: AsRef<str>
|
|
||||||
{
|
|
||||||
Subsetter::subset(self, chars, needed_tables, optional_tables)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Font metrics relevant to the typesetting or exporting processes.
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub struct FontMetrics {
|
|
||||||
/// Whether the font is italic.
|
|
||||||
pub italic: bool,
|
|
||||||
/// Whether font is monospace.
|
|
||||||
pub monospace: bool,
|
|
||||||
/// The angle of text in italics.
|
|
||||||
pub italic_angle: f32,
|
|
||||||
/// The glyph bounding box: [x_min, y_min, x_max, y_max],
|
|
||||||
pub bounding_box: [Size; 4],
|
|
||||||
/// The typographics ascender.
|
|
||||||
pub ascender: Size,
|
|
||||||
/// The typographics descender.
|
|
||||||
pub descender: Size,
|
|
||||||
/// The approximate height of capital letters.
|
|
||||||
pub cap_height: Size,
|
|
||||||
/// The weight class of the font.
|
|
||||||
pub weight_class: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Categorizes a font.
|
/// Categorizes a font.
|
||||||
///
|
///
|
||||||
/// Can be constructed conveniently with the [`font`] macro.
|
/// Can be constructed conveniently with the [`font`] macro.
|
||||||
@ -327,12 +320,14 @@ impl FontProvider for FileSystemFontProvider {
|
|||||||
pub enum FontError {
|
pub enum FontError {
|
||||||
/// The font file is incorrect.
|
/// The font file is incorrect.
|
||||||
InvalidFont(String),
|
InvalidFont(String),
|
||||||
/// A requested table was not present in the source font.
|
/// A character requested for subsetting was not present in the source font.
|
||||||
|
MissingCharacter(char),
|
||||||
|
/// A requested table was not present.
|
||||||
MissingTable(String),
|
MissingTable(String),
|
||||||
/// The table is unknown to the subsetting engine.
|
/// The table is unknown to the subsetting engine.
|
||||||
UnsupportedTable(String),
|
UnsupportedTable(String),
|
||||||
/// A character requested for subsetting was not present in the source font.
|
/// The font is not supported by the subsetting engine.
|
||||||
MissingCharacter(char),
|
UnsupportedFont(String),
|
||||||
/// An I/O Error occured while reading the font program.
|
/// An I/O Error occured while reading the font program.
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
}
|
}
|
||||||
@ -342,9 +337,10 @@ error_type! {
|
|||||||
res: FontResult,
|
res: FontResult,
|
||||||
show: f => match err {
|
show: f => match err {
|
||||||
FontError::InvalidFont(message) => write!(f, "invalid font: {}", message),
|
FontError::InvalidFont(message) => write!(f, "invalid font: {}", message),
|
||||||
FontError::MissingTable(table) => write!(f, "missing table: {}", table),
|
|
||||||
FontError::UnsupportedTable(table) => write!(f, "unsupported table: {}", table),
|
|
||||||
FontError::MissingCharacter(c) => write!(f, "missing character: '{}'", c),
|
FontError::MissingCharacter(c) => write!(f, "missing character: '{}'", c),
|
||||||
|
FontError::MissingTable(table) => write!(f, "missing table: '{}'", table),
|
||||||
|
FontError::UnsupportedTable(table) => write!(f, "unsupported table: {}", table),
|
||||||
|
FontError::UnsupportedFont(message) => write!(f, "unsupported font: {}", message),
|
||||||
FontError::Io(err) => write!(f, "io error: {}", err),
|
FontError::Io(err) => write!(f, "io error: {}", err),
|
||||||
},
|
},
|
||||||
source: match err {
|
source: match err {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
//! Subsetting of opentype fonts.
|
//! Subsetting of opentype fonts.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::{self, Cursor, Seek, SeekFrom};
|
use std::io::{Cursor, Seek, SeekFrom};
|
||||||
|
|
||||||
use byteorder::{BE, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BE, ReadBytesExt, WriteBytesExt};
|
||||||
use opentype::{OpenTypeReader, Outlines, TableRecord, Tag};
|
use opentype::{OpenTypeReader, Outlines, Table, TableRecord, Tag};
|
||||||
use opentype::tables::{Header, CharMap, MaximumProfile, HorizontalMetrics};
|
use opentype::tables::{CharMap, Locations, HorizontalMetrics, Glyphs};
|
||||||
|
|
||||||
|
use crate::size::Size;
|
||||||
use super::{Font, FontError, FontResult};
|
use super::{Font, FontError, FontResult};
|
||||||
|
|
||||||
|
|
||||||
@ -18,9 +19,6 @@ pub struct Subsetter<'a> {
|
|||||||
reader: OpenTypeReader<Cursor<&'a [u8]>>,
|
reader: OpenTypeReader<Cursor<&'a [u8]>>,
|
||||||
outlines: Outlines,
|
outlines: Outlines,
|
||||||
tables: Vec<TableRecord>,
|
tables: Vec<TableRecord>,
|
||||||
cmap: Option<CharMap>,
|
|
||||||
hmtx: Option<HorizontalMetrics>,
|
|
||||||
loca: Option<Vec<u32>>,
|
|
||||||
glyphs: Vec<u16>,
|
glyphs: Vec<u16>,
|
||||||
|
|
||||||
// The subsetted font
|
// The subsetted font
|
||||||
@ -31,159 +29,109 @@ pub struct Subsetter<'a> {
|
|||||||
|
|
||||||
impl<'a> Subsetter<'a> {
|
impl<'a> Subsetter<'a> {
|
||||||
/// Subset a font. See [`Font::subetted`] for more details.
|
/// Subset a font. See [`Font::subetted`] for more details.
|
||||||
pub fn subset<C, I, S>(
|
pub fn subset<C, I, S>(font: &Font, chars: C, tables: I) -> Result<Font, FontError>
|
||||||
font: &Font,
|
where C: IntoIterator<Item=char>, I: IntoIterator<Item=S>, S: AsRef<str> {
|
||||||
chars: C,
|
// Parse some header information.
|
||||||
needed_tables: I,
|
|
||||||
optional_tables: I,
|
|
||||||
) -> Result<Font, FontError>
|
|
||||||
where
|
|
||||||
C: IntoIterator<Item=char>,
|
|
||||||
I: IntoIterator<Item=S>,
|
|
||||||
S: AsRef<str>
|
|
||||||
{
|
|
||||||
// Parse some header information and keep the reading around.
|
|
||||||
let mut reader = OpenTypeReader::from_slice(&font.program);
|
let mut reader = OpenTypeReader::from_slice(&font.program);
|
||||||
let outlines = reader.outlines()?;
|
let outlines = reader.outlines()?;
|
||||||
let tables = reader.tables()?.to_vec();
|
let table_records = reader.tables()?.to_vec();
|
||||||
|
|
||||||
|
// Store all chars we want in a vector.
|
||||||
let chars: Vec<_> = chars.into_iter().collect();
|
let chars: Vec<_> = chars.into_iter().collect();
|
||||||
|
|
||||||
let subsetter = Subsetter {
|
let subsetter = Subsetter {
|
||||||
font,
|
font,
|
||||||
reader,
|
reader,
|
||||||
outlines,
|
outlines,
|
||||||
tables,
|
tables: table_records,
|
||||||
cmap: None,
|
|
||||||
hmtx: None,
|
|
||||||
loca: None,
|
|
||||||
glyphs: Vec::with_capacity(1 + chars.len()),
|
glyphs: Vec::with_capacity(1 + chars.len()),
|
||||||
chars,
|
chars,
|
||||||
records: vec![],
|
records: vec![],
|
||||||
body: vec![],
|
body: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
subsetter.run(needed_tables, optional_tables)
|
subsetter.run(tables)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run<I, S>(mut self, needed_tables: I, optional_tables: I) -> FontResult<Font>
|
/// Do the subsetting.
|
||||||
|
fn run<I, S>(mut self, tables: I) -> FontResult<Font>
|
||||||
where I: IntoIterator<Item=S>, S: AsRef<str> {
|
where I: IntoIterator<Item=S>, S: AsRef<str> {
|
||||||
// Find out which glyphs to include based on which characters we want and which glyphs are
|
// Quit early if we cannot handle the font.
|
||||||
// used by other composite glyphs.
|
if self.outlines != Outlines::TrueType {
|
||||||
self.build_glyphs()?;
|
return Err(FontError::UnsupportedFont("CFF outlines".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate through the needed tables first
|
// Find out which glyphs to include based on which characters we want and
|
||||||
for table in needed_tables.into_iter() {
|
// which glyphs are additionally used by composite glyphs.
|
||||||
let table = table.as_ref();
|
self.find_glyphs()?;
|
||||||
let tag: Tag = table.parse()
|
|
||||||
.map_err(|_| FontError::UnsupportedTable(table.to_string()))?;
|
// Write all the tables the callee wants.
|
||||||
|
for table in tables.into_iter() {
|
||||||
|
let tag = table.as_ref().parse()
|
||||||
|
.map_err(|_| FontError::UnsupportedTable(table.as_ref().to_string()))?;
|
||||||
|
|
||||||
if self.contains_table(tag) {
|
if self.contains_table(tag) {
|
||||||
self.write_table(tag)?;
|
self.subset_table(tag)?;
|
||||||
} else {
|
|
||||||
return Err(FontError::MissingTable(tag.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now iterate through the optional tables
|
|
||||||
for table in optional_tables.into_iter() {
|
|
||||||
let table = table.as_ref();
|
|
||||||
let tag: Tag = table.parse()
|
|
||||||
.map_err(|_| FontError::UnsupportedTable(table.to_string()))?;
|
|
||||||
|
|
||||||
if self.contains_table(tag) {
|
|
||||||
self.write_table(tag)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Preprend the new header to the body. We have to do this last, because
|
||||||
|
// we only have the necessary information now.
|
||||||
self.write_header()?;
|
self.write_header()?;
|
||||||
|
|
||||||
// Build the new widths.
|
|
||||||
let widths = self.glyphs.iter()
|
|
||||||
.map(|&glyph| {
|
|
||||||
self.font.widths.get(glyph as usize).map(|&w| w)
|
|
||||||
.take_invalid("missing glyph metrics")
|
|
||||||
}).collect::<FontResult<Vec<_>>>()?;
|
|
||||||
|
|
||||||
// We add one to the index here because we added the default glyph to the front.
|
|
||||||
let mapping = self.chars.into_iter().enumerate().map(|(i, c)| (c, 1 + i as u16))
|
|
||||||
.collect::<HashMap<char, u16>>();
|
|
||||||
|
|
||||||
Ok(Font {
|
Ok(Font {
|
||||||
name: self.font.name.clone(),
|
name: self.font.name.clone(),
|
||||||
|
mapping: self.compute_mapping(),
|
||||||
|
widths: self.compute_widths()?,
|
||||||
program: self.body,
|
program: self.body,
|
||||||
mapping,
|
|
||||||
widths,
|
|
||||||
default_glyph: self.font.default_glyph,
|
default_glyph: self.font.default_glyph,
|
||||||
metrics: self.font.metrics,
|
metrics: self.font.metrics,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_glyphs(&mut self) -> FontResult<()> {
|
/// Store all glyphs the subset shall contain into `self.glyphs`.
|
||||||
self.read_cmap()?;
|
fn find_glyphs(&mut self) -> FontResult<()> {
|
||||||
let cmap = self.cmap.as_ref().unwrap();
|
if self.outlines == Outlines::TrueType {
|
||||||
|
// Parse the necessary information.
|
||||||
|
let char_map = self.read_table::<CharMap>()?;
|
||||||
|
let glyf = self.read_table::<Glyphs>()?;
|
||||||
|
|
||||||
// The default glyph should be always present, others only if used.
|
// Add the default glyph at index 0 in any case.
|
||||||
self.glyphs.push(self.font.default_glyph);
|
self.glyphs.push(self.font.default_glyph);
|
||||||
for &c in &self.chars {
|
|
||||||
let glyph = cmap.get(c).ok_or_else(|| FontError::MissingCharacter(c))?;
|
|
||||||
self.glyphs.push(glyph);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Composite glyphs may need additional glyphs we do not have in our list yet. So now we
|
// Add all the glyphs for the chars requested.
|
||||||
// have a look at the `glyf` table to check that and add glyphs we need additionally.
|
for &c in &self.chars {
|
||||||
if self.contains_table("glyf".parse().unwrap()) {
|
let glyph = char_map.get(c).ok_or_else(|| FontError::MissingCharacter(c))?;
|
||||||
self.read_loca()?;
|
self.glyphs.push(glyph);
|
||||||
let loca = self.loca.as_ref().unwrap();
|
}
|
||||||
let table = self.get_table_data("glyf".parse().unwrap())?;
|
|
||||||
|
|
||||||
|
// Collect the composite glyphs.
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < self.glyphs.len() {
|
while i < self.glyphs.len() as u16 {
|
||||||
let glyph = self.glyphs[i];
|
let glyph_id = self.glyphs[i as usize];
|
||||||
|
let glyph = glyf.get(glyph_id).take_invalid("missing glyf entry")?;
|
||||||
|
|
||||||
let start = *loca.get(glyph as usize).take_bytes()? as usize;
|
for &composite in &glyph.composites {
|
||||||
let end = *loca.get(glyph as usize + 1).take_bytes()? as usize;
|
if self.glyphs.iter().rev().all(|&x| x != composite) {
|
||||||
|
self.glyphs.push(composite);
|
||||||
let glyph = table.get(start..end).take_bytes()?;
|
|
||||||
|
|
||||||
if end > start {
|
|
||||||
let mut cursor = Cursor::new(&glyph);
|
|
||||||
let num_contours = cursor.read_i16::<BE>()?;
|
|
||||||
|
|
||||||
// This is a composite glyph
|
|
||||||
if num_contours < 0 {
|
|
||||||
cursor.seek(SeekFrom::Current(8))?;
|
|
||||||
loop {
|
|
||||||
let flags = cursor.read_u16::<BE>()?;
|
|
||||||
let glyph_index = cursor.read_u16::<BE>()?;
|
|
||||||
|
|
||||||
if self.glyphs.iter().rev().find(|&&x| x == glyph_index).is_none() {
|
|
||||||
self.glyphs.push(glyph_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This was the last component
|
|
||||||
if flags & 0x0020 == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let args_len = if flags & 0x0001 == 1 { 4 } else { 2 };
|
|
||||||
cursor.seek(SeekFrom::Current(args_len))?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepend the new header to the constructed body.
|
||||||
fn write_header(&mut self) -> FontResult<()> {
|
fn write_header(&mut self) -> FontResult<()> {
|
||||||
// Create an output buffer
|
// Create an output buffer
|
||||||
let header_len = 12 + self.records.len() * 16;
|
let header_len = 12 + self.records.len() * 16;
|
||||||
let mut header = Vec::with_capacity(header_len);
|
let mut header = Vec::with_capacity(header_len);
|
||||||
|
|
||||||
|
// Compute the first four header entries.
|
||||||
let num_tables = self.records.len() as u16;
|
let num_tables = self.records.len() as u16;
|
||||||
|
|
||||||
// The highester power lower than the table count.
|
// The highester power lower than the table count.
|
||||||
@ -215,173 +163,232 @@ impl<'a> Subsetter<'a> {
|
|||||||
header.write_u32::<BE>(record.length)?;
|
header.write_u32::<BE>(record.length)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepend the fresh header to the body.
|
||||||
header.append(&mut self.body);
|
header.append(&mut self.body);
|
||||||
self.body = header;
|
self.body = header;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_table(&mut self, tag: Tag) -> FontResult<()> {
|
/// Compute the new widths.
|
||||||
|
fn compute_widths(&self) -> FontResult<Vec<Size>> {
|
||||||
|
let mut widths = Vec::with_capacity(self.glyphs.len());
|
||||||
|
for &glyph in &self.glyphs {
|
||||||
|
let &width = self.font.widths.get(glyph as usize)
|
||||||
|
.take_invalid("missing glyph width")?;
|
||||||
|
widths.push(width);
|
||||||
|
}
|
||||||
|
Ok(widths)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the new mapping.
|
||||||
|
fn compute_mapping(&self) -> HashMap<char, u16> {
|
||||||
|
// The mapping is basically just the index in the char vector, but we add one
|
||||||
|
// to each index here because we added the default glyph to the front.
|
||||||
|
self.chars.iter().enumerate().map(|(i, &c)| (c, 1 + i as u16))
|
||||||
|
.collect::<HashMap<char, u16>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subset and write the table with the given tag to the output.
|
||||||
|
fn subset_table(&mut self, tag: Tag) -> FontResult<()> {
|
||||||
match tag.value() {
|
match tag.value() {
|
||||||
b"head" | b"cvt " | b"prep" | b"fpgm" | b"name" | b"post" | b"OS/2" => {
|
// These tables can just be copied.
|
||||||
self.copy_table(tag)
|
b"head" | b"name" | b"OS/2" | b"post" |
|
||||||
},
|
b"cvt " | b"fpgm" | b"prep" | b"gasp" => self.copy_table(tag),
|
||||||
b"hhea" => {
|
|
||||||
let table = self.get_table_data(tag)?;
|
|
||||||
let glyph_count = self.glyphs.len() as u16;
|
|
||||||
self.write_table_body(tag, |this| {
|
|
||||||
this.body.extend(&table[..table.len() - 2]);
|
|
||||||
Ok(this.body.write_u16::<BE>(glyph_count)?)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
b"maxp" => {
|
|
||||||
let table = self.get_table_data(tag)?;
|
|
||||||
let glyph_count = self.glyphs.len() as u16;
|
|
||||||
self.write_table_body(tag, |this| {
|
|
||||||
this.body.extend(&table[..4]);
|
|
||||||
this.body.write_u16::<BE>(glyph_count)?;
|
|
||||||
Ok(this.body.extend(&table[6..]))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
b"hmtx" => {
|
|
||||||
self.write_table_body(tag, |this| {
|
|
||||||
this.read_hmtx()?;
|
|
||||||
let metrics = this.hmtx.as_ref().unwrap();
|
|
||||||
|
|
||||||
for &glyph in &this.glyphs {
|
// These tables have more complex subsetting routines.
|
||||||
let metrics = metrics.get(glyph).take_invalid("missing glyph metrics")?;
|
b"hhea" => self.subset_hhea(),
|
||||||
|
b"hmtx" => self.subset_hmtx(),
|
||||||
|
b"maxp" => self.subset_maxp(),
|
||||||
|
b"cmap" => self.subset_cmap(),
|
||||||
|
b"glyf" => self.subset_glyf(),
|
||||||
|
b"loca" => self.subset_loca(),
|
||||||
|
|
||||||
this.body.write_i16::<BE>(metrics.advance_width)?;
|
_ => Err(FontError::UnsupportedTable(tag.to_string()))
|
||||||
this.body.write_i16::<BE>(metrics.left_side_bearing)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
},
|
|
||||||
b"loca" => {
|
|
||||||
self.write_table_body(tag, |this| {
|
|
||||||
this.read_loca()?;
|
|
||||||
let loca = this.loca.as_ref().unwrap();
|
|
||||||
|
|
||||||
let mut offset = 0;
|
|
||||||
for &glyph in &this.glyphs {
|
|
||||||
this.body.write_u32::<BE>(offset)?;
|
|
||||||
let len = loca.get(glyph as usize + 1).take_bytes()?
|
|
||||||
- loca.get(glyph as usize).take_bytes()?;
|
|
||||||
offset += len;
|
|
||||||
}
|
|
||||||
this.body.write_u32::<BE>(offset)?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
b"glyf" => {
|
|
||||||
self.write_table_body(tag, |this| {
|
|
||||||
this.read_loca()?;
|
|
||||||
let loca = this.loca.as_ref().unwrap();
|
|
||||||
let table = this.get_table_data(tag)?;
|
|
||||||
|
|
||||||
for &glyph in &this.glyphs {
|
|
||||||
let start = *loca.get(glyph as usize).take_bytes()? as usize;
|
|
||||||
let end = *loca.get(glyph as usize + 1).take_bytes()? as usize;
|
|
||||||
|
|
||||||
let mut data = table.get(start..end).take_bytes()?.to_vec();
|
|
||||||
|
|
||||||
if end > start {
|
|
||||||
let mut cursor = Cursor::new(&mut data);
|
|
||||||
let num_contours = cursor.read_i16::<BE>()?;
|
|
||||||
|
|
||||||
// This is a composite glyph
|
|
||||||
if num_contours < 0 {
|
|
||||||
cursor.seek(SeekFrom::Current(8))?;
|
|
||||||
loop {
|
|
||||||
let flags = cursor.read_u16::<BE>()?;
|
|
||||||
|
|
||||||
let glyph_index = cursor.read_u16::<BE>()?;
|
|
||||||
let new_glyph_index = this.glyphs.iter()
|
|
||||||
.position(|&g| g == glyph_index)
|
|
||||||
.take_invalid("referenced non-existent glyph")? as u16;
|
|
||||||
|
|
||||||
cursor.seek(SeekFrom::Current(-2))?;
|
|
||||||
cursor.write_u16::<BE>(new_glyph_index)?;
|
|
||||||
|
|
||||||
// This was the last component
|
|
||||||
if flags & 0x0020 == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let args_len = if flags & 0x0001 == 1 { 4 } else { 2 };
|
|
||||||
cursor.seek(SeekFrom::Current(args_len))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.body.extend(data);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
b"cmap" => {
|
|
||||||
// Always uses format 12 for simplicity
|
|
||||||
self.write_table_body(tag, |this| {
|
|
||||||
// Find out which chars are in consecutive groups
|
|
||||||
let mut groups = Vec::new();
|
|
||||||
let len = this.chars.len();
|
|
||||||
let mut i = 0;
|
|
||||||
while i < len {
|
|
||||||
let start = i;
|
|
||||||
while i + 1 < len && this.chars[i+1] as u32 == this.chars[i] as u32 + 1 {
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add one to the start because we inserted the default glyph in front.
|
|
||||||
let glyph = 1 + start;
|
|
||||||
groups.push((this.chars[start], this.chars[i], glyph));
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table header
|
|
||||||
this.body.write_u16::<BE>(0)?;
|
|
||||||
this.body.write_u16::<BE>(1)?;
|
|
||||||
this.body.write_u16::<BE>(3)?;
|
|
||||||
this.body.write_u16::<BE>(1)?;
|
|
||||||
this.body.write_u32::<BE>(12)?;
|
|
||||||
|
|
||||||
// Subtable header
|
|
||||||
this.body.write_u16::<BE>(12)?;
|
|
||||||
this.body.write_u16::<BE>(0)?;
|
|
||||||
this.body.write_u32::<BE>((16 + 12 * groups.len()) as u32)?;
|
|
||||||
this.body.write_u32::<BE>(0)?;
|
|
||||||
this.body.write_u32::<BE>(groups.len() as u32)?;
|
|
||||||
|
|
||||||
// Subtable body
|
|
||||||
for group in &groups {
|
|
||||||
this.body.write_u32::<BE>(group.0 as u32)?;
|
|
||||||
this.body.write_u32::<BE>(group.1 as u32)?;
|
|
||||||
this.body.write_u32::<BE>(group.2 as u32)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => Err(FontError::UnsupportedTable(tag.to_string())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Copy the table body without modification.
|
||||||
fn copy_table(&mut self, tag: Tag) -> FontResult<()> {
|
fn copy_table(&mut self, tag: Tag) -> FontResult<()> {
|
||||||
self.write_table_body(tag, |this| {
|
self.write_table_body(tag, |this| {
|
||||||
let table = this.get_table_data(tag)?;
|
let table = this.read_table_data(tag)?;
|
||||||
Ok(this.body.extend(table))
|
Ok(this.body.extend(table))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Subset the `hhea` table by changing the glyph count in it.
|
||||||
|
fn subset_hhea(&mut self) -> FontResult<()> {
|
||||||
|
let tag = "hhea".parse().unwrap();
|
||||||
|
let hhea = self.read_table_data(tag)?;
|
||||||
|
let glyph_count = self.glyphs.len() as u16;
|
||||||
|
self.write_table_body(tag, |this| {
|
||||||
|
this.body.extend(&hhea[..hhea.len() - 2]);
|
||||||
|
this.body.write_u16::<BE>(glyph_count)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subset the `hmtx` table by changing the included metrics.
|
||||||
|
fn subset_hmtx(&mut self) -> FontResult<()> {
|
||||||
|
let tag = "hmtx".parse().unwrap();
|
||||||
|
let hmtx = self.read_table::<HorizontalMetrics>()?;
|
||||||
|
self.write_table_body(tag, |this| {
|
||||||
|
for &glyph in &this.glyphs {
|
||||||
|
let metrics = hmtx.get(glyph).take_invalid("missing glyph metrics")?;
|
||||||
|
this.body.write_i16::<BE>(metrics.advance_width)?;
|
||||||
|
this.body.write_i16::<BE>(metrics.left_side_bearing)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subset the `maxp` table by changing the glyph count in it.
|
||||||
|
fn subset_maxp(&mut self) -> FontResult<()> {
|
||||||
|
let tag = "maxp".parse().unwrap();
|
||||||
|
let maxp = self.read_table_data(tag)?;
|
||||||
|
let glyph_count = self.glyphs.len() as u16;
|
||||||
|
self.write_table_body(tag, |this| {
|
||||||
|
this.body.extend(&maxp[..4]);
|
||||||
|
this.body.write_u16::<BE>(glyph_count)?;
|
||||||
|
Ok(this.body.extend(&maxp[6..]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subset the `cmap` table by
|
||||||
|
fn subset_cmap(&mut self) -> FontResult<()> {
|
||||||
|
let tag = "cmap".parse().unwrap();
|
||||||
|
|
||||||
|
// Always uses format 12 for simplicity.
|
||||||
|
self.write_table_body(tag, |this| {
|
||||||
|
let mut groups = Vec::new();
|
||||||
|
|
||||||
|
// Find out which chars are in consecutive groups.
|
||||||
|
let mut end = 0;
|
||||||
|
let len = this.chars.len();
|
||||||
|
while end < len {
|
||||||
|
// Compute the end of the consecutive group.
|
||||||
|
let start = end;
|
||||||
|
while end + 1 < len && this.chars[end+1] as u32 == this.chars[end] as u32 + 1 {
|
||||||
|
end += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add one to the start because we inserted the default glyph in front.
|
||||||
|
let glyph_id = 1 + start;
|
||||||
|
groups.push((this.chars[start], this.chars[end], glyph_id));
|
||||||
|
end += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the table header.
|
||||||
|
this.body.write_u16::<BE>(0)?;
|
||||||
|
this.body.write_u16::<BE>(1)?;
|
||||||
|
this.body.write_u16::<BE>(3)?;
|
||||||
|
this.body.write_u16::<BE>(1)?;
|
||||||
|
this.body.write_u32::<BE>(12)?;
|
||||||
|
|
||||||
|
// Write the subtable header.
|
||||||
|
this.body.write_u16::<BE>(12)?;
|
||||||
|
this.body.write_u16::<BE>(0)?;
|
||||||
|
this.body.write_u32::<BE>((16 + 12 * groups.len()) as u32)?;
|
||||||
|
this.body.write_u32::<BE>(0)?;
|
||||||
|
this.body.write_u32::<BE>(groups.len() as u32)?;
|
||||||
|
|
||||||
|
// Write the subtable body.
|
||||||
|
for group in &groups {
|
||||||
|
this.body.write_u32::<BE>(group.0 as u32)?;
|
||||||
|
this.body.write_u32::<BE>(group.1 as u32)?;
|
||||||
|
this.body.write_u32::<BE>(group.2 as u32)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subset the `glyf` table by changing the indices of composite glyphs.
|
||||||
|
fn subset_glyf(&mut self) -> FontResult<()> {
|
||||||
|
let tag = "glyf".parse().unwrap();
|
||||||
|
let loca = self.read_table::<Locations>()?;
|
||||||
|
let glyf = self.read_table_data(tag)?;
|
||||||
|
|
||||||
|
self.write_table_body(tag, |this| {
|
||||||
|
for &glyph in &this.glyphs {
|
||||||
|
// Find out the location of the glyph in the glyf table.
|
||||||
|
let start = loca.offset(glyph).take_invalid("missing loca entry")?;
|
||||||
|
let end = loca.offset(glyph + 1).take_invalid("missing loca entry")?;
|
||||||
|
|
||||||
|
// If this glyph has no contours, skip it.
|
||||||
|
if end == start {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the glyph data.
|
||||||
|
let mut glyph_data = glyf.get(start as usize .. end as usize)
|
||||||
|
.take_invalid("missing glyph data")?.to_vec();
|
||||||
|
|
||||||
|
// Construct a cursor to operate on the data.
|
||||||
|
let mut cursor = Cursor::new(&mut glyph_data);
|
||||||
|
let num_contours = cursor.read_i16::<BE>()?;
|
||||||
|
|
||||||
|
// This is a composite glyph
|
||||||
|
if num_contours < 0 {
|
||||||
|
cursor.seek(SeekFrom::Current(8))?;
|
||||||
|
loop {
|
||||||
|
let flags = cursor.read_u16::<BE>()?;
|
||||||
|
|
||||||
|
// Read the old glyph index.
|
||||||
|
let glyph_index = cursor.read_u16::<BE>()?;
|
||||||
|
|
||||||
|
// Compute the new glyph index by searching for it's index
|
||||||
|
// in the glyph vector.
|
||||||
|
let new_glyph_index = this.glyphs.iter()
|
||||||
|
.position(|&g| g == glyph_index)
|
||||||
|
.take_invalid("invalid composite glyph")? as u16;
|
||||||
|
|
||||||
|
// Overwrite the old index with the new one.
|
||||||
|
cursor.seek(SeekFrom::Current(-2))?;
|
||||||
|
cursor.write_u16::<BE>(new_glyph_index)?;
|
||||||
|
|
||||||
|
// This was the last component
|
||||||
|
if flags & 0x0020 == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let args_len = if flags & 0x0001 == 1 { 4 } else { 2 };
|
||||||
|
cursor.seek(SeekFrom::Current(args_len))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.body.extend(glyph_data);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subset the `loca` table by changing to the new offsets.
|
||||||
|
fn subset_loca(&mut self) -> FontResult<()> {
|
||||||
|
let tag = "loca".parse().unwrap();
|
||||||
|
let loca = self.read_table::<Locations>()?;
|
||||||
|
|
||||||
|
self.write_table_body(tag, |this| {
|
||||||
|
let mut offset = 0;
|
||||||
|
for &glyph in &this.glyphs {
|
||||||
|
this.body.write_u32::<BE>(offset)?;
|
||||||
|
let len = loca.length(glyph).take_invalid("missing loca entry")?;
|
||||||
|
offset += len;
|
||||||
|
}
|
||||||
|
this.body.write_u32::<BE>(offset)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Let a writer write the table body and then store the relevant metadata.
|
||||||
fn write_table_body<F>(&mut self, tag: Tag, writer: F) -> FontResult<()>
|
fn write_table_body<F>(&mut self, tag: Tag, writer: F) -> FontResult<()>
|
||||||
where F: FnOnce(&mut Self) -> FontResult<()> {
|
where F: FnOnce(&mut Self) -> FontResult<()> {
|
||||||
|
// Run the writer and capture the length.
|
||||||
let start = self.body.len();
|
let start = self.body.len();
|
||||||
writer(self)?;
|
writer(self)?;
|
||||||
let end = self.body.len();
|
let end = self.body.len();
|
||||||
|
|
||||||
|
// Pad with zeroes.
|
||||||
while (self.body.len() - start) % 4 != 0 {
|
while (self.body.len() - start) % 4 != 0 {
|
||||||
self.body.push(0);
|
self.body.push(0);
|
||||||
}
|
}
|
||||||
@ -394,7 +401,13 @@ impl<'a> Subsetter<'a> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_table_data(&self, tag: Tag) -> FontResult<&'a [u8]> {
|
/// Read a table with the opentype reader.
|
||||||
|
fn read_table<T: Table>(&mut self) -> FontResult<T> {
|
||||||
|
self.reader.read_table::<T>().map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the raw table data of a table.
|
||||||
|
fn read_table_data(&self, tag: Tag) -> FontResult<&'a [u8]> {
|
||||||
let record = match self.tables.binary_search_by_key(&tag, |r| r.tag) {
|
let record = match self.tables.binary_search_by_key(&tag, |r| r.tag) {
|
||||||
Ok(index) => &self.tables[index],
|
Ok(index) => &self.tables[index],
|
||||||
Err(_) => return Err(FontError::MissingTable(tag.to_string())),
|
Err(_) => return Err(FontError::MissingTable(tag.to_string())),
|
||||||
@ -402,44 +415,13 @@ impl<'a> Subsetter<'a> {
|
|||||||
|
|
||||||
self.font.program
|
self.font.program
|
||||||
.get(record.offset as usize .. (record.offset + record.length) as usize)
|
.get(record.offset as usize .. (record.offset + record.length) as usize)
|
||||||
.take_bytes()
|
.take_invalid("missing table data")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this font contains some table.
|
/// Whether this font contains a given table.
|
||||||
fn contains_table(&self, tag: Tag) -> bool {
|
fn contains_table(&self, tag: Tag) -> bool {
|
||||||
self.tables.binary_search_by_key(&tag, |r| r.tag).is_ok()
|
self.tables.binary_search_by_key(&tag, |r| r.tag).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_cmap(&mut self) -> FontResult<()> {
|
|
||||||
Ok(if self.cmap.is_none() {
|
|
||||||
self.cmap = Some(self.reader.read_table::<CharMap>()?);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_hmtx(&mut self) -> FontResult<()> {
|
|
||||||
Ok(if self.hmtx.is_none() {
|
|
||||||
self.hmtx = Some(self.reader.read_table::<HorizontalMetrics>()?);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_loca(&mut self) -> FontResult<()> {
|
|
||||||
Ok(if self.loca.is_none() {
|
|
||||||
let mut table = self.get_table_data("loca".parse().unwrap())?;
|
|
||||||
let format = self.reader.read_table::<Header>()?.index_to_loc_format;
|
|
||||||
let count = self.reader.read_table::<MaximumProfile>()?.num_glyphs + 1;
|
|
||||||
|
|
||||||
let loca = if format == 0 {
|
|
||||||
(0..count).map(|_| table.read_u16::<BE>()
|
|
||||||
.map(|x| (x as u32) * 2))
|
|
||||||
.collect::<io::Result<Vec<u32>>>()
|
|
||||||
} else {
|
|
||||||
(0..count).map(|_| table.read_u32::<BE>())
|
|
||||||
.collect::<io::Result<Vec<u32>>>()
|
|
||||||
}?;
|
|
||||||
|
|
||||||
self.loca = Some(loca);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate a checksum over the sliced data as sum of u32's. The data length has to be a multiple
|
/// Calculate a checksum over the sliced data as sum of u32's. The data length has to be a multiple
|
||||||
@ -461,11 +443,6 @@ fn calculate_check_sum(data: &[u8]) -> u32 {
|
|||||||
trait TakeInvalid<T>: Sized {
|
trait TakeInvalid<T>: Sized {
|
||||||
/// Pull the type out of the option, returning an invalid font error if self was not valid.
|
/// Pull the type out of the option, returning an invalid font error if self was not valid.
|
||||||
fn take_invalid<S: Into<String>>(self, message: S) -> FontResult<T>;
|
fn take_invalid<S: Into<String>>(self, message: S) -> FontResult<T>;
|
||||||
|
|
||||||
/// Same as above with predefined message "expected more bytes".
|
|
||||||
fn take_bytes(self) -> FontResult<T> {
|
|
||||||
self.take_invalid("expected more bytes")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> TakeInvalid<T> for Option<T> {
|
impl<T> TakeInvalid<T> for Option<T> {
|
||||||
@ -473,3 +450,23 @@ impl<T> TakeInvalid<T> for Option<T> {
|
|||||||
self.ok_or(FontError::InvalidFont(message.into()))
|
self.ok_or(FontError::InvalidFont(message.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::font::Font;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn subset() {
|
||||||
|
let program = std::fs::read("../fonts/NotoSans-Regular.ttf").unwrap();
|
||||||
|
let font = Font::new(program).unwrap();
|
||||||
|
|
||||||
|
let subsetted = font.subsetted(
|
||||||
|
"abcdefghijklmnopqrstuvwxyz‼".chars(),
|
||||||
|
&["name", "OS/2", "post", "head", "hhea", "hmtx", "maxp", "cmap",
|
||||||
|
"cvt ", "fpgm", "prep", "loca", "glyf"][..]
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
std::fs::write("../target/NotoSans-Subsetted.ttf", &subsetted.program).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user