Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GlyfLocaBuilder #559

Merged
merged 1 commit into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions write-fonts/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ impl std::fmt::Debug for PackingError {
impl std::error::Error for PackingError {}
impl std::error::Error for Error {}

impl From<ValidationReport> for Error {
fn from(value: ValidationReport) -> Self {
Error::ValidationFailed(value)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
8 changes: 6 additions & 2 deletions write-fonts/src/tables/glyf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ use kurbo::Rect;
use read_fonts::{FontRead, TopLevelTable};

mod composite;
mod glyf_loca_builder;
mod simple;

pub use composite::{Anchor, Component, ComponentFlags, CompositeGlyph, Transform};
pub use glyf_loca_builder::{GlyfLocaBuilder, SomeGlyph};
pub use simple::{Contour, MalformedPath, SimpleGlyph};

/// The [glyf (Glyph Data)](https://docs.microsoft.com/en-us/typography/opentype/spec/glyf) table
///
/// Compiling the glyf table requires additional logic, since the positions
/// of glyfs are stored in the 'loca' type.
/// This table is the concatenated bytes of all the glyphs in the font, with
/// the positions of each individual glyph stored in the ['loca' table][super::loca].
/// As such, these two tables must be constructed together. The [`GlyfLocaBuilder`]
/// type is provided to simplify this.
pub struct Glyf(Vec<u8>);

impl TopLevelTable for Glyf {
Expand Down
200 changes: 200 additions & 0 deletions write-fonts/src/tables/glyf/glyf_loca_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
//! A builder for the 'glyf' and 'loca' tables

use crate::{
error::Error,
tables::loca::{Loca, LocaFormat},
validate::Validate,
FontWrite, TableWriter,
};

use super::{CompositeGlyph, Glyf, Glyph, SimpleGlyph};

/// A builder for constructing the 'glyf' & 'loca' tables.
///
/// These two tables are tightly coupled, and are necessarily constructed
/// together.
///
/// # Example
///
/// ```
/// use write_fonts::tables::glyf::{Glyph, GlyfLocaBuilder};
/// # fn get_glyphs() -> Vec<(String, Glyph)> { Vec::new() }
///
/// let names_and_glyphs: Vec<(String, Glyph)> = get_glyphs();
/// let mut builder = GlyfLocaBuilder::new();
///
/// for (name, glyph) in names_and_glyphs {
/// // your error handling goes here
/// if let Err(e) = builder.add_glyph(&glyph) {
/// panic!("error compiling glyph '{name}': '{e}'");
/// }
/// }
///
/// let (_glyf, _loca, _loca_format) = builder.build();
/// // store the results somewhere
/// ```
pub struct GlyfLocaBuilder {
glyph_writer: TableWriter,
raw_loca: Vec<u32>,
}

/// A trait encompassing [`Glyph`], [`SimpleGlyph`] and [`CompositeGlyph`]
///
/// This is a marker trait to ensure that only glyphs are passed to [`GlyfLocaBuilder`].
pub trait SomeGlyph: Validate + FontWrite {
/// Returns `true` if the glyph contains no contours or components.
///
/// If a glyph is empty, we do not need to write any data at all for the glyph,
/// and we insert a duplicate value in the loca table.
fn is_empty(&self) -> bool;
}

impl GlyfLocaBuilder {
/// Construct a new builder for the 'glyf' and 'loca' tables.
pub fn new() -> Self {
Self {
glyph_writer: TableWriter::default(),
raw_loca: vec![0],
}
}

/// Add a glyph to the table.
///
/// The argument can be any of [`Glyph`], [`SimpleGlyph`] or [`CompositeGlyph`].
///
/// The glyph is validated and compiled immediately, so that the caller can
/// associate any errors with a particular glyph.
pub fn add_glyph(&mut self, glyph: &impl SomeGlyph) -> Result<&mut Self, Error> {
if !glyph.is_empty() {
glyph.validate()?;
glyph.write_into(&mut self.glyph_writer);
}
let pos = self.glyph_writer.current_data().bytes.len();
self.raw_loca.push(pos as u32);
Ok(self)
}

/// Construct the final glyf and loca tables.
///
/// This method also returns the loca format; the caller is responsible for
/// setting this field in the ['head'] table.
///
/// [`head`]: crate::tables::head::Head::index_to_loc_format
#[must_use]
pub fn build(self) -> (Glyf, Loca, LocaFormat) {
let glyph_data = self.glyph_writer.into_data();
let loca = Loca::new(self.raw_loca);
let format = loca.format();
(Glyf(glyph_data), loca, format)
}
}

impl SomeGlyph for SimpleGlyph {
fn is_empty(&self) -> bool {
self.contours().is_empty()
}
}

impl SomeGlyph for CompositeGlyph {
fn is_empty(&self) -> bool {
self.components().is_empty()
}
}

impl SomeGlyph for Glyph {
fn is_empty(&self) -> bool {
self.is_empty()
}
}

impl Default for GlyfLocaBuilder {
fn default() -> Self {
Self::new()
}
}

#[cfg(test)]
mod tests {
use super::super::{Anchor, Component, ComponentFlags, Transform};
use crate::from_obj::FromTableRef;
use font_types::GlyphId;
use kurbo::{BezPath, Shape};
use read_fonts::FontRead;

use super::*;

#[test]
fn build_some_glyphs() {
fn make_triangle() -> BezPath {
let mut path = BezPath::new();
path.move_to((0., 0.));
path.line_to((0., 40.));
path.line_to((20., 40.));
path.line_to((0., 0.));
path
}
let notdef = BezPath::new();
let square = kurbo::Rect::from_points((5., 5.), (100., 100.)).into_path(0.1);
let triangle = make_triangle();

let glyph0 = SimpleGlyph::from_bezpath(&notdef).unwrap();
let glyph1 = SimpleGlyph::from_bezpath(&square).unwrap();
let glyph2 = SimpleGlyph::from_bezpath(&triangle).unwrap();
let gid1 = GlyphId::new(1);
let gid2 = GlyphId::new(2);

let mut glyph3 = CompositeGlyph::new(
Component::new(
gid1,
Anchor::Offset { x: 0, y: 0 },
Transform::default(),
ComponentFlags::default(),
),
square.bounding_box(),
);
glyph3.add_component(
Component::new(
gid2,
Anchor::Offset { x: 0, y: 0 },
Transform::default(),
ComponentFlags::default(),
),
triangle.bounding_box(),
);

let len1 = crate::dump_table(&glyph1).unwrap().len() as u32;
let len2 = crate::dump_table(&glyph2).unwrap().len() as u32;
let len3 = crate::dump_table(&glyph3).unwrap().len() as u32;

let mut builder = GlyfLocaBuilder::new();
builder
.add_glyph(&glyph0)
.unwrap()
.add_glyph(&glyph1)
.unwrap()
.add_glyph(&glyph2)
.unwrap()
.add_glyph(&glyph3)
.unwrap();

let (glyf, loca, format) = builder.build();
assert_eq!(loca.offsets.len(), 5);
assert_eq!(loca.offsets, &[0, 0, len1, len1 + len2, len1 + len2 + len3]);

let rglyf = read_fonts::tables::glyf::Glyf::read(glyf.0.as_slice().into()).unwrap();
let loca_bytes = crate::dump_table(&loca).unwrap();
let rloca = read_fonts::tables::loca::Loca::read(
loca_bytes.as_slice().into(),
format == LocaFormat::Long,
)
.unwrap();

let rglyph1 = Glyph::from_table_ref(&rloca.get_glyf(gid1, &rglyf).unwrap().unwrap());
let rglyph2 = Glyph::from_table_ref(&rloca.get_glyf(gid2, &rglyf).unwrap().unwrap());
let rglyph3 =
Glyph::from_table_ref(&rloca.get_glyf(GlyphId::new(3), &rglyf).unwrap().unwrap());
assert_eq!(rglyph1, glyph1.into());
assert_eq!(rglyph2, glyph2.into());
assert_eq!(rglyph3, glyph3.into());
}
}
4 changes: 4 additions & 0 deletions write-fonts/src/tables/loca.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ impl Loca {
/// Create a new loca table from 32-bit offsets.
///
/// The loca format will be calculated based on the raw values.
///
/// You generally do not construct this directly; it is constructed alongside
/// the corresponding 'glyf' table using the
/// [GlyfLocaBuilder](super::glyf::GlyfLocaBuilder).
pub fn new(offsets: Vec<u32>) -> Self {
let loca_format = LocaFormat::new(&offsets);

Expand Down
10 changes: 9 additions & 1 deletion write-fonts/src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub struct TableWriter {
/// otherwise it will return the bytes encoding the table.
pub fn dump_table<T: FontWrite + Validate>(table: &T) -> Result<Vec<u8>, Error> {
log::trace!("writing table '{}'", table.table_type());
table.validate().map_err(Error::ValidationFailed)?;
table.validate()?;
let mut graph = TableWriter::make_graph(table);

if !graph.pack_objects() {
Expand Down Expand Up @@ -125,6 +125,14 @@ impl TableWriter {
assert!(result.offsets.is_empty());
result.bytes
}

/// A reference to the current table data.
///
/// This is currently only used to figure out the glyph positions when
/// compiling the glyf table.
pub(crate) fn current_data(&self) -> &TableData {
self.stack.last().unwrap() // there is always at least one
}
}

impl Default for TableWriter {
Expand Down