Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[read-fonts] Add scaler for CFF/CFF2 #520

Merged
merged 10 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion read-fonts/src/tables/postscript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::fmt;
mod blend;
mod fd_select;
mod index;
mod scale;
mod stack;
mod string;

Expand All @@ -15,6 +16,7 @@ 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 All @@ -34,6 +36,8 @@ pub enum Error {
CharstringNestingDepthLimitExceeded,
MissingSubroutines,
MissingBlendState,
MissingPrivateDict,
MissingCharstrings,
Read(ReadError),
}

Expand Down Expand Up @@ -98,7 +102,12 @@ impl fmt::Display for Error {
"encountered a blend operator but no blend state was provided"
)
}

Self::MissingPrivateDict => {
write!(f, "CFF table does not contain a private dictionary")
}
Self::MissingCharstrings => {
write!(f, "CFF table does not contain a charstrings index")
}
Self::Read(err) => write!(f, "{err}"),
}
}
Expand Down
189 changes: 181 additions & 8 deletions read-fonts/src/tables/postscript/charstring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,21 @@ where
P: Pen,
{
fn move_to(&mut self, x: Fixed, y: Fixed) {
self.0.move_to(x.to_f64() as f32, y.to_f64() as f32);
self.0.move_to(x.to_f32(), y.to_f32());
}

fn line_to(&mut self, x: Fixed, y: Fixed) {
self.0.line_to(x.to_f64() as f32, y.to_f64() as f32);
self.0.line_to(x.to_f32(), y.to_f32());
}

fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
self.0.curve_to(
cx0.to_f64() as f32,
cy0.to_f64() as f32,
cx1.to_f64() as f32,
cy1.to_f64() as f32,
x.to_f64() as f32,
y.to_f64() as f32,
cx0.to_f32(),
cy0.to_f32(),
cx1.to_f32(),
cy1.to_f32(),
x.to_f32(),
y.to_f32(),
dfrg marked this conversation as resolved.
Show resolved Hide resolved
);
}

Expand All @@ -78,6 +78,179 @@ 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:
dfrg marked this conversation as resolved.
Show resolved Hide resolved
// 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 simplifies path operations.
dfrg marked this conversation as resolved.
Show resolved Hide resolved
///
/// This currently removes degenerate moves and lines.
dfrg marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) struct SimplifyingSink<'a, S> {
start: Option<(Fixed, Fixed)>,
last: Option<(Fixed, Fixed)>,
pending_move: Option<(Fixed, Fixed)>,
inner: &'a mut S,
}

impl<'a, S> SimplifyingSink<'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 SimplifyingSink<'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
Loading