Skip to content

Commit

Permalink
Move CFF scaler implementation to skrifa
Browse files Browse the repository at this point in the history
  • Loading branch information
dfrg committed Aug 8, 2023
1 parent d281c3d commit c75229f
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 337 deletions.
2 changes: 0 additions & 2 deletions read-fonts/src/tables/postscript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::fmt;
mod blend;
mod fd_select;
mod index;
mod scale;
mod stack;
mod string;

Expand All @@ -16,7 +15,6 @@ include!("../../generated/generated_postscript.rs");

pub use blend::BlendState;
pub use index::Index;
pub use scale::{Scaler, ScalerSubfont};
pub use stack::{Number, Stack};
pub use string::{Latin1String, StringId, STANDARD_STRINGS};

Expand Down
180 changes: 0 additions & 180 deletions read-fonts/src/tables/postscript/charstring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,186 +78,6 @@ where
}
}

/// Command sink adapter that applies a scaling factor.
///
/// This assumes a 26.6 scaling factor packed into a Fixed and thus,
/// this is not public and exists only to match FreeType's exact
/// scaling process.
pub(crate) struct ScalingSink26Dot6<'a, S> {
inner: &'a mut S,
scale: Fixed,
}

impl<'a, S> ScalingSink26Dot6<'a, S> {
pub fn new(sink: &'a mut S, scale: Fixed) -> Self {
Self { scale, inner: sink }
}

fn scale(&self, coord: Fixed) -> Fixed {
if self.scale != Fixed::ONE {
// The following dance is necessary to exactly match FreeType's
// application of scaling factors. This seems to be the result
// of merging the contributed Adobe code while not breaking the
// FreeType public API.
// 1. Multiply by 1/64
// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psft.c#L284>
let a = coord * Fixed::from_bits(0x0400);
// 2. Convert to 26.6 by truncation
// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psobjs.c#L2219>
let b = Fixed::from_bits(a.to_bits() >> 10);
// 3. Multiply by the original scale factor
// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/cff/cffgload.c#L721>
let c = b * self.scale;
// Finally, we convert back to 16.16
Fixed::from_bits(c.to_bits() << 10)
} else {
// Otherwise, simply zero the low 10 bits
Fixed::from_bits(coord.to_bits() & !0x3FF)
}
}
}

impl<'a, S: CommandSink> CommandSink for ScalingSink26Dot6<'a, S> {
fn hstem(&mut self, y: Fixed, dy: Fixed) {
self.inner.hstem(y, dy);
}

fn vstem(&mut self, x: Fixed, dx: Fixed) {
self.inner.vstem(x, dx);
}

fn hint_mask(&mut self, mask: &[u8]) {
self.inner.hint_mask(mask);
}

fn counter_mask(&mut self, mask: &[u8]) {
self.inner.counter_mask(mask);
}

fn move_to(&mut self, x: Fixed, y: Fixed) {
self.inner.move_to(self.scale(x), self.scale(y));
}

fn line_to(&mut self, x: Fixed, y: Fixed) {
self.inner.line_to(self.scale(x), self.scale(y));
}

fn curve_to(&mut self, cx1: Fixed, cy1: Fixed, cx2: Fixed, cy2: Fixed, x: Fixed, y: Fixed) {
self.inner.curve_to(
self.scale(cx1),
self.scale(cy1),
self.scale(cx2),
self.scale(cy2),
self.scale(x),
self.scale(y),
);
}

fn close(&mut self) {
self.inner.close();
}
}

/// Command sink adapter that supresses degenerate move and line commands.
///
/// FreeType avoids emitting empty contours and zero length lines to prevent
/// artifacts when stem darkening is enabled. We don't support stem darkening
/// because it's not enabled by any of our clients but we remove the degenerate
/// elements regardless to match the output.
///
/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/pshints.c#L1786>
pub(crate) struct NopFilteringSink<'a, S> {
start: Option<(Fixed, Fixed)>,
last: Option<(Fixed, Fixed)>,
pending_move: Option<(Fixed, Fixed)>,
inner: &'a mut S,
}

impl<'a, S> NopFilteringSink<'a, S>
where
S: CommandSink,
{
pub fn new(inner: &'a mut S) -> Self {
Self {
start: None,
last: None,
pending_move: None,
inner,
}
}

fn flush_pending_move(&mut self) {
if let Some((x, y)) = self.pending_move.take() {
if let Some((last_x, last_y)) = self.start {
if self.last != self.start {
self.inner.line_to(last_x, last_y);
}
}
self.start = Some((x, y));
self.last = None;
self.inner.move_to(x, y);
}
}

pub fn finish(&mut self) {
match self.start {
Some((x, y)) if self.last != self.start => {
self.inner.line_to(x, y);
}
_ => {}
}
}
}

impl<'a, S> CommandSink for NopFilteringSink<'a, S>
where
S: CommandSink,
{
fn hstem(&mut self, y: Fixed, dy: Fixed) {
self.inner.hstem(y, dy);
}

fn vstem(&mut self, x: Fixed, dx: Fixed) {
self.inner.vstem(x, dx);
}

fn hint_mask(&mut self, mask: &[u8]) {
self.inner.hint_mask(mask);
}

fn counter_mask(&mut self, mask: &[u8]) {
self.inner.counter_mask(mask);
}

fn move_to(&mut self, x: Fixed, y: Fixed) {
self.pending_move = Some((x, y));
}

fn line_to(&mut self, x: Fixed, y: Fixed) {
if self.pending_move == Some((x, y)) {
return;
}
self.flush_pending_move();
if self.last == Some((x, y)) || (self.last.is_none() && self.start == Some((x, y))) {
return;
}
self.inner.line_to(x, y);
self.last = Some((x, y));
}

fn curve_to(&mut self, cx1: Fixed, cy1: Fixed, cx2: Fixed, cy2: Fixed, x: Fixed, y: Fixed) {
self.flush_pending_move();
self.last = Some((x, y));
self.inner.curve_to(cx1, cy1, cx2, cy2, x, y);
}

fn close(&mut self) {
if self.pending_move.is_none() {
self.inner.close()
}
}
}

/// Evaluates the given charstring and emits the resulting commands to the
/// specified sink.
///
Expand Down
49 changes: 49 additions & 0 deletions skrifa/src/scale/cff/hint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! CFF hinting.

use read_fonts::{tables::postscript::dict::Blues, types::Fixed};

/// Parameters used to generate the stem and counter zones for the hinting
/// algorithm.
#[derive(Clone)]
pub(crate) struct HintParams {
pub blues: Blues,
pub family_blues: Blues,
pub other_blues: Blues,
pub family_other_blues: Blues,
pub blue_scale: Fixed,
pub blue_shift: Fixed,
pub blue_fuzz: Fixed,
pub language_group: i32,
}

impl Default for HintParams {
fn default() -> Self {
Self {
blues: Blues::default(),
other_blues: Blues::default(),
family_blues: Blues::default(),
family_other_blues: Blues::default(),
// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-16-private-dict-operators>
blue_scale: Fixed::from_f64(0.039625),
blue_shift: Fixed::from_i32(7),
blue_fuzz: Fixed::ONE,
language_group: 0,
}
}
}

/// Hinting state for a PostScript subfont.
///
/// Note that hinter states depend on the scale, subfont index and
/// variation coordinates of a glyph. They can be retained and reused
/// if those values remain the same.
#[derive(Copy, Clone)]
pub(crate) struct HintState {
// TODO
}

impl HintState {
pub fn new(_params: &HintParams, _scale: Fixed) -> Self {
Self {}
}
}
9 changes: 9 additions & 0 deletions skrifa/src/scale/cff/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! Support for scaling CFF outlines.

// Temporary until new scaler API is done.
#![allow(dead_code)]

mod hint;
mod scaler;

pub(crate) use scaler::{Scaler, Subfont};
Loading

0 comments on commit c75229f

Please sign in to comment.