Skip to content

Commit

Permalink
Add Font::build_outline & trait OutlineBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
alexheretic committed Aug 31, 2024
1 parent 1a02ae8 commit fe8d7cb
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 4 deletions.
65 changes: 65 additions & 0 deletions dev/tests/usage.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
}

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());
}
}
4 changes: 4 additions & 0 deletions glyph/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
22 changes: 19 additions & 3 deletions glyph/src/font.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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<Rect> {
_ = (glyph_id, builder);
// panic impl prevents this method from breaking external Font impls
unimplemented!()
}
}

impl<F: Font> Font for &F {
Expand Down Expand Up @@ -380,4 +391,9 @@ impl<F: Font> Font for &F {
fn font_data(&self) -> &[u8] {
(*self).font_data()
}

#[inline]
fn build_outline(&self, glyph_id: GlyphId, builder: &mut dyn OutlineBuilder) -> Option<Rect> {
(*self).build_outline(glyph_id, builder)
}
}
7 changes: 6 additions & 1 deletion glyph/src/font_arc.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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<Rect> {
self.0.build_outline(glyph_id, builder)
}
}

impl From<FontVec> for FontArc {
Expand Down
22 changes: 22 additions & 0 deletions glyph/src/outlined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
27 changes: 27 additions & 0 deletions glyph/src/ttfp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Rect> {
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()),
})
}
}
};
}
Expand Down
30 changes: 30 additions & 0 deletions glyph/src/ttfp/outliner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

0 comments on commit fe8d7cb

Please sign in to comment.