Skip to content

Commit

Permalink
[write-fonts] glyf & loca improvements
Browse files Browse the repository at this point in the history
This adds a loca module and a Loca table, and also adds a Glyph enum to
represent either a simple or composite glyph.

A follow-up patch will add a builder type for constructing the glyf and
loca tables.
  • Loading branch information
cmyr committed Aug 3, 2023
1 parent 8eb03ab commit f591170
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 5 deletions.
1 change: 1 addition & 0 deletions write-fonts/src/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod hhea;
pub mod hmtx;
pub mod hvar;
pub mod layout;
pub mod loca;
pub mod maxp;
pub mod name;
pub mod os2;
Expand Down
99 changes: 98 additions & 1 deletion write-fonts/src/tables/glyf.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
//! The [glyf (Glyph Data)](https://docs.microsoft.com/en-us/typography/opentype/spec/glyf) table

use crate::{FontWrite, OtRound};
use crate::{
from_obj::{FromObjRef, FromTableRef},
validate::{Validate, ValidationCtx},
FontWrite, OtRound,
};

use font_types::Tag;
use kurbo::Rect;
use read_fonts::{FontRead, TopLevelTable};

mod composite;
mod simple;

pub use composite::{Anchor, Component, ComponentFlags, CompositeGlyph, Transform};
pub use simple::{simple_glyphs_from_kurbo, BadKurbo, Contour, 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.
pub struct Glyf(Vec<u8>);

impl TopLevelTable for Glyf {
/// 'glyf'
const TAG: Tag = Tag::new(b"glyf");
}

/// A simple or composite glyph
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Glyph {
Simple(SimpleGlyph),
Composite(CompositeGlyph),
}

/// A Bounding box.
///
/// This should be the minimum rectangle which fully encloses the glyph outline;
Expand All @@ -23,7 +47,26 @@ pub struct Bbox {
pub y_max: i16,
}

impl Glyph {
/// The bounding box for the glyph
pub fn bbox(&self) -> Bbox {
match self {
Glyph::Simple(glyph) => glyph.bbox,
Glyph::Composite(glyph) => glyph.bbox,
}
}

/// 'true' if the glyph contains no contours or components
pub fn is_empty(&self) -> bool {
match self {
Glyph::Simple(glyph) => glyph.contours().is_empty(),
Glyph::Composite(glyph) => glyph.components().is_empty(),
}
}
}

impl Bbox {
/// Return the smallest bounding box covering `self` and `other`
pub fn union(self, other: Bbox) -> Bbox {
Bbox {
x_min: self.x_min.min(other.x_min),
Expand Down Expand Up @@ -57,6 +100,60 @@ impl FontWrite for Bbox {
}
}

impl<'a> FromObjRef<read_fonts::tables::glyf::Glyph<'a>> for Glyph {
fn from_obj_ref(
from: &read_fonts::tables::glyf::Glyph<'a>,
data: read_fonts::FontData,
) -> Self {
match from {
read_fonts::tables::glyf::Glyph::Simple(glyph) => {
Self::Simple(SimpleGlyph::from_obj_ref(glyph, data))
}
read_fonts::tables::glyf::Glyph::Composite(glyph) => {
Self::Composite(CompositeGlyph::from_obj_ref(glyph, data))
}
}
}
}

impl<'a> FromTableRef<read_fonts::tables::glyf::Glyph<'a>> for Glyph {}

impl<'a> FontRead<'a> for Glyph {
fn read(data: read_fonts::FontData<'a>) -> Result<Self, read_fonts::ReadError> {
read_fonts::tables::glyf::Glyph::read(data).map(|g| Glyph::from_table_ref(&g))
}
}

impl From<SimpleGlyph> for Glyph {
fn from(value: SimpleGlyph) -> Self {
Glyph::Simple(value)
}
}

impl From<CompositeGlyph> for Glyph {
fn from(value: CompositeGlyph) -> Self {
Glyph::Composite(value)
}
}

impl Validate for Glyph {
fn validate_impl(&self, ctx: &mut ValidationCtx) {
match self {
Glyph::Simple(glyph) => glyph.validate_impl(ctx),
Glyph::Composite(glyph) => glyph.validate_impl(ctx),
}
}
}

impl FontWrite for Glyph {
fn write_into(&self, writer: &mut crate::TableWriter) {
match self {
Glyph::Simple(glyph) => glyph.write_into(writer),
Glyph::Composite(glyph) => glyph.write_into(writer),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
4 changes: 2 additions & 2 deletions write-fonts/src/tables/glyf/composite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ use super::Bbox;
pub use read_fonts::tables::glyf::{Anchor, Transform};

/// A glyph consisting of multiple component sub-glyphs
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CompositeGlyph {
pub bbox: Bbox,
components: Vec<Component>,
_instructions: Vec<u8>,
}

/// A single component glyph (part of a [`CompositeGlyph`]).
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Component {
pub glyph: GlyphId,
pub anchor: Anchor,
Expand Down
4 changes: 2 additions & 2 deletions write-fonts/src/tables/glyf/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ use read_fonts::{
use super::Bbox;

/// A simple (without components) glyph
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SimpleGlyph {
pub bbox: Bbox,
contours: Vec<Contour>,
_instructions: Vec<u8>,
}

/// A single contour, comprising only line and quadratic bezier segments
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Contour(Vec<CurvePoint>);

/// An error if an input curve is malformed
Expand Down
86 changes: 86 additions & 0 deletions write-fonts/src/tables/loca.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//! The [loca (Index to Location)][loca] table
//!
//! [loca]: https://docs.microsoft.com/en-us/typography/opentype/spec/loca

use read_fonts::TopLevelTable;
use types::Tag;

use crate::{
validate::{Validate, ValidationCtx},
FontWrite,
};

/// The [loca] table.
///
/// [loca]: https://docs.microsoft.com/en-us/typography/opentype/spec/loca
pub struct Loca {
// we just store u32, and then convert to u16 if needed in the `FontWrite` impl
pub(crate) offsets: Vec<u32>,
loca_format: LocaFormat,
}

/// Whether or not the 'loca' table uses short or long offsets.
///
/// This flag is stored in the 'head' table's [indexToLocFormat][locformat] field.
/// See the ['loca' spec][spec] for more information.
///
/// [locformat]: super::head::Head::index_to_loc_format
/// [spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/loca
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum LocaFormat {
Short = 0,
Long = 1,
}

impl TopLevelTable for Loca {
const TAG: Tag = Tag::new(b"loca");
}

impl Loca {
/// Create a new loca table from 32-bit offsets.
///
/// The loca format will be calculated based on the raw values.
pub fn new(offsets: Vec<u32>) -> Self {
let loca_format = LocaFormat::new(&offsets);

Loca {
offsets,
loca_format,
}
}

pub fn format(&self) -> LocaFormat {
self.loca_format
}
}

impl LocaFormat {
fn new(loca: &[u32]) -> LocaFormat {
// https://github.com/fonttools/fonttools/blob/1c283756a5e39d69459eea80ed12792adc4922dd/Lib/fontTools/ttLib/tables/_l_o_c_a.py#L37
const MAX_SHORT_LOCA_VALUE: u32 = 0x20000;
if loca.last().copied().unwrap_or_default() < MAX_SHORT_LOCA_VALUE
&& loca.iter().all(|offset| offset % 2 == 0)
{
LocaFormat::Short
} else {
LocaFormat::Long
}
}
}

impl FontWrite for Loca {
fn write_into(&self, writer: &mut crate::TableWriter) {
match self.loca_format {
LocaFormat::Long => self.offsets.write_into(writer),
LocaFormat::Short => self
.offsets
.iter()
.for_each(|off| ((off >> 1) as u16).write_into(writer)),
}
}
}

impl Validate for Loca {
fn validate_impl(&self, _ctx: &mut ValidationCtx) {}
}

0 comments on commit f591170

Please sign in to comment.