From f0c634b609edcab689748c3dc6d33292e5dcadc8 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Thu, 16 Nov 2023 20:46:21 -0500 Subject: [PATCH] [skrifa] cleanup glyf code structure No functional changes. Just some much needed renaming and code shuffling. --- skrifa/src/scale/glyf/mem.rs | 36 +- skrifa/src/scale/glyf/mod.rs | 775 +++++++++++++++++- .../src/scale/glyf/{glyph.rs => outline.rs} | 16 +- skrifa/src/scale/glyf/scaler.rs | 773 ----------------- skrifa/src/scale/scaler.rs | 8 +- 5 files changed, 800 insertions(+), 808 deletions(-) rename skrifa/src/scale/glyf/{glyph.rs => outline.rs} (92%) delete mode 100644 skrifa/src/scale/glyf/scaler.rs diff --git a/skrifa/src/scale/glyf/mem.rs b/skrifa/src/scale/glyf/mem.rs index 666ddb853..30d6a4b05 100644 --- a/skrifa/src/scale/glyf/mem.rs +++ b/skrifa/src/scale/glyf/mem.rs @@ -7,10 +7,10 @@ use read_fonts::{ types::{F26Dot6, Fixed, Point}, }; -use super::ScalerGlyph; +use super::Outline; /// Buffers used during glyph scaling. -pub struct ScalerMemory<'a> { +pub struct OutlineMemory<'a> { pub unscaled: &'a mut [Point], pub scaled: &'a mut [Point], pub original_scaled: &'a mut [Point], @@ -21,21 +21,21 @@ pub struct ScalerMemory<'a> { pub composite_deltas: &'a mut [Point], } -impl<'a> ScalerMemory<'a> { - pub(super) fn new(glyph: &ScalerGlyph, buf: &'a mut [u8], with_hinting: bool) -> Option { - let (scaled, buf) = alloc_slice(buf, glyph.points)?; - let (unscaled, buf) = alloc_slice(buf, glyph.max_other_points)?; +impl<'a> OutlineMemory<'a> { + pub(super) fn new(outline: &Outline, buf: &'a mut [u8], with_hinting: bool) -> Option { + let (scaled, buf) = alloc_slice(buf, outline.points)?; + let (unscaled, buf) = alloc_slice(buf, outline.max_other_points)?; // We only need original scaled points when hinting - let (original_scaled, buf) = if glyph.has_hinting && with_hinting { - alloc_slice(buf, glyph.max_other_points)? + let (original_scaled, buf) = if outline.has_hinting && with_hinting { + alloc_slice(buf, outline.max_other_points)? } else { (Default::default(), buf) }; // Don't allocate any delta buffers if we don't have variations - let (deltas, iup_buffer, composite_deltas, buf) = if glyph.has_variations { - let (deltas, buf) = alloc_slice(buf, glyph.max_simple_points)?; - let (iup_buffer, buf) = alloc_slice(buf, glyph.max_simple_points)?; - let (composite_deltas, buf) = alloc_slice(buf, glyph.max_component_delta_stack)?; + let (deltas, iup_buffer, composite_deltas, buf) = if outline.has_variations { + let (deltas, buf) = alloc_slice(buf, outline.max_simple_points)?; + let (iup_buffer, buf) = alloc_slice(buf, outline.max_simple_points)?; + let (composite_deltas, buf) = alloc_slice(buf, outline.max_component_delta_stack)?; (deltas, iup_buffer, composite_deltas, buf) } else { ( @@ -45,8 +45,8 @@ impl<'a> ScalerMemory<'a> { buf, ) }; - let (contours, buf) = alloc_slice(buf, glyph.contours)?; - let (flags, _) = alloc_slice(buf, glyph.points)?; + let (contours, buf) = alloc_slice(buf, outline.contours)?; + let (flags, _) = alloc_slice(buf, outline.points)?; Some(Self { unscaled, scaled, @@ -145,7 +145,7 @@ mod tests { #[test] fn outline_memory() { - let outline_info = ScalerGlyph { + let outline_info = Outline { glyph: None, glyph_id: Default::default(), points: 10, @@ -159,7 +159,7 @@ mod tests { }; let required_size = outline_info.required_buffer_size(false); let mut buf = vec![0u8; required_size]; - let memory = ScalerMemory::new(&outline_info, &mut buf, false).unwrap(); + let memory = OutlineMemory::new(&outline_info, &mut buf, false).unwrap(); assert_eq!(memory.scaled.len(), outline_info.points); assert_eq!(memory.unscaled.len(), outline_info.max_other_points); // We don't allocate this buffer when hinting is disabled @@ -176,7 +176,7 @@ mod tests { #[test] fn fail_outline_memory() { - let outline_info = ScalerGlyph { + let outline_info = Outline { glyph: None, glyph_id: Default::default(), points: 10, @@ -192,6 +192,6 @@ mod tests { // requirements. So subtract 5 to force a failure. let not_enough = outline_info.required_buffer_size(false) - 5; let mut buf = vec![0u8; not_enough]; - assert!(ScalerMemory::new(&outline_info, &mut buf, false).is_none()); + assert!(OutlineMemory::new(&outline_info, &mut buf, false).is_none()); } } diff --git a/skrifa/src/scale/glyf/mod.rs b/skrifa/src/scale/glyf/mod.rs index e94881abe..a48f37e54 100644 --- a/skrifa/src/scale/glyf/mod.rs +++ b/skrifa/src/scale/glyf/mod.rs @@ -1,18 +1,30 @@ //! Scaling support for TrueType outlines. mod deltas; -mod glyph; mod hint; mod mem; -mod scaler; +mod outline; -pub use glyph::{ScalerGlyph, ScalerOutline}; pub use hint::HinterOutline; -pub use mem::ScalerMemory; -pub use scaler::Scaler; +pub use mem::OutlineMemory; +pub use outline::{Outline, ScaledOutline}; use super::Error; +use read_fonts::{ + tables::{ + glyf::{ + Anchor, CompositeGlyph, CompositeGlyphFlags, Glyf, Glyph, PointMarker, SimpleGlyph, + }, + gvar::Gvar, + hmtx::Hmtx, + hvar::Hvar, + loca::Loca, + }, + types::{BigEndian, F26Dot6, F2Dot14, Fixed, GlyphId, Point, Tag}, + TableProvider, +}; + /// Recursion limit for processing composite outlines. /// /// In reality, most fonts contain shallow composite graphs with a nesting @@ -22,3 +34,756 @@ pub const COMPOSITE_RECURSION_LIMIT: usize = 32; /// Number of phantom points generated at the end of an outline. pub const PHANTOM_POINT_COUNT: usize = 4; + +/// Scaler state for TrueType outlines. +#[derive(Clone)] +pub struct Outlines<'a> { + loca: Loca<'a>, + glyf: Glyf<'a>, + gvar: Option>, + hmtx: Hmtx<'a>, + hvar: Option>, + fpgm: &'a [u8], + prep: &'a [u8], + cvt: &'a [BigEndian], + units_per_em: u16, + has_var_lsb: bool, +} + +impl<'a> Outlines<'a> { + pub fn new(font: &impl TableProvider<'a>) -> Option { + let hvar = font.hvar().ok(); + let has_var_lsb = hvar + .as_ref() + .map(|hvar| hvar.lsb_mapping().is_some()) + .unwrap_or_default(); + Some(Self { + loca: font.loca(None).ok()?, + glyf: font.glyf().ok()?, + gvar: font.gvar().ok(), + hmtx: font.hmtx().ok()?, + hvar, + fpgm: font + .data_for_tag(Tag::new(b"fpgm")) + .unwrap_or_default() + .as_bytes(), + prep: font + .data_for_tag(Tag::new(b"prep")) + .unwrap_or_default() + .as_bytes(), + cvt: font + .data_for_tag(Tag::new(b"cvt ")) + .and_then(|d| d.read_array(0..d.len()).ok()) + .unwrap_or_default(), + units_per_em: font.head().ok()?.units_per_em(), + has_var_lsb, + }) + } + + pub fn outline(&self, glyph_id: GlyphId) -> Result { + let mut info = Outline { + glyph_id, + has_variations: self.gvar.is_some(), + ..Default::default() + }; + let glyph = self.loca.get_glyf(glyph_id, &self.glyf)?; + if glyph.is_none() { + return Ok(info); + } + self.glyph_rec(glyph.as_ref().unwrap(), &mut info, 0, 0)?; + if info.points != 0 { + info.points += PHANTOM_POINT_COUNT; + } + info.glyph = glyph; + Ok(info) + } + + pub fn scale( + &self, + memory: OutlineMemory<'a>, + info: &Outline, + size: f32, + coords: &'a [F2Dot14], + ) -> Result, Error> { + Scaler::new(self.clone(), memory, size, coords, |_| true, false) + .scale(&info.glyph, info.glyph_id) + } + + pub fn scale_hinted( + &self, + memory: OutlineMemory<'a>, + info: &Outline, + size: f32, + coords: &'a [F2Dot14], + hint_fn: impl FnMut(HinterOutline) -> bool, + ) -> Result, Error> { + Scaler::new(self.clone(), memory, size, coords, hint_fn, false) + .scale(&info.glyph, info.glyph_id) + } +} + +impl<'a> Outlines<'a> { + fn glyph_rec( + &self, + glyph: &Glyph, + info: &mut Outline, + component_depth: usize, + recurse_depth: usize, + ) -> Result<(), Error> { + if recurse_depth > COMPOSITE_RECURSION_LIMIT { + return Err(Error::RecursionLimitExceeded(info.glyph_id)); + } + match glyph { + Glyph::Simple(simple) => { + let num_points = simple.num_points(); + let num_points_with_phantom = num_points + PHANTOM_POINT_COUNT; + info.max_simple_points = info.max_simple_points.max(num_points_with_phantom); + info.points += num_points; + info.contours += simple.end_pts_of_contours().len(); + info.has_hinting = info.has_hinting || simple.instruction_length() != 0; + info.max_other_points = info.max_other_points.max(num_points_with_phantom); + info.has_overlaps |= simple.has_overlapping_contours(); + } + Glyph::Composite(composite) => { + let (mut count, instructions) = composite.count_and_instructions(); + count += PHANTOM_POINT_COUNT; + let point_base = info.points; + for (component, flags) in composite.component_glyphs_and_flags() { + info.has_overlaps |= flags.contains(CompositeGlyphFlags::OVERLAP_COMPOUND); + let component_glyph = self.loca.get_glyf(component, &self.glyf)?; + let Some(component_glyph) = component_glyph else { + continue; + }; + self.glyph_rec( + &component_glyph, + info, + component_depth + count, + recurse_depth + 1, + )?; + } + let has_hinting = !instructions.unwrap_or_default().is_empty(); + if has_hinting { + // We only need the "other points" buffers if the + // composite glyph has instructions. + let num_points_in_composite = info.points - point_base + PHANTOM_POINT_COUNT; + info.max_other_points = info.max_other_points.max(num_points_in_composite); + } + info.max_component_delta_stack = + info.max_component_delta_stack.max(component_depth + count); + info.has_hinting = info.has_hinting || has_hinting; + } + } + Ok(()) + } + + fn advance_width(&self, gid: GlyphId, coords: &'a [F2Dot14]) -> i32 { + let default_advance = self + .hmtx + .h_metrics() + .last() + .map(|metric| metric.advance()) + .unwrap_or(0); + let mut advance = self + .hmtx + .h_metrics() + .get(gid.to_u16() as usize) + .map(|metric| metric.advance()) + .unwrap_or(default_advance) as i32; + if let Some(hvar) = &self.hvar { + advance += hvar + .advance_width_delta(gid, coords) + // FreeType truncates metric deltas... + .map(|delta| delta.to_f64() as i32) + .unwrap_or(0); + } + advance + } + + fn lsb(&self, gid: GlyphId, coords: &'a [F2Dot14]) -> i32 { + let gid_index = gid.to_u16() as usize; + let mut lsb = self + .hmtx + .h_metrics() + .get(gid_index) + .map(|metric| metric.side_bearing()) + .unwrap_or_else(|| { + self.hmtx + .left_side_bearings() + .get(gid_index.saturating_sub(self.hmtx.h_metrics().len())) + .map(|lsb| lsb.get()) + .unwrap_or(0) + }) as i32; + if let Some(hvar) = &self.hvar { + lsb += hvar + .lsb_delta(gid, coords) + // FreeType truncates metric deltas... + .map(|delta| delta.to_f64() as i32) + .unwrap_or(0); + } + lsb + } +} + +struct Scaler<'a, H> { + outlines: Outlines<'a>, + memory: OutlineMemory<'a>, + coords: &'a [F2Dot14], + point_count: usize, + contour_count: usize, + component_delta_count: usize, + scale: F26Dot6, + is_scaled: bool, + is_hinted: bool, + /// Phantom points. These are 4 extra points appended to the end of an + /// outline that allow the bytecode interpreter to produce hinted + /// metrics. + /// + /// See + phantom: [Point; PHANTOM_POINT_COUNT], + hint_fn: H, +} + +impl<'a, H> Scaler<'a, H> +where + H: FnMut(HinterOutline) -> bool, +{ + fn new( + outlines: Outlines<'a>, + memory: OutlineMemory<'a>, + size: f32, + coords: &'a [F2Dot14], + hint_fn: H, + is_hinted: bool, + ) -> Self { + let (is_scaled, scale) = if size > 0.0 && outlines.units_per_em > 0 { + ( + true, + F26Dot6::from_bits((size * 64.) as i32) + / F26Dot6::from_bits(outlines.units_per_em as i32), + ) + } else { + (false, F26Dot6::from_bits(0x10000)) + }; + Self { + outlines, + memory, + coords, + point_count: 0, + contour_count: 0, + component_delta_count: 0, + scale, + is_scaled, + // We don't hint unscaled outlines + is_hinted: is_hinted && is_scaled, + phantom: Default::default(), + hint_fn, + } + } + + fn scale( + mut self, + glyph: &Option, + glyph_id: GlyphId, + ) -> Result, Error> { + self.load(glyph, glyph_id, 0)?; + let outline = ScaledOutline { + points: &mut self.memory.scaled[..self.point_count], + flags: &mut self.memory.flags[..self.point_count], + contours: &mut self.memory.contours[..self.contour_count], + }; + let x_shift = self.phantom[0].x; + if x_shift != F26Dot6::ZERO { + for point in outline.points.iter_mut() { + point.x -= x_shift; + } + } + Ok(outline) + } + + fn load( + &mut self, + glyph: &Option, + glyph_id: GlyphId, + recurse_depth: usize, + ) -> Result<(), Error> { + if recurse_depth > COMPOSITE_RECURSION_LIMIT { + return Err(Error::RecursionLimitExceeded(glyph_id)); + } + let glyph = match &glyph { + Some(glyph) => glyph, + // This is a valid empty glyph + None => return Ok(()), + }; + let bounds = [glyph.x_min(), glyph.x_max(), glyph.y_min(), glyph.y_max()]; + self.setup_phantom_points(bounds, glyph_id); + match glyph { + Glyph::Simple(simple) => self.load_simple(simple, glyph_id), + Glyph::Composite(composite) => self.load_composite(composite, glyph_id, recurse_depth), + } + } + + fn load_simple(&mut self, glyph: &SimpleGlyph, glyph_id: GlyphId) -> Result<(), Error> { + use Error::InsufficientMemory; + // Compute the ranges for our point/flag buffers and slice them. + let points_start = self.point_count; + let point_count = glyph.num_points(); + let phantom_start = point_count; + let points_end = points_start + point_count + PHANTOM_POINT_COUNT; + let point_range = points_start..points_end; + let other_points_end = point_count + PHANTOM_POINT_COUNT; + // Scaled points and flags are accumulated as we load the outline. + let scaled = self + .memory + .scaled + .get_mut(point_range.clone()) + .ok_or(InsufficientMemory)?; + let flags = self + .memory + .flags + .get_mut(point_range) + .ok_or(InsufficientMemory)?; + // Unscaled points are temporary and are allocated as needed. We only + // ever need one copy in memory for any simple or composite glyph so + // allocate from the base of the buffer. + let unscaled = self + .memory + .unscaled + .get_mut(..other_points_end) + .ok_or(InsufficientMemory)?; + // Read our unscaled points and flags (up to point_count which does not + // include phantom points). + glyph.read_points_fast(&mut unscaled[..point_count], &mut flags[..point_count])?; + // Compute the range for our contour end point buffer and slice it. + let contours_start = self.contour_count; + let contour_end_pts = glyph.end_pts_of_contours(); + let contour_count = contour_end_pts.len(); + let contours_end = contours_start + contour_count; + let contours = self + .memory + .contours + .get_mut(contours_start..contours_end) + .ok_or(InsufficientMemory)?; + // Read the contour end points. + for (end_pt, contour) in contour_end_pts.iter().zip(contours.iter_mut()) { + *contour = end_pt.get(); + } + // Adjust the running point/contour total counts + self.point_count += point_count; + self.contour_count += contour_count; + // Append phantom points to the outline. + for (i, phantom) in self.phantom.iter().enumerate() { + unscaled[phantom_start + i] = phantom.map(|x| x.to_bits()); + flags[phantom_start + i] = Default::default(); + } + let mut have_deltas = false; + if self.outlines.gvar.is_some() && !self.coords.is_empty() { + let gvar = self.outlines.gvar.as_ref().unwrap(); + let glyph = deltas::SimpleGlyph { + points: &mut unscaled[..], + flags: &mut flags[..], + contours, + }; + let deltas = self + .memory + .deltas + .get_mut(..point_count + PHANTOM_POINT_COUNT) + .ok_or(InsufficientMemory)?; + let iup_buffer = self + .memory + .iup_buffer + .get_mut(..point_count + PHANTOM_POINT_COUNT) + .ok_or(InsufficientMemory)?; + if deltas::simple_glyph( + gvar, + glyph_id, + self.coords, + self.outlines.has_var_lsb, + glyph, + iup_buffer, + deltas, + ) + .is_ok() + { + have_deltas = true; + } + } + let ins = glyph.instructions(); + let is_hinted = self.is_hinted && !ins.is_empty(); + if self.is_scaled { + let scale = self.scale; + if have_deltas { + for ((point, unscaled), delta) in scaled + .iter_mut() + .zip(unscaled.iter_mut()) + .zip(self.memory.deltas.iter()) + { + let delta = delta.map(Fixed::to_f26dot6); + let scaled = (unscaled.map(F26Dot6::from_i32) + delta) * scale; + // The computed scale factor has an i32 -> 26.26 conversion built in. This undoes the + // extra shift. + *point = scaled.map(|v| F26Dot6::from_bits(v.to_i32())); + } + if is_hinted { + // For hinting, we need to adjust the unscaled points as well. + // Round off deltas for unscaled outlines. + for (unscaled, delta) in unscaled.iter_mut().zip(self.memory.deltas.iter()) { + *unscaled += delta.map(Fixed::to_i32); + } + } + } else { + for (point, unscaled) in scaled.iter_mut().zip(unscaled.iter_mut()) { + *point = unscaled.map(|v| F26Dot6::from_bits(v) * scale); + } + } + } else { + if have_deltas { + // Round off deltas for unscaled outlines. + for (unscaled, delta) in unscaled.iter_mut().zip(self.memory.deltas.iter()) { + *unscaled += delta.map(Fixed::to_i32); + } + } + // Unlike FreeType, we also store unscaled outlines in 26.6. + for (point, unscaled) in scaled.iter_mut().zip(unscaled.iter()) { + *point = unscaled.map(F26Dot6::from_i32); + } + } + // Commit our potentially modified phantom points. + for (i, point) in scaled[phantom_start..] + .iter() + .enumerate() + .take(PHANTOM_POINT_COUNT) + { + self.phantom[i] = *point; + } + if is_hinted { + // Create a copy of our scaled points in original_scaled. + let original_scaled = self + .memory + .original_scaled + .get_mut(..other_points_end) + .ok_or(InsufficientMemory)?; + original_scaled.copy_from_slice(scaled); + // When hinting, round the phantom points. + for point in &mut scaled[phantom_start..] { + point.x = point.x.round(); + point.y = point.y.round(); + } + if !(self.hint_fn)(HinterOutline { + unscaled, + scaled, + original_scaled, + flags, + contours, + bytecode: ins, + phantom: &mut self.phantom, + is_composite: false, + coords: self.coords, + }) { + return Err(Error::HintingFailed(glyph_id)); + } + } + if points_start != 0 { + // If we're not the first component, shift our contour end points. + for contour_end in contours.iter_mut() { + *contour_end += points_start as u16; + } + } + Ok(()) + } + + fn load_composite( + &mut self, + glyph: &CompositeGlyph, + glyph_id: GlyphId, + recurse_depth: usize, + ) -> Result<(), Error> { + use Error::InsufficientMemory; + let scale = self.scale; + // The base indices of the points and contours for the current glyph. + let point_base = self.point_count; + let contour_base = self.contour_count; + // Compute the per component deltas. Since composites can be nested, we + // use a stack and keep track of the base. + let mut have_deltas = false; + let delta_base = self.component_delta_count; + if self.outlines.gvar.is_some() && !self.coords.is_empty() { + let gvar = self.outlines.gvar.as_ref().unwrap(); + let count = glyph.components().count() + PHANTOM_POINT_COUNT; + let deltas = self + .memory + .composite_deltas + .get_mut(delta_base..delta_base + count) + .ok_or(InsufficientMemory)?; + if deltas::composite_glyph(gvar, glyph_id, self.coords, &mut deltas[..]).is_ok() { + // If the font is missing variation data for LSBs in HVAR then we + // apply the delta to the first phantom point. + if !self.outlines.has_var_lsb { + self.phantom[0].x += F26Dot6::from_bits(deltas[count - 4].x.to_i32()); + } + have_deltas = true; + } + self.component_delta_count += count; + } + if self.is_scaled { + for point in self.phantom.iter_mut() { + *point *= scale; + } + } else { + for point in self.phantom.iter_mut() { + *point = point.map(|x| F26Dot6::from_i32(x.to_bits())); + } + } + for (i, component) in glyph.components().enumerate() { + // Loading a component glyph will override phantom points so save a copy. We'll + // restore them unless the USE_MY_METRICS flag is set. + let phantom = self.phantom; + // Load the component glyph and keep track of the points range. + let start_point = self.point_count; + let component_glyph = self + .outlines + .loca + .get_glyf(component.glyph, &self.outlines.glyf)?; + self.load(&component_glyph, component.glyph, recurse_depth + 1)?; + let end_point = self.point_count; + if !component + .flags + .contains(CompositeGlyphFlags::USE_MY_METRICS) + { + // If the USE_MY_METRICS flag is missing, we restore the phantom points we + // saved at the start of the loop. + self.phantom = phantom; + } + // Prepares the transform components for our conversion math below. + fn scale_component(x: F2Dot14) -> F26Dot6 { + F26Dot6::from_bits(x.to_bits() as i32 * 4) + } + let xform = &component.transform; + let xx = scale_component(xform.xx); + let yx = scale_component(xform.yx); + let xy = scale_component(xform.xy); + let yy = scale_component(xform.yy); + let have_xform = component.flags.intersects( + CompositeGlyphFlags::WE_HAVE_A_SCALE + | CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE + | CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO, + ); + if have_xform { + let scaled = &mut self.memory.scaled[start_point..end_point]; + if self.is_scaled { + for point in scaled { + let x = point.x * xx + point.y * xy; + let y = point.x * yx + point.y * yy; + point.x = x; + point.y = y; + } + } else { + for point in scaled { + // This juggling is necessary because, unlike FreeType, we also + // return unscaled outlines in 26.6 format for a consistent interface. + let unscaled = point.map(|c| F26Dot6::from_bits(c.to_i32())); + let x = unscaled.x * xx + unscaled.y * xy; + let y = unscaled.x * yx + unscaled.y * yy; + *point = Point::new(x, y).map(|c| F26Dot6::from_i32(c.to_bits())); + } + } + } + let anchor_offset = match component.anchor { + Anchor::Offset { x, y } => { + let (mut x, mut y) = (x as i32, y as i32); + if have_xform + && component.flags + & (CompositeGlyphFlags::SCALED_COMPONENT_OFFSET + | CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET) + == CompositeGlyphFlags::SCALED_COMPONENT_OFFSET + { + // According to FreeType, this algorithm is a "guess" + // and works better than the one documented by Apple. + // https://github.com/freetype/freetype/blob/b1c90733ee6a04882b133101d61b12e352eeb290/src/truetype/ttgload.c#L1259 + fn hypot(a: F26Dot6, b: F26Dot6) -> Fixed { + let a = a.to_bits().abs(); + let b = b.to_bits().abs(); + Fixed::from_bits(if a > b { + a + ((3 * b) >> 3) + } else { + b + ((3 * a) >> 3) + }) + } + // FreeType uses a fixed point multiplication here. + x = (Fixed::from_bits(x) * hypot(xx, xy)).to_bits(); + y = (Fixed::from_bits(y) * hypot(yy, yx)).to_bits(); + } + if have_deltas { + let delta = self + .memory + .composite_deltas + .get(delta_base + i) + .copied() + .unwrap_or_default(); + // For composite glyphs, we copy FreeType and round off + // the fractional parts of deltas. + x += delta.x.to_i32(); + y += delta.y.to_i32(); + } + if self.is_scaled { + let mut offset = Point::new(x, y).map(F26Dot6::from_bits) * scale; + if self.is_hinted + && component + .flags + .contains(CompositeGlyphFlags::ROUND_XY_TO_GRID) + { + // Only round the y-coordinate, per FreeType. + offset.y = offset.y.round(); + } + offset + } else { + Point::new(x, y).map(F26Dot6::from_i32) + } + } + Anchor::Point { base, component } => { + let (base_offset, component_offset) = (base as usize, component as usize); + let base_point = self + .memory + .scaled + .get(point_base + base_offset) + .ok_or(Error::InvalidAnchorPoint(glyph_id, base))?; + let component_point = self + .memory + .scaled + .get(start_point + component_offset) + .ok_or(Error::InvalidAnchorPoint(glyph_id, component))?; + *base_point - *component_point + } + }; + if anchor_offset.x != F26Dot6::ZERO || anchor_offset.y != F26Dot6::ZERO { + for point in &mut self.memory.scaled[start_point..end_point] { + *point += anchor_offset; + } + } + } + if have_deltas { + self.component_delta_count = delta_base; + } + if self.is_hinted { + let ins = glyph.instructions().unwrap_or_default(); + if !ins.is_empty() { + let start_point = point_base; + let end_point = self.point_count + PHANTOM_POINT_COUNT; + let point_range = start_point..end_point; + let scaled = &mut self.memory.scaled[point_range.clone()]; + let flags = self + .memory + .flags + .get_mut(point_range.clone()) + .ok_or(InsufficientMemory)?; + // Append the current phantom points to the outline. + let phantom_start = self.point_count; + for (i, phantom) in self.phantom.iter().enumerate() { + scaled[phantom_start + i] = *phantom; + flags[phantom_start + i] = Default::default(); + } + // For composite glyphs, the unscaled and original points are + // simply copies of the current point set. + let other_points_end = point_range.len(); + let unscaled = self + .memory + .unscaled + .get_mut(..other_points_end) + .ok_or(InsufficientMemory)?; + for (scaled, unscaled) in scaled.iter().zip(unscaled.iter_mut()) { + *unscaled = scaled.map(|x| x.to_bits()); + } + let original_scaled = self + .memory + .original_scaled + .get_mut(..other_points_end) + .ok_or(InsufficientMemory)?; + original_scaled.copy_from_slice(scaled); + let contours = self + .memory + .contours + .get_mut(contour_base..self.contour_count) + .ok_or(InsufficientMemory)?; + // Round the phantom points. + for p in &mut scaled[self.point_count..] { + p.x = p.x.round(); + p.y = p.y.round(); + } + // Clear the "touched" flags that are used during IUP processing. + for flag in flags.iter_mut() { + flag.clear_marker(PointMarker::TOUCHED); + } + // Make sure our contour end points accurately reflect the + // outline slices. + if point_base != 0 { + let delta = point_base as u16; + for contour in contours.iter_mut() { + *contour -= delta; + } + } + if !(self.hint_fn)(HinterOutline { + unscaled, + scaled, + original_scaled, + flags, + contours, + bytecode: ins, + phantom: &mut self.phantom, + is_composite: true, + coords: self.coords, + }) { + return Err(Error::HintingFailed(glyph_id)); + } + // Undo the contour shifts if we applied them above. + if point_base != 0 { + let delta = point_base as u16; + for contour in contours.iter_mut() { + *contour += delta; + } + } + } + } + Ok(()) + } +} + +impl<'a, H> Scaler<'a, H> { + fn setup_phantom_points(&mut self, bounds: [i16; 4], glyph_id: GlyphId) { + let lsb = self.outlines.lsb(glyph_id, self.coords); + let advance = self.outlines.advance_width(glyph_id, self.coords); + // Vertical metrics aren't significant to the glyph loading process, so + // they are ignored. + let vadvance = 0; + let tsb = 0; + // The four "phantom" points as computed by FreeType. + self.phantom[0].x = F26Dot6::from_bits(bounds[0] as i32 - lsb); + self.phantom[0].y = F26Dot6::ZERO; + self.phantom[1].x = self.phantom[0].x + F26Dot6::from_bits(advance); + self.phantom[1].y = F26Dot6::ZERO; + self.phantom[2].x = F26Dot6::from_bits(advance / 2); + self.phantom[2].y = F26Dot6::from_bits(bounds[3] as i32 + tsb); + self.phantom[3].x = F26Dot6::from_bits(advance / 2); + self.phantom[3].y = self.phantom[2].y - F26Dot6::from_bits(vadvance); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use read_fonts::{FontRef, TableProvider}; + + #[test] + fn overlap_flags() { + let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap(); + let scaler = Outlines::new(&font).unwrap(); + let glyph_count = font.maxp().unwrap().num_glyphs(); + // GID 2 is a composite glyph with the overlap bit on a component + // GID 3 is a simple glyph with the overlap bit on the first flag + let expected_gids_with_overlap = vec![2, 3]; + assert_eq!( + expected_gids_with_overlap, + (0..glyph_count) + .filter(|gid| scaler.outline(GlyphId::new(*gid)).unwrap().has_overlaps) + .collect::>() + ); + } +} diff --git a/skrifa/src/scale/glyf/glyph.rs b/skrifa/src/scale/glyf/outline.rs similarity index 92% rename from skrifa/src/scale/glyf/glyph.rs rename to skrifa/src/scale/glyf/outline.rs index 6ea01a794..e36e4f76b 100644 --- a/skrifa/src/scale/glyf/glyph.rs +++ b/skrifa/src/scale/glyf/outline.rs @@ -7,14 +7,14 @@ use read_fonts::{ types::{F26Dot6, Fixed, GlyphId, Pen, Point}, }; -use super::ScalerMemory; +use super::OutlineMemory; -/// Represents the information necessary to scale a glyph. +/// Represents the information necessary to scale a glyph outline. /// /// Contains a reference to the glyph data itself as well as metrics that /// can be used to compute the memory requirements for scaling the glyph. #[derive(Default)] -pub struct ScalerGlyph<'a> { +pub struct Outline<'a> { pub glyph_id: GlyphId, /// The associated top-level glyph for the outline. pub glyph: Option>, @@ -45,7 +45,7 @@ pub struct ScalerGlyph<'a> { pub has_overlaps: bool, } -impl<'a> ScalerGlyph<'a> { +impl<'a> Outline<'a> { /// Returns the minimum size in bytes required to scale an outline based /// on the computed sizes. pub fn required_buffer_size(&self, with_hinting: bool) -> usize { @@ -83,19 +83,19 @@ impl<'a> ScalerGlyph<'a> { &self, buf: &'a mut [u8], with_hinting: bool, - ) -> Option> { - ScalerMemory::new(self, buf, with_hinting) + ) -> Option> { + OutlineMemory::new(self, buf, with_hinting) } } #[derive(Debug)] -pub struct ScalerOutline<'a> { +pub struct ScaledOutline<'a> { pub points: &'a mut [Point], pub flags: &'a mut [PointFlags], pub contours: &'a mut [u16], } -impl<'a> ScalerOutline<'a> { +impl<'a> ScaledOutline<'a> { pub fn to_path(&self, pen: &mut impl Pen) -> Result<(), ToPathError> { to_path(self.points, self.flags, self.contours, pen) } diff --git a/skrifa/src/scale/glyf/scaler.rs b/skrifa/src/scale/glyf/scaler.rs deleted file mode 100644 index a0d6e1c8d..000000000 --- a/skrifa/src/scale/glyf/scaler.rs +++ /dev/null @@ -1,773 +0,0 @@ -//! Scaler for TrueType outlines. - -use read_fonts::{ - tables::{ - glyf::{ - Anchor, CompositeGlyph, CompositeGlyphFlags, Glyf, Glyph, PointMarker, SimpleGlyph, - }, - gvar::Gvar, - hmtx::Hmtx, - hvar::Hvar, - loca::Loca, - }, - types::{BigEndian, F26Dot6, F2Dot14, Fixed, GlyphId, Point, Tag}, - TableProvider, -}; - -use super::{ - deltas, Error, HinterOutline, ScalerGlyph, ScalerMemory, ScalerOutline, - COMPOSITE_RECURSION_LIMIT, PHANTOM_POINT_COUNT, -}; - -/// Scaler state for TrueType outlines. -#[derive(Clone)] -pub struct Scaler<'a> { - loca: Loca<'a>, - glyf: Glyf<'a>, - gvar: Option>, - hmtx: Hmtx<'a>, - hvar: Option>, - fpgm: &'a [u8], - prep: &'a [u8], - cvt: &'a [BigEndian], - units_per_em: u16, - has_var_lsb: bool, -} - -impl<'a> Scaler<'a> { - pub fn new(font: &impl TableProvider<'a>) -> Option { - let hvar = font.hvar().ok(); - let has_var_lsb = hvar - .as_ref() - .map(|hvar| hvar.lsb_mapping().is_some()) - .unwrap_or_default(); - Some(Self { - loca: font.loca(None).ok()?, - glyf: font.glyf().ok()?, - gvar: font.gvar().ok(), - hmtx: font.hmtx().ok()?, - hvar, - fpgm: font - .data_for_tag(Tag::new(b"fpgm")) - .unwrap_or_default() - .as_bytes(), - prep: font - .data_for_tag(Tag::new(b"prep")) - .unwrap_or_default() - .as_bytes(), - cvt: font - .data_for_tag(Tag::new(b"cvt ")) - .and_then(|d| d.read_array(0..d.len()).ok()) - .unwrap_or_default(), - units_per_em: font.head().ok()?.units_per_em(), - has_var_lsb, - }) - } - - pub fn glyph(&self, glyph_id: GlyphId) -> Result { - let mut info = ScalerGlyph { - glyph_id, - has_variations: self.gvar.is_some(), - ..Default::default() - }; - let glyph = self.loca.get_glyf(glyph_id, &self.glyf)?; - if glyph.is_none() { - return Ok(info); - } - self.glyph_rec(glyph.as_ref().unwrap(), &mut info, 0, 0)?; - if info.points != 0 { - info.points += PHANTOM_POINT_COUNT; - } - info.glyph = glyph; - Ok(info) - } - - pub fn outline( - &self, - memory: ScalerMemory<'a>, - info: &ScalerGlyph, - size: f32, - coords: &'a [F2Dot14], - ) -> Result, Error> { - ScalerInstance::new(self.clone(), memory, size, coords, |_| true, false) - .outline(&info.glyph, info.glyph_id) - } - - pub fn hinted_outline( - &self, - memory: ScalerMemory<'a>, - info: &ScalerGlyph, - size: f32, - coords: &'a [F2Dot14], - hint_fn: impl FnMut(HinterOutline) -> bool, - ) -> Result, Error> { - ScalerInstance::new(self.clone(), memory, size, coords, hint_fn, false) - .outline(&info.glyph, info.glyph_id) - } -} - -impl<'a> Scaler<'a> { - fn glyph_rec( - &self, - glyph: &Glyph, - info: &mut ScalerGlyph, - component_depth: usize, - recurse_depth: usize, - ) -> Result<(), Error> { - if recurse_depth > COMPOSITE_RECURSION_LIMIT { - return Err(Error::RecursionLimitExceeded(info.glyph_id)); - } - match glyph { - Glyph::Simple(simple) => { - let num_points = simple.num_points(); - let num_points_with_phantom = num_points + PHANTOM_POINT_COUNT; - info.max_simple_points = info.max_simple_points.max(num_points_with_phantom); - info.points += num_points; - info.contours += simple.end_pts_of_contours().len(); - info.has_hinting = info.has_hinting || simple.instruction_length() != 0; - info.max_other_points = info.max_other_points.max(num_points_with_phantom); - info.has_overlaps |= simple.has_overlapping_contours(); - } - Glyph::Composite(composite) => { - let (mut count, instructions) = composite.count_and_instructions(); - count += PHANTOM_POINT_COUNT; - let point_base = info.points; - for (component, flags) in composite.component_glyphs_and_flags() { - info.has_overlaps |= flags.contains(CompositeGlyphFlags::OVERLAP_COMPOUND); - let component_glyph = self.loca.get_glyf(component, &self.glyf)?; - let Some(component_glyph) = component_glyph else { - continue; - }; - self.glyph_rec( - &component_glyph, - info, - component_depth + count, - recurse_depth + 1, - )?; - } - let has_hinting = !instructions.unwrap_or_default().is_empty(); - if has_hinting { - // We only need the "other points" buffers if the - // composite glyph has instructions. - let num_points_in_composite = info.points - point_base + PHANTOM_POINT_COUNT; - info.max_other_points = info.max_other_points.max(num_points_in_composite); - } - info.max_component_delta_stack = - info.max_component_delta_stack.max(component_depth + count); - info.has_hinting = info.has_hinting || has_hinting; - } - } - Ok(()) - } - - fn advance_width(&self, gid: GlyphId, coords: &'a [F2Dot14]) -> i32 { - let default_advance = self - .hmtx - .h_metrics() - .last() - .map(|metric| metric.advance()) - .unwrap_or(0); - let mut advance = self - .hmtx - .h_metrics() - .get(gid.to_u16() as usize) - .map(|metric| metric.advance()) - .unwrap_or(default_advance) as i32; - if let Some(hvar) = &self.hvar { - advance += hvar - .advance_width_delta(gid, coords) - // FreeType truncates metric deltas... - .map(|delta| delta.to_f64() as i32) - .unwrap_or(0); - } - advance - } - - fn lsb(&self, gid: GlyphId, coords: &'a [F2Dot14]) -> i32 { - let gid_index = gid.to_u16() as usize; - let mut lsb = self - .hmtx - .h_metrics() - .get(gid_index) - .map(|metric| metric.side_bearing()) - .unwrap_or_else(|| { - self.hmtx - .left_side_bearings() - .get(gid_index.saturating_sub(self.hmtx.h_metrics().len())) - .map(|lsb| lsb.get()) - .unwrap_or(0) - }) as i32; - if let Some(hvar) = &self.hvar { - lsb += hvar - .lsb_delta(gid, coords) - // FreeType truncates metric deltas... - .map(|delta| delta.to_f64() as i32) - .unwrap_or(0); - } - lsb - } -} - -struct ScalerInstance<'a, H> { - scaler: Scaler<'a>, - memory: ScalerMemory<'a>, - coords: &'a [F2Dot14], - point_count: usize, - contour_count: usize, - component_delta_count: usize, - scale: F26Dot6, - is_scaled: bool, - is_hinted: bool, - /// Phantom points. These are 4 extra points appended to the end of an - /// outline that allow the bytecode interpreter to produce hinted - /// metrics. - /// - /// See - phantom: [Point; PHANTOM_POINT_COUNT], - hint_fn: H, -} - -impl<'a, H> ScalerInstance<'a, H> -where - H: FnMut(HinterOutline) -> bool, -{ - fn new( - scaler: Scaler<'a>, - memory: ScalerMemory<'a>, - size: f32, - coords: &'a [F2Dot14], - hint_fn: H, - is_hinted: bool, - ) -> Self { - let (is_scaled, scale) = if size > 0.0 && scaler.units_per_em > 0 { - ( - true, - F26Dot6::from_bits((size * 64.) as i32) - / F26Dot6::from_bits(scaler.units_per_em as i32), - ) - } else { - (false, F26Dot6::from_bits(0x10000)) - }; - Self { - scaler, - memory, - coords, - point_count: 0, - contour_count: 0, - component_delta_count: 0, - scale, - is_scaled, - // We don't hint unscaled outlines - is_hinted: is_hinted && is_scaled, - phantom: Default::default(), - hint_fn, - } - } - - fn outline( - mut self, - glyph: &Option, - glyph_id: GlyphId, - ) -> Result, Error> { - self.load(glyph, glyph_id, 0)?; - let outline = ScalerOutline { - points: &mut self.memory.scaled[..self.point_count], - flags: &mut self.memory.flags[..self.point_count], - contours: &mut self.memory.contours[..self.contour_count], - }; - let x_shift = self.phantom[0].x; - if x_shift != F26Dot6::ZERO { - for point in outline.points.iter_mut() { - point.x -= x_shift; - } - } - Ok(outline) - } - - fn load( - &mut self, - glyph: &Option, - glyph_id: GlyphId, - recurse_depth: usize, - ) -> Result<(), Error> { - if recurse_depth > COMPOSITE_RECURSION_LIMIT { - return Err(Error::RecursionLimitExceeded(glyph_id)); - } - let glyph = match &glyph { - Some(glyph) => glyph, - // This is a valid empty glyph - None => return Ok(()), - }; - let bounds = [glyph.x_min(), glyph.x_max(), glyph.y_min(), glyph.y_max()]; - self.setup_phantom_points(bounds, glyph_id); - match glyph { - Glyph::Simple(simple) => self.load_simple(simple, glyph_id), - Glyph::Composite(composite) => self.load_composite(composite, glyph_id, recurse_depth), - } - } - - fn load_simple(&mut self, glyph: &SimpleGlyph, glyph_id: GlyphId) -> Result<(), Error> { - use Error::InsufficientMemory; - // Compute the ranges for our point/flag buffers and slice them. - let points_start = self.point_count; - let point_count = glyph.num_points(); - let phantom_start = point_count; - let points_end = points_start + point_count + PHANTOM_POINT_COUNT; - let point_range = points_start..points_end; - let other_points_end = point_count + PHANTOM_POINT_COUNT; - // Scaled points and flags are accumulated as we load the outline. - let scaled = self - .memory - .scaled - .get_mut(point_range.clone()) - .ok_or(InsufficientMemory)?; - let flags = self - .memory - .flags - .get_mut(point_range) - .ok_or(InsufficientMemory)?; - // Unscaled points are temporary and are allocated as needed. We only - // ever need one copy in memory for any simple or composite glyph so - // allocate from the base of the buffer. - let unscaled = self - .memory - .unscaled - .get_mut(..other_points_end) - .ok_or(InsufficientMemory)?; - // Read our unscaled points and flags (up to point_count which does not - // include phantom points). - glyph.read_points_fast(&mut unscaled[..point_count], &mut flags[..point_count])?; - // Compute the range for our contour end point buffer and slice it. - let contours_start = self.contour_count; - let contour_end_pts = glyph.end_pts_of_contours(); - let contour_count = contour_end_pts.len(); - let contours_end = contours_start + contour_count; - let contours = self - .memory - .contours - .get_mut(contours_start..contours_end) - .ok_or(InsufficientMemory)?; - // Read the contour end points. - for (end_pt, contour) in contour_end_pts.iter().zip(contours.iter_mut()) { - *contour = end_pt.get(); - } - // Adjust the running point/contour total counts - self.point_count += point_count; - self.contour_count += contour_count; - // Append phantom points to the outline. - for (i, phantom) in self.phantom.iter().enumerate() { - unscaled[phantom_start + i] = phantom.map(|x| x.to_bits()); - flags[phantom_start + i] = Default::default(); - } - let mut have_deltas = false; - if self.scaler.gvar.is_some() && !self.coords.is_empty() { - let gvar = self.scaler.gvar.as_ref().unwrap(); - let glyph = deltas::SimpleGlyph { - points: &mut unscaled[..], - flags: &mut flags[..], - contours, - }; - let deltas = self - .memory - .deltas - .get_mut(..point_count + PHANTOM_POINT_COUNT) - .ok_or(InsufficientMemory)?; - let iup_buffer = self - .memory - .iup_buffer - .get_mut(..point_count + PHANTOM_POINT_COUNT) - .ok_or(InsufficientMemory)?; - if deltas::simple_glyph( - gvar, - glyph_id, - self.coords, - self.scaler.has_var_lsb, - glyph, - iup_buffer, - deltas, - ) - .is_ok() - { - have_deltas = true; - } - } - let ins = glyph.instructions(); - let is_hinted = self.is_hinted && !ins.is_empty(); - if self.is_scaled { - let scale = self.scale; - if have_deltas { - for ((point, unscaled), delta) in scaled - .iter_mut() - .zip(unscaled.iter_mut()) - .zip(self.memory.deltas.iter()) - { - let delta = delta.map(Fixed::to_f26dot6); - let scaled = (unscaled.map(F26Dot6::from_i32) + delta) * scale; - // The computed scale factor has an i32 -> 26.26 conversion built in. This undoes the - // extra shift. - *point = scaled.map(|v| F26Dot6::from_bits(v.to_i32())); - } - if is_hinted { - // For hinting, we need to adjust the unscaled points as well. - // Round off deltas for unscaled outlines. - for (unscaled, delta) in unscaled.iter_mut().zip(self.memory.deltas.iter()) { - *unscaled += delta.map(Fixed::to_i32); - } - } - } else { - for (point, unscaled) in scaled.iter_mut().zip(unscaled.iter_mut()) { - *point = unscaled.map(|v| F26Dot6::from_bits(v) * scale); - } - } - } else { - if have_deltas { - // Round off deltas for unscaled outlines. - for (unscaled, delta) in unscaled.iter_mut().zip(self.memory.deltas.iter()) { - *unscaled += delta.map(Fixed::to_i32); - } - } - // Unlike FreeType, we also store unscaled outlines in 26.6. - for (point, unscaled) in scaled.iter_mut().zip(unscaled.iter()) { - *point = unscaled.map(F26Dot6::from_i32); - } - } - // Commit our potentially modified phantom points. - for (i, point) in scaled[phantom_start..] - .iter() - .enumerate() - .take(PHANTOM_POINT_COUNT) - { - self.phantom[i] = *point; - } - if is_hinted { - // Create a copy of our scaled points in original_scaled. - let original_scaled = self - .memory - .original_scaled - .get_mut(..other_points_end) - .ok_or(InsufficientMemory)?; - original_scaled.copy_from_slice(scaled); - // When hinting, round the phantom points. - for point in &mut scaled[phantom_start..] { - point.x = point.x.round(); - point.y = point.y.round(); - } - if !(self.hint_fn)(HinterOutline { - unscaled, - scaled, - original_scaled, - flags, - contours, - bytecode: ins, - phantom: &mut self.phantom, - is_composite: false, - coords: self.coords, - }) { - return Err(Error::HintingFailed(glyph_id)); - } - } - if points_start != 0 { - // If we're not the first component, shift our contour end points. - for contour_end in contours.iter_mut() { - *contour_end += points_start as u16; - } - } - Ok(()) - } - - fn load_composite( - &mut self, - glyph: &CompositeGlyph, - glyph_id: GlyphId, - recurse_depth: usize, - ) -> Result<(), Error> { - use Error::InsufficientMemory; - let scale = self.scale; - // The base indices of the points and contours for the current glyph. - let point_base = self.point_count; - let contour_base = self.contour_count; - // Compute the per component deltas. Since composites can be nested, we - // use a stack and keep track of the base. - let mut have_deltas = false; - let delta_base = self.component_delta_count; - if self.scaler.gvar.is_some() && !self.coords.is_empty() { - let gvar = self.scaler.gvar.as_ref().unwrap(); - let count = glyph.components().count() + PHANTOM_POINT_COUNT; - let deltas = self - .memory - .composite_deltas - .get_mut(delta_base..delta_base + count) - .ok_or(InsufficientMemory)?; - if deltas::composite_glyph(gvar, glyph_id, self.coords, &mut deltas[..]).is_ok() { - // If the font is missing variation data for LSBs in HVAR then we - // apply the delta to the first phantom point. - if !self.scaler.has_var_lsb { - self.phantom[0].x += F26Dot6::from_bits(deltas[count - 4].x.to_i32()); - } - have_deltas = true; - } - self.component_delta_count += count; - } - if self.is_scaled { - for point in self.phantom.iter_mut() { - *point *= scale; - } - } else { - for point in self.phantom.iter_mut() { - *point = point.map(|x| F26Dot6::from_i32(x.to_bits())); - } - } - for (i, component) in glyph.components().enumerate() { - // Loading a component glyph will override phantom points so save a copy. We'll - // restore them unless the USE_MY_METRICS flag is set. - let phantom = self.phantom; - // Load the component glyph and keep track of the points range. - let start_point = self.point_count; - let component_glyph = self - .scaler - .loca - .get_glyf(component.glyph, &self.scaler.glyf)?; - self.load(&component_glyph, component.glyph, recurse_depth + 1)?; - let end_point = self.point_count; - if !component - .flags - .contains(CompositeGlyphFlags::USE_MY_METRICS) - { - // If the USE_MY_METRICS flag is missing, we restore the phantom points we - // saved at the start of the loop. - self.phantom = phantom; - } - // Prepares the transform components for our conversion math below. - fn scale_component(x: F2Dot14) -> F26Dot6 { - F26Dot6::from_bits(x.to_bits() as i32 * 4) - } - let xform = &component.transform; - let xx = scale_component(xform.xx); - let yx = scale_component(xform.yx); - let xy = scale_component(xform.xy); - let yy = scale_component(xform.yy); - let have_xform = component.flags.intersects( - CompositeGlyphFlags::WE_HAVE_A_SCALE - | CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE - | CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO, - ); - if have_xform { - let scaled = &mut self.memory.scaled[start_point..end_point]; - if self.is_scaled { - for point in scaled { - let x = point.x * xx + point.y * xy; - let y = point.x * yx + point.y * yy; - point.x = x; - point.y = y; - } - } else { - for point in scaled { - // This juggling is necessary because, unlike FreeType, we also - // return unscaled outlines in 26.6 format for a consistent interface. - let unscaled = point.map(|c| F26Dot6::from_bits(c.to_i32())); - let x = unscaled.x * xx + unscaled.y * xy; - let y = unscaled.x * yx + unscaled.y * yy; - *point = Point::new(x, y).map(|c| F26Dot6::from_i32(c.to_bits())); - } - } - } - let anchor_offset = match component.anchor { - Anchor::Offset { x, y } => { - let (mut x, mut y) = (x as i32, y as i32); - if have_xform - && component.flags - & (CompositeGlyphFlags::SCALED_COMPONENT_OFFSET - | CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET) - == CompositeGlyphFlags::SCALED_COMPONENT_OFFSET - { - // According to FreeType, this algorithm is a "guess" - // and works better than the one documented by Apple. - // https://github.com/freetype/freetype/blob/b1c90733ee6a04882b133101d61b12e352eeb290/src/truetype/ttgload.c#L1259 - fn hypot(a: F26Dot6, b: F26Dot6) -> Fixed { - let a = a.to_bits().abs(); - let b = b.to_bits().abs(); - Fixed::from_bits(if a > b { - a + ((3 * b) >> 3) - } else { - b + ((3 * a) >> 3) - }) - } - // FreeType uses a fixed point multiplication here. - x = (Fixed::from_bits(x) * hypot(xx, xy)).to_bits(); - y = (Fixed::from_bits(y) * hypot(yy, yx)).to_bits(); - } - if have_deltas { - let delta = self - .memory - .composite_deltas - .get(delta_base + i) - .copied() - .unwrap_or_default(); - // For composite glyphs, we copy FreeType and round off - // the fractional parts of deltas. - x += delta.x.to_i32(); - y += delta.y.to_i32(); - } - if self.is_scaled { - let mut offset = Point::new(x, y).map(F26Dot6::from_bits) * scale; - if self.is_hinted - && component - .flags - .contains(CompositeGlyphFlags::ROUND_XY_TO_GRID) - { - // Only round the y-coordinate, per FreeType. - offset.y = offset.y.round(); - } - offset - } else { - Point::new(x, y).map(F26Dot6::from_i32) - } - } - Anchor::Point { base, component } => { - let (base_offset, component_offset) = (base as usize, component as usize); - let base_point = self - .memory - .scaled - .get(point_base + base_offset) - .ok_or(Error::InvalidAnchorPoint(glyph_id, base))?; - let component_point = self - .memory - .scaled - .get(start_point + component_offset) - .ok_or(Error::InvalidAnchorPoint(glyph_id, component))?; - *base_point - *component_point - } - }; - if anchor_offset.x != F26Dot6::ZERO || anchor_offset.y != F26Dot6::ZERO { - for point in &mut self.memory.scaled[start_point..end_point] { - *point += anchor_offset; - } - } - } - if have_deltas { - self.component_delta_count = delta_base; - } - if self.is_hinted { - let ins = glyph.instructions().unwrap_or_default(); - if !ins.is_empty() { - let start_point = point_base; - let end_point = self.point_count + PHANTOM_POINT_COUNT; - let point_range = start_point..end_point; - let scaled = &mut self.memory.scaled[point_range.clone()]; - let flags = self - .memory - .flags - .get_mut(point_range.clone()) - .ok_or(InsufficientMemory)?; - // Append the current phantom points to the outline. - let phantom_start = self.point_count; - for (i, phantom) in self.phantom.iter().enumerate() { - scaled[phantom_start + i] = *phantom; - flags[phantom_start + i] = Default::default(); - } - // For composite glyphs, the unscaled and original points are - // simply copies of the current point set. - let other_points_end = point_range.len(); - let unscaled = self - .memory - .unscaled - .get_mut(..other_points_end) - .ok_or(InsufficientMemory)?; - for (scaled, unscaled) in scaled.iter().zip(unscaled.iter_mut()) { - *unscaled = scaled.map(|x| x.to_bits()); - } - let original_scaled = self - .memory - .original_scaled - .get_mut(..other_points_end) - .ok_or(InsufficientMemory)?; - original_scaled.copy_from_slice(scaled); - let contours = self - .memory - .contours - .get_mut(contour_base..self.contour_count) - .ok_or(InsufficientMemory)?; - // Round the phantom points. - for p in &mut scaled[self.point_count..] { - p.x = p.x.round(); - p.y = p.y.round(); - } - // Clear the "touched" flags that are used during IUP processing. - for flag in flags.iter_mut() { - flag.clear_marker(PointMarker::TOUCHED); - } - // Make sure our contour end points accurately reflect the - // outline slices. - if point_base != 0 { - let delta = point_base as u16; - for contour in contours.iter_mut() { - *contour -= delta; - } - } - if !(self.hint_fn)(HinterOutline { - unscaled, - scaled, - original_scaled, - flags, - contours, - bytecode: ins, - phantom: &mut self.phantom, - is_composite: true, - coords: self.coords, - }) { - return Err(Error::HintingFailed(glyph_id)); - } - // Undo the contour shifts if we applied them above. - if point_base != 0 { - let delta = point_base as u16; - for contour in contours.iter_mut() { - *contour += delta; - } - } - } - } - Ok(()) - } -} - -impl<'a, H> ScalerInstance<'a, H> { - fn setup_phantom_points(&mut self, bounds: [i16; 4], glyph_id: GlyphId) { - let lsb = self.scaler.lsb(glyph_id, self.coords); - let advance = self.scaler.advance_width(glyph_id, self.coords); - // Vertical metrics aren't significant to the glyph loading process, so - // they are ignored. - let vadvance = 0; - let tsb = 0; - // The four "phantom" points as computed by FreeType. - self.phantom[0].x = F26Dot6::from_bits(bounds[0] as i32 - lsb); - self.phantom[0].y = F26Dot6::ZERO; - self.phantom[1].x = self.phantom[0].x + F26Dot6::from_bits(advance); - self.phantom[1].y = F26Dot6::ZERO; - self.phantom[2].x = F26Dot6::from_bits(advance / 2); - self.phantom[2].y = F26Dot6::from_bits(bounds[3] as i32 + tsb); - self.phantom[3].x = F26Dot6::from_bits(advance / 2); - self.phantom[3].y = self.phantom[2].y - F26Dot6::from_bits(vadvance); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use read_fonts::{FontRef, TableProvider}; - - #[test] - fn overlap_flags() { - let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap(); - let scaler = Scaler::new(&font).unwrap(); - let glyph_count = font.maxp().unwrap().num_glyphs(); - // GID 2 is a composite glyph with the overlap bit on a component - // GID 3 is a simple glyph with the overlap bit on the first flag - let expected_gids_with_overlap = vec![2, 3]; - assert_eq!( - expected_gids_with_overlap, - (0..glyph_count) - .filter(|gid| scaler.glyph(GlyphId::new(*gid)).unwrap().has_overlaps) - .collect::>() - ); - } -} diff --git a/skrifa/src/scale/scaler.rs b/skrifa/src/scale/scaler.rs index 347c564ab..ea3252f63 100644 --- a/skrifa/src/scale/scaler.rs +++ b/skrifa/src/scale/scaler.rs @@ -139,7 +139,7 @@ impl<'a> ScalerBuilder<'a> { self.resolve_variations(font); let coords = &self.context.coords[..]; let size = self.size.ppem().unwrap_or_default(); - let outlines = if let Some(glyf) = glyf::Scaler::new(font) { + let outlines = if let Some(glyf) = glyf::Outlines::new(font) { Some(Outlines::TrueType(glyf, &mut self.context.outline_memory)) } else { cff::Outlines::new(font) @@ -244,7 +244,7 @@ impl<'a> Scaler<'a> { // for now: we'll replace this with a real cache. #[allow(clippy::large_enum_variant)] enum Outlines<'a> { - TrueType(glyf::Scaler<'a>, &'a mut Vec), + TrueType(glyf::Outlines<'a>, &'a mut Vec), PostScript(cff::Outlines<'a>, cff::Subfont), } @@ -259,7 +259,7 @@ impl<'a> Outlines<'a> { ) -> Result { match self { Self::TrueType(scaler, buf) => { - let glyph = scaler.glyph(glyph_id)?; + let glyph = scaler.outline(glyph_id)?; let buf_size = glyph.required_buffer_size(false); if buf.len() < buf_size { buf.resize(buf_size, 0); @@ -267,7 +267,7 @@ impl<'a> Outlines<'a> { let memory = glyph .memory_from_buffer(&mut buf[..], false) .ok_or(Error::InsufficientMemory)?; - let outline = scaler.outline(memory, &glyph, size, coords)?; + let outline = scaler.scale(memory, &glyph, size, coords)?; outline.to_path(pen)?; Ok(ScalerMetrics { has_overlaps: glyph.has_overlaps,