From fe8d7cbe3faae4a1880d1c1ad62690a6c56a09fb Mon Sep 17 00:00:00 2001 From: Alex Butler Date: Sat, 31 Aug 2024 10:21:30 +0100 Subject: [PATCH] Add Font::build_outline & trait OutlineBuilder --- dev/tests/usage.rs | 65 ++++++++++++++++++++++++++++++++++++++ glyph/CHANGELOG.md | 4 +++ glyph/src/font.rs | 22 +++++++++++-- glyph/src/font_arc.rs | 7 +++- glyph/src/outlined.rs | 22 +++++++++++++ glyph/src/ttfp.rs | 27 ++++++++++++++++ glyph/src/ttfp/outliner.rs | 30 ++++++++++++++++++ 7 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 dev/tests/usage.rs diff --git a/dev/tests/usage.rs b/dev/tests/usage.rs new file mode 100644 index 0000000..871c5e1 --- /dev/null +++ b/dev/tests/usage.rs @@ -0,0 +1,65 @@ +use ab_glyph::{point, Font, FontRef, Point, Rect}; + +#[test] +fn build_outline() { + let font = FontRef::try_from_slice(include_bytes!("../fonts/OpenSans-Italic.ttf")).unwrap(); + let glyph_id = font.glyph_id('x'); + + let mut outliner = Outliner::default(); + + let rect = font.build_outline(glyph_id, &mut outliner).unwrap(); + assert_eq!( + rect, + Rect { + min: point(-74.0, 1096.0), + max: point(1030.0, 0.0), + } + ); + + assert_eq!( + outliner.calls, + vec![ + "M point(467.0, 434.0)", + "L point(121.0, 0.0)", + "L point(-74.0, 0.0)", + "L point(401.0, 565.0)", + "L point(162.0, 1096.0)", + "L point(332.0, 1096.0)", + "L point(506.0, 684.0)", + "L point(836.0, 1096.0)", + "L point(1030.0, 1096.0)", + "L point(575.0, 557.0)", + "L point(827.0, 0.0)", + "L point(659.0, 0.0)", + "L point(467.0, 434.0)", + "Z" + ] + ); +} + +#[derive(Debug, Default)] +struct Outliner { + calls: Vec, +} + +impl ab_glyph::OutlineBuilder for Outliner { + fn move_to(&mut self, p: Point) { + self.calls.push(format!("M {p:?}")); + } + + fn line_to(&mut self, p: Point) { + self.calls.push(format!("L {p:?}")); + } + + fn quad_to(&mut self, a: Point, b: Point) { + self.calls.push(format!("Q {a:?} {b:?}")); + } + + fn curve_to(&mut self, a: Point, b: Point, c: Point) { + self.calls.push(format!("C {a:?} {b:?} {c:?}")); + } + + fn close(&mut self) { + self.calls.push("Z".into()); + } +} diff --git a/glyph/CHANGELOG.md b/glyph/CHANGELOG.md index 5279961..81eb8d7 100644 --- a/glyph/CHANGELOG.md +++ b/glyph/CHANGELOG.md @@ -1,3 +1,7 @@ +# Unreleased (0.2.29) +* Add `Font::build_outline` & trait `OutlineBuilder` to provide ttf-parser like custom outline + building logic. + # 0.2.28 * Update _ttf-parser_ to `0.24`. * Clarify `OutlinedGlyph::px_bounds`, `Font::glyph_bounds` documentation, diff --git a/glyph/src/font.rs b/glyph/src/font.rs index 96f45eb..706d004 100644 --- a/glyph/src/font.rs +++ b/glyph/src/font.rs @@ -1,6 +1,6 @@ use crate::{ - point, v2, Glyph, GlyphId, GlyphSvg, Outline, OutlinedGlyph, PxScale, PxScaleFont, Rect, - ScaleFont, + point, v2, Glyph, GlyphId, GlyphSvg, Outline, OutlineBuilder, OutlinedGlyph, PxScale, + PxScaleFont, Rect, ScaleFont, }; /// Functionality required from font data. @@ -293,11 +293,22 @@ pub trait Font { /// ``` /// /// [`FontArc::try_from_slice`]: crate::FontArc::try_from_slice - #[inline] fn font_data(&self) -> &[u8] { // panic impl prevents this method from breaking external Font impls unimplemented!() } + + /// Outlines a glyph via a [`OutlineBuilder`] and returns its tight bounding box. + /// + /// This fn facilitates more custom usage. For general use also see + /// [`Font::outline_glyph`], [`Font::outline`]. + /// + /// Returns `None` when glyph has no outline or on error. + fn build_outline(&self, glyph_id: GlyphId, builder: &mut dyn OutlineBuilder) -> Option { + _ = (glyph_id, builder); + // panic impl prevents this method from breaking external Font impls + unimplemented!() + } } impl Font for &F { @@ -380,4 +391,9 @@ impl Font for &F { fn font_data(&self) -> &[u8] { (*self).font_data() } + + #[inline] + fn build_outline(&self, glyph_id: GlyphId, builder: &mut dyn OutlineBuilder) -> Option { + (*self).build_outline(glyph_id, builder) + } } diff --git a/glyph/src/font_arc.rs b/glyph/src/font_arc.rs index b757a90..2a128cc 100644 --- a/glyph/src/font_arc.rs +++ b/glyph/src/font_arc.rs @@ -1,4 +1,4 @@ -use crate::{v2, Font, FontRef, FontVec, GlyphId, InvalidFont, Outline}; +use crate::{v2, Font, FontRef, FontVec, GlyphId, InvalidFont, Outline, OutlineBuilder, Rect}; use alloc::sync::Arc; use core::fmt; @@ -150,6 +150,11 @@ impl Font for FontArc { fn font_data(&self) -> &[u8] { self.0.font_data() } + + #[inline] + fn build_outline(&self, glyph_id: GlyphId, builder: &mut dyn OutlineBuilder) -> Option { + self.0.build_outline(glyph_id, builder) + } } impl From for FontArc { diff --git a/glyph/src/outlined.rs b/glyph/src/outlined.rs index 1820624..d027f6f 100644 --- a/glyph/src/outlined.rs +++ b/glyph/src/outlined.rs @@ -189,3 +189,25 @@ impl Rect { self.max.y - self.min.y } } + +/// A trait for custom glyph outline construction. +pub trait OutlineBuilder { + /// Appends a MoveTo segment. + /// + /// Start of a contour. + fn move_to(&mut self, p: Point); + + /// Appends a LineTo segment. + fn line_to(&mut self, p: Point); + + /// Appends a QuadTo segment. + fn quad_to(&mut self, a: Point, b: Point); + + /// Appends a CurveTo segment. + fn curve_to(&mut self, a: Point, b: Point, c: Point); + + /// Appends a ClosePath segment. + /// + /// End of a contour. + fn close(&mut self); +} diff --git a/glyph/src/ttfp.rs b/glyph/src/ttfp.rs index 5b021f7..67670c6 100644 --- a/glyph/src/ttfp.rs +++ b/glyph/src/ttfp.rs @@ -357,6 +357,33 @@ macro_rules! impl_font { fn font_data(&self) -> &[u8] { self.0.as_face_ref().raw_face().data } + + #[inline] + fn build_outline( + &self, + glyph_id: GlyphId, + builder: &mut dyn crate::OutlineBuilder, + ) -> Option { + let ttfp::Rect { + x_min, + x_max, + y_min, + y_max, + } = self + .0 + .as_face_ref() + .outline_glyph( + glyph_id.into(), + &mut outliner::CompatOutlineBuilder(builder), + ) + // invalid bounds are treated as having no outline + .filter(|b| b.x_min < b.x_max && b.y_min < b.y_max)?; + + Some(Rect { + min: point(x_min.into(), y_max.into()), + max: point(x_max.into(), y_min.into()), + }) + } } }; } diff --git a/glyph/src/ttfp/outliner.rs b/glyph/src/ttfp/outliner.rs index e2c4a39..390a159 100644 --- a/glyph/src/ttfp/outliner.rs +++ b/glyph/src/ttfp/outliner.rs @@ -63,3 +63,33 @@ impl owned_ttf_parser::OutlineBuilder for OutlineCurveBuilder { } } } + +/// Compatibility struct for ttf-parser using a OutlineBuilder. +pub(crate) struct CompatOutlineBuilder<'a>(pub &'a mut dyn crate::OutlineBuilder); + +impl owned_ttf_parser::OutlineBuilder for CompatOutlineBuilder<'_> { + #[inline] + fn move_to(&mut self, x: f32, y: f32) { + self.0.move_to(point(x, y)); + } + + #[inline] + fn line_to(&mut self, x: f32, y: f32) { + self.0.line_to(point(x, y)); + } + + #[inline] + fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) { + self.0.quad_to(point(x1, y1), point(x2, y2)); + } + + #[inline] + fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) { + self.0.curve_to(point(x1, y1), point(x2, y2), point(x3, y3)) + } + + #[inline] + fn close(&mut self) { + self.0.close(); + } +}