Skip to content

Commit

Permalink
(gvar) Add phantom points support.
Browse files Browse the repository at this point in the history
Closes #27
  • Loading branch information
RazrFalcon committed Jun 22, 2024
1 parent 41cf80c commit 12c5719
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 9 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- `Face::glyph_hor_advance` and `Face::glyph_ver_advance` include `gvar`'s phantom points
when `HVAR`/`VVAR` tables are missing. Affects only variable fonts.
- `Face::glyph_phantom_points`

### Fixed
- `opentype_layout::LookupFlags::mark_attachment_type` parsing.

Expand Down
40 changes: 40 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,30 @@ impl core::fmt::Debug for Transform {
}
}

/// A float point.
#[derive(Clone, Copy, Debug)]
pub struct PointF {
/// The X-axis coordinate.
pub x: f32,
/// The Y-axis coordinate.
pub y: f32,
}

/// Phantom points.
///
/// Available only for variable fonts with the `gvar` table.
#[derive(Clone, Copy, Debug)]
pub struct PhantomPoints {
/// Left side bearing point.
pub left: PointF,
/// Right side bearing point.
pub right: PointF,
/// Top side bearing point.
pub top: PointF,
/// Bottom side bearing point.
pub bottom: PointF,
}

/// A RGBA color in the sRGB color space.
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
Expand Down Expand Up @@ -1886,6 +1910,9 @@ impl<'a> Face<'a> {
// We can't use `round()` in `no_std`, so this is the next best thing.
advance += offset + 0.5;
}
} else if let Some(points) = self.glyph_phantom_points(glyph_id) {
// We can't use `round()` in `no_std`, so this is the next best thing.
advance += points.right.x + 0.5
}
}

Expand Down Expand Up @@ -1914,6 +1941,9 @@ impl<'a> Face<'a> {
// We can't use `round()` in `no_std`, so this is the next best thing.
advance += offset + 0.5;
}
} else if let Some(points) = self.glyph_phantom_points(glyph_id) {
// We can't use `round()` in `no_std`, so this is the next best thing.
advance += points.bottom.y + 0.5
}
}

Expand Down Expand Up @@ -2314,6 +2344,16 @@ impl<'a> Face<'a> {
self.coordinates.as_slice().iter().any(|c| c.0 != 0)
}

/// Parses glyph's phantom points.
///
/// Available only for variable fonts with the `gvar` table.
#[cfg(feature = "variable-fonts")]
pub fn glyph_phantom_points(&self, glyph_id: GlyphId) -> Option<PhantomPoints> {
let glyf = self.tables.glyf?;
let gvar = self.tables.gvar?;
gvar.phantom_points(glyf, self.coords(), glyph_id)
}

#[cfg(feature = "variable-fonts")]
#[inline]
fn metrics_var_offset(&self, tag: Tag) -> f32 {
Expand Down
28 changes: 28 additions & 0 deletions src/tables/glyf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,4 +604,32 @@ impl<'a> Table<'a> {
let range = self.loca_table.glyph_range(glyph_id)?;
self.data.get(range)
}

/// Returns the number of points in this outline.
pub(crate) fn outline_points(&self, glyph_id: GlyphId) -> u16 {
self.outline_points_impl(glyph_id).unwrap_or(0)
}

fn outline_points_impl(&self, glyph_id: GlyphId) -> Option<u16> {
let data = self.get(glyph_id)?;
let mut s = Stream::new(data);
let number_of_contours = s.read::<i16>()?;

// Skip bbox.
s.advance(8);

if number_of_contours > 0 {
// Simple glyph.
let number_of_contours = NonZeroU16::new(number_of_contours as u16)?;
let glyph_points = parse_simple_outline(s.tail()?, number_of_contours)?;
Some(glyph_points.points_left)
} else if number_of_contours < 0 {
// Composite glyph.
let components = CompositeGlyphIter::new(s.tail()?);
Some(components.clone().count() as u16)
} else {
// An empty glyph.
None
}
}
}
35 changes: 26 additions & 9 deletions src/tables/gvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use core::cmp;
use core::convert::TryFrom;
use core::num::NonZeroU16;

use crate::glyf;
use crate::{glyf, PhantomPoints, PointF};
use crate::parser::{LazyArray16, Offset, Offset16, Offset32, Stream, F2DOT14};
use crate::{GlyphId, NormalizedCoordinate, OutlineBuilder, Rect, RectF, Transform};

Expand Down Expand Up @@ -179,7 +179,7 @@ impl<'a> VariationTuples<'a> {
all_points: glyf::GlyphPointsIter,
points: glyf::GlyphPointsIter,
point: glyf::GlyphPoint,
) -> Option<(f32, f32)> {
) -> Option<PointF> {
let mut x = f32::from(point.x);
let mut y = f32::from(point.y);

Expand Down Expand Up @@ -232,13 +232,13 @@ impl<'a> VariationTuples<'a> {
}
}

Some((x, y))
Some(PointF { x, y })
}

// This is just like `apply()`, but without `infer_deltas`,
// since we use it only for component points and not a contour.
// And since there are no contour and no points, `infer_deltas()` will do nothing.
fn apply_null(&mut self) -> Option<(f32, f32)> {
fn apply_null(&mut self) -> Option<PointF> {
let mut x = 0.0;
let mut y = 0.0;

Expand All @@ -258,7 +258,7 @@ impl<'a> VariationTuples<'a> {
}
}

Some((x, y))
Some(PointF { x, y })
}
}

Expand Down Expand Up @@ -1757,6 +1757,23 @@ impl<'a> Table<'a> {
);
b.bbox.to_rect()
}

pub(crate) fn phantom_points(
&self,
glyf_table: glyf::Table,
coordinates: &[NormalizedCoordinate],
glyph_id: GlyphId,
) -> Option<PhantomPoints> {
let outline_points = glyf_table.outline_points(glyph_id);
let mut tuples = VariationTuples::default();
self.parse_variation_data(glyph_id, coordinates, outline_points, &mut tuples)?;
Some(PhantomPoints {
left: tuples.apply_null()?,
right: tuples.apply_null()?,
top: tuples.apply_null()?,
bottom: tuples.apply_null()?,
})
}
}

impl core::fmt::Debug for Table<'_> {
Expand Down Expand Up @@ -1803,8 +1820,8 @@ fn outline_var_impl(
gvar_table.parse_variation_data(glyph_id, coordinates, points_len, &mut tuples)?;

while let Some(point) = glyph_points.next() {
let (x, y) = tuples.apply(all_glyph_points.clone(), glyph_points.clone(), point)?;
builder.push_point(x, y, point.on_curve_point, point.last_point);
let p = tuples.apply(all_glyph_points.clone(), glyph_points.clone(), point)?;
builder.push_point(p.x, p.y, point.on_curve_point, point.last_point);
}

Some(())
Expand All @@ -1824,14 +1841,14 @@ fn outline_var_impl(
gvar_table.parse_variation_data(glyph_id, coordinates, components_count, &mut tuples)?;

for component in components {
let (tx, ty) = tuples.apply_null()?;
let t = tuples.apply_null()?;

let mut transform = builder.transform;

// Variation component offset should be applied only when
// the ARGS_ARE_XY_VALUES flag is set.
if component.flags.args_are_xy_values() {
transform = Transform::combine(transform, Transform::new_translate(tx, ty));
transform = Transform::combine(transform, Transform::new_translate(t.x, t.y));
}

transform = Transform::combine(transform, component.transform);
Expand Down

0 comments on commit 12c5719

Please sign in to comment.