Skip to content

Commit

Permalink
Reverse TrueType glyph contour direction by default to match fontmake
Browse files Browse the repository at this point in the history
Fixes #174
  • Loading branch information
anthrotype committed Sep 29, 2023
1 parent 4a3fc6b commit e12e857
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 3 deletions.
2 changes: 2 additions & 0 deletions fontbe/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub enum Error {
glyph: GlyphName,
errors: Vec<Error>,
},
#[error("Contour reversal error {0}")]
ContourReversalError(String),
#[error("Generating bytes for {context} failed: {e}")]
DumpTableError {
e: write_fonts::error::Error,
Expand Down
50 changes: 48 additions & 2 deletions fontbe/src/glyphs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use fontdrasil::{
use fontir::{
coords::{Location, NormalizedCoord, NormalizedLocation},
ir,
orchestration::WorkId as FeWorkId,
orchestration::{Flags, WorkId as FeWorkId},
variations::{VariationModel, VariationRegion},
};
use kurbo::{cubics_to_quadratic_splines, Affine, BezPath, CubicBez, PathEl, Point, Rect, Vec2};
Expand All @@ -26,6 +26,7 @@ use read_fonts::{
types::{F2Dot14, GlyphId},
};
use write_fonts::{
pens::{write_to_pen, BezPathPen, ReverseContourPen},
tables::{
glyf::{
Bbox, Component, ComponentFlags, CompositeGlyph, GlyfLocaBuilder, Glyph as RawGlyph,
Expand Down Expand Up @@ -346,7 +347,12 @@ impl Work<Context, AnyWorkId, Error> for GlyphWork {
let glyph = CheckedGlyph::new(ir_glyph)?;

// Hopefully in time https://github.com/harfbuzz/boring-expansion-spec means we can drop this
let glyph = cubics_to_quadratics(glyph);
let mut glyph = cubics_to_quadratics(glyph);

if !context.flags.contains(Flags::KEEP_DIRECTION) {
glyph = reverse_contour_direction(glyph)?;
}

let should_iup = glyph.should_iup(); // we partially borrow it later

let (name, point_seqs, contour_ends) = match glyph {
Expand Down Expand Up @@ -562,6 +568,46 @@ fn cubics_to_quadratics(glyph: CheckedGlyph) -> CheckedGlyph {
}
}

/// Flip the direction of the contours in the glyph.
///
/// The source contours are normally drawn with cubic curves thus are expected to be
/// in counter-clockwise winding direction as recommended for PostScript outlines.
/// When converting to TrueType quadratic splines, we reverse them so that they
/// follow the clockwise direction as recommeded for TrueType outlines.
fn reverse_contour_direction(glyph: CheckedGlyph) -> Result<CheckedGlyph, Error> {
let CheckedGlyph::Contour {
name,
paths: contours,
} = glyph
else {
return Ok(glyph); // nop for composite
};

trace!("Reverse '{name}' contour direction");

let mut reversed_contours = HashMap::<NormalizedLocation, BezPath>::new();
for (loc, contour) in contours.iter() {
let reversed = reverse_direction(contour)?;
reversed_contours.insert(loc.clone(), reversed);
}

Ok(CheckedGlyph::Contour {
name,
paths: reversed_contours,
})
}

/// Reverse the winding direction of the path.
fn reverse_direction(path: &BezPath) -> Result<BezPath, Error> {
let mut bez_pen = BezPathPen::new();
let mut rev_pen = ReverseContourPen::new(&mut bez_pen);
write_to_pen(path, &mut rev_pen);
rev_pen
.flush()
.map_err(|e| Error::ContourReversalError(format!("{e:?}")))?;
Ok(bez_pen.into_inner())
}

/// An [ir::Glyph] that has been confirmed to maintain invariants:
///
/// <ul>
Expand Down
11 changes: 11 additions & 0 deletions fontc/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ pub struct Args {
/// Set to false to skip compilation of OpenType Layout features
#[arg(long, default_value = "true", action = ArgAction::Set)]
pub compile_features: bool,

/// For TTFs, whether to keep the original glyph contour direction.
///
/// By default we reverse the direction of all contours to match clockwise orientation
/// as recommended for TrueType, with the assumption that the original contours
/// containing cubic Bezier curves were drawn in a counter-clockwise direction as
/// is recommended for PostScript.
#[arg(long, default_value = "false")]
pub keep_direction: bool,
}

impl Args {
Expand All @@ -60,6 +69,7 @@ impl Args {
flags.set(Flags::PREFER_SIMPLE_GLYPHS, self.prefer_simple_glyphs);
flags.set(Flags::FLATTEN_COMPONENTS, self.flatten_components);
flags.set(Flags::EMIT_TIMING, self.emit_timing);
flags.set(Flags::KEEP_DIRECTION, self.keep_direction);

flags
}
Expand All @@ -81,6 +91,7 @@ impl Args {
prefer_simple_glyphs: Flags::default().contains(Flags::PREFER_SIMPLE_GLYPHS),
flatten_components: Flags::default().contains(Flags::FLATTEN_COMPONENTS),
compile_features: true,
keep_direction: false,
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion fontir/src/orchestration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ bitflags! {
const FLATTEN_COMPONENTS = 0b00001000;
// If set a files reporting on timing will be emitted to disk
const EMIT_TIMING = 0b00010000;
// If set, the direction of contours will NOT be reversed
const KEEP_DIRECTION = 0b00100000;
}
}

impl Default for Flags {
/// Match the way gftools configures fontmake by default
/// Match vanilla fontmake's defaults, *not* the way gftools configures it
fn default() -> Self {
Flags::EMIT_IR | Flags::PREFER_SIMPLE_GLYPHS
}
Expand Down

0 comments on commit e12e857

Please sign in to comment.