From a9c4c7aa3ed4bf7fb5687acd9889f4055c6fcbe7 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 27 Feb 2017 12:52:14 -0800 Subject: [PATCH] Implement most of the basic arithmetic and control structures in the hinting VM --- Cargo.toml | 1 + src/error.rs | 4 + src/hinting/insns.rs | 12 +++ src/hinting/interp.rs | 206 +++++++++++++++++++++++++++++++++++++++++- src/hinting/mod.rs | 41 ++++++++- src/lib.rs | 1 + src/tables/glyf.rs | 18 +--- src/util.rs | 112 +++++++++++++++++++++++ 8 files changed, 372 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e815acba..21b44a9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ euclid = "0.10" flate2 = "0.2" gl = "0.6" memmap = "0.5" +num-traits = "0.1" time = "0.1" [dependencies.compute-shader] diff --git a/src/error.rs b/src/error.rs index 60ded817..8870eedc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -171,5 +171,9 @@ pub enum HintingExecutionError { ParseError(HintingParseError), /// An instruction expected more values than were on the stack. StackUnderflow, + /// An operation tried to read out of bounds of the control value table. + CvtReadOutOfBounds, + /// An undefined function ID was called. + CallToUndefinedFunction, } diff --git a/src/hinting/insns.rs b/src/hinting/insns.rs index 69bafc59..ad5fe602 100644 --- a/src/hinting/insns.rs +++ b/src/hinting/insns.rs @@ -11,6 +11,8 @@ //! TrueType instructions. use error::HintingParseError; +use euclid::Point2D; +use util::{F2DOT14_ONE, F2DOT14_ZERO, F2Dot14}; /// All TrueType instructions. #[derive(Clone, Copy, Debug)] @@ -455,6 +457,16 @@ pub enum Axis { X = 1, } +impl Axis { + #[inline] + pub fn as_point(self) -> Point2D { + match self { + Axis::Y => Point2D::new(F2DOT14_ZERO, F2DOT14_ONE), + Axis::X => Point2D::new(F2DOT14_ONE, F2DOT14_ZERO), + } + } +} + #[derive(Copy, Clone, Debug)] #[repr(u8)] pub enum LineOrientation { diff --git a/src/hinting/interp.rs b/src/hinting/interp.rs index 5888f024..5b5a7951 100644 --- a/src/hinting/interp.rs +++ b/src/hinting/interp.rs @@ -12,8 +12,12 @@ use byteorder::{BigEndian, ByteOrder}; use error::{HintingAnalysisError, HintingExecutionError, HintingParseError}; -use hinting::Hinter; use hinting::insns::Instruction; +use hinting::{FONT_SMOOTHING_GRAYSCALE, GETINFO_VERSION, Hinter}; +use hinting::{INFO_RESULT_FONT_SMOOTHING_GRAYSCALE_SHIFT, InfoSelector, RoundState, VERSION}; +use num_traits::Zero; +use std::cmp; +use util::{F26DOT6_ZERO, F26Dot6}; impl<'a> Hinter<'a> { pub fn exec(&mut self) -> Result<(), HintingExecutionError> { @@ -63,6 +67,175 @@ impl<'a> Hinter<'a> { } self.storage_area[addr] = value } + Instruction::Rcvt => { + let addr = try!(self.pop()) as usize; + let value = *try!(self.control_value_table + .get(addr) + .ok_or(HintingExecutionError::CvtReadOutOfBounds)); + self.stack.push(value.0) + } + Instruction::Svtca(axis) => { + self.projection_vector = axis.as_point(); + self.freedom_vector = axis.as_point(); + } + Instruction::Spvtca(axis) => self.projection_vector = axis.as_point(), + Instruction::Sfvtca(axis) => self.freedom_vector = axis.as_point(), + Instruction::Srp0 => self.reference_points[0] = try!(self.pop()) as u32, + Instruction::Srp1 => self.reference_points[1] = try!(self.pop()) as u32, + Instruction::Srp2 => self.reference_points[2] = try!(self.pop()) as u32, + Instruction::Szp0 => self.zone_points[0] = try!(self.pop()) as u32, + Instruction::Szp1 => self.zone_points[1] = try!(self.pop()) as u32, + Instruction::Szp2 => self.zone_points[2] = try!(self.pop()) as u32, + Instruction::Szps => { + let zone = try!(self.pop()) as u32; + self.zone_points = [zone; 3] + } + Instruction::Rthg => self.round_state = RoundState::RoundToHalfGrid, + Instruction::Rtg => self.round_state = RoundState::RoundToGrid, + Instruction::Rtdg => self.round_state = RoundState::RoundToDoubleGrid, + Instruction::Rutg => self.round_state = RoundState::RoundUpToGrid, + Instruction::Roff => self.round_state = RoundState::RoundOff, + Instruction::Sround => { + // TODO(pcwalton): Super rounding. + try!(self.pop()); + } + Instruction::Scanctrl | Instruction::Scantype => { + // Not applicable to antialiased glyphs. + try!(self.pop()); + } + Instruction::Mppem => { + // We always scale both axes in the same direction, so we don't have to look + // at the projection vector. + self.stack.push(self.point_size.round() as i32) + } + Instruction::Dup => { + let value = *try!(self.stack + .last() + .ok_or(HintingExecutionError::StackUnderflow)); + self.stack.push(value); + } + Instruction::Pop => { + try!(self.pop()); + } + Instruction::Clear => self.stack.clear(), + Instruction::Swap => { + let (a, b) = (try!(self.pop()), try!(self.pop())); + self.stack.push(a); + self.stack.push(b); + } + Instruction::Mindex => { + let index = try!(self.pop()) as usize; + if index >= self.stack.len() { + return Err(HintingExecutionError::StackUnderflow) + } + let rindex = self.stack.len() - 1 - index; + let value = self.stack.remove(rindex); + self.stack.push(value) + } + Instruction::If => { + let cond = try!(self.pop()); + if cond == 0 { + // Move to the instruction following `else` or `eif`. + let else_target_index = self.scripts[frame.script] + .branch_targets + .binary_search_by(|script| { + script.branch_location.cmp(&frame.pc) + }).unwrap(); + new_pc = self.scripts[frame.script] + .branch_targets[else_target_index] + .target_location + 1 + } + } + Instruction::Else => { + // The only way we get here is by falling off the end of a then-branch. So jump + // to the instruction following the matching `eif`. + let eif_target_index = self.scripts[frame.script] + .branch_targets + .binary_search_by(|script| { + script.branch_location.cmp(&frame.pc) + }).unwrap(); + new_pc = self.scripts[frame.script] + .branch_targets[eif_target_index] + .target_location + 1 + } + Instruction::Eif => { + // Likewise, the only way we get here is by falling off the end of a + // then-branch. + } + Instruction::Lt => { + let (rhs, lhs) = (try!(self.pop()), try!(self.pop())); + self.stack.push((lhs < rhs) as i32) + } + Instruction::Lteq => { + let (rhs, lhs) = (try!(self.pop()), try!(self.pop())); + self.stack.push((lhs <= rhs) as i32) + } + Instruction::Gt => { + let (rhs, lhs) = (try!(self.pop()), try!(self.pop())); + self.stack.push((lhs > rhs) as i32) + } + Instruction::Gteq => { + let (rhs, lhs) = (try!(self.pop()), try!(self.pop())); + self.stack.push((lhs >= rhs) as i32) + } + Instruction::Eq => { + let (rhs, lhs) = (try!(self.pop()), try!(self.pop())); + self.stack.push((lhs == rhs) as i32) + } + Instruction::Neq => { + let (rhs, lhs) = (try!(self.pop()), try!(self.pop())); + self.stack.push((lhs != rhs) as i32) + } + Instruction::And => { + let (rhs, lhs) = (try!(self.pop()), try!(self.pop())); + self.stack.push((lhs != 0 && rhs != 0) as i32) + } + Instruction::Or => { + let (rhs, lhs) = (try!(self.pop()), try!(self.pop())); + self.stack.push((lhs != 0 || rhs != 0) as i32) + } + Instruction::Not => { + let cond = try!(self.pop()); + self.stack.push((cond == 0) as i32) + } + Instruction::Add => { + let (rhs, lhs) = (F26Dot6(try!(self.pop())), F26Dot6(try!(self.pop()))); + self.stack.push((lhs + rhs).0) + } + Instruction::Sub => { + let (rhs, lhs) = (F26Dot6(try!(self.pop())), F26Dot6(try!(self.pop()))); + self.stack.push((lhs - rhs).0) + } + Instruction::Div => { + let (rhs, lhs) = (F26Dot6(try!(self.pop())), F26Dot6(try!(self.pop()))); + if rhs.is_zero() { + // Obey Postel's law… + self.stack.push(F26DOT6_ZERO.0) + } else { + self.stack.push((lhs / rhs).0) + } + } + Instruction::Mul => { + let (rhs, lhs) = (F26Dot6(try!(self.pop())), F26Dot6(try!(self.pop()))); + self.stack.push((lhs * rhs).0) + } + Instruction::Abs => { + // Actually in fixed point, but it works out the same way. + let n = try!(self.pop()); + self.stack.push(n.abs()) + } + Instruction::Neg => { + let n = F26Dot6(try!(self.pop())); + self.stack.push((-n).0) + } + Instruction::Max => { + let (rhs, lhs) = (F26Dot6(try!(self.pop())), F26Dot6(try!(self.pop()))); + self.stack.push(cmp::max(rhs, lhs).0) + } + Instruction::Min => { + let (rhs, lhs) = (F26Dot6(try!(self.pop())), F26Dot6(try!(self.pop()))); + self.stack.push(cmp::max(rhs, lhs).0) + } Instruction::Fdef => { // We should throw an exception here if the function definition list isn't big // enough, but let's follow Postel's law. @@ -86,6 +259,37 @@ impl<'a> Hinter<'a> { self.functions[id] = Some(Frame::new(new_pc, end_pc, frame.script)); new_pc = end_pc + 1 } + Instruction::Call => { + let id = try!(self.pop()) as usize; + let new_frame = match self.functions.get(id) { + Some(&Some(new_frame)) => new_frame, + Some(&None) | None => { + return Err(HintingExecutionError::CallToUndefinedFunction) + } + }; + + // Save our return address. + self.call_stack.last_mut().unwrap().pc = new_pc; + + // Jump to the new function. + self.call_stack.push(new_frame); + new_pc = new_frame.pc + } + Instruction::Getinfo => { + let selector = InfoSelector::from_bits_truncate(try!(self.pop())); + + // We only handle a subset of the selectors here. + // + // TODO(pcwalton): Handle the ones relating to subpixel AA. + let mut result = 0; + if selector.contains(VERSION) { + result |= GETINFO_VERSION + } + if selector.contains(FONT_SMOOTHING_GRAYSCALE) { + result |= 1 << INFO_RESULT_FONT_SMOOTHING_GRAYSCALE_SHIFT + } + self.stack.push(result) + } _ => { println!("TODO: {:?}", instruction); } diff --git a/src/hinting/mod.rs b/src/hinting/mod.rs index d4be4dc0..35cc7fc3 100644 --- a/src/hinting/mod.rs +++ b/src/hinting/mod.rs @@ -19,10 +19,15 @@ use error::{HinterCreationError, HintingExecutionError}; use euclid::Point2D; use font::Font; use hinting::interp::{Frame, Script}; +use util::{F26Dot6, F2Dot14}; mod insns; mod interp; +/// The version we return for the `getinfo` instruction. FreeType uses 35, so we do as well. (This +/// supposedly corresponds to Windows 98.) +pub const GETINFO_VERSION: i32 = 35; + const FONT_PROGRAM: usize = 0; const CONTROL_VALUE_PROGRAM: usize = 1; @@ -37,17 +42,17 @@ pub struct Hinter<'a> { // The set of defined functions. functions: Vec>, // The Control Value Table: the VM's initialized memory. - control_value_table: Vec, + control_value_table: Vec, // The Storage Area: the VM's uninitialized memory. storage_area: Vec, // The current font size. point_size: f32, // The projection vector, in 2.14 fixed point. - projection_vector: Point2D, + projection_vector: Point2D, // The dual projection vector, in 2.14 fixed point. - dual_projection_vector: Point2D, + dual_projection_vector: Point2D, // The freedom vector, in 2.14 fixed point. - freedom_vector: Point2D, + freedom_vector: Point2D, // The reference point indices. reference_points: [u32; 3], // The zone numbers. @@ -92,7 +97,11 @@ impl<'a> Hinter<'a> { HinterCreationError::ControlValueProgramAnalysisError)), ]; - let cvt = font.control_value_table().chunks(2).map(BigEndian::read_i16).collect(); + let cvt = font.control_value_table().chunks(2).map(|bytes| { + // FIXME(pcwalton): This is wrong! + let unscaled = BigEndian::read_i16(bytes) as i32; + F26Dot6(unscaled) + }).collect(); // Initialize the call stack to the font program, so that we'll start executing it. let call_stack = vec![Frame::new(0, scripts[FONT_PROGRAM].len(), FONT_PROGRAM)]; @@ -188,3 +197,25 @@ bitflags! { } } +bitflags! { + // Info returned by the `getinfo` instruction. + pub flags InfoSelector: i32 { + const VERSION = 0x1, + const GLYPH_ROTATION = 0x2, + const GLYPH_STRETCHED = 0x4, + const FONT_VARIATIONS = 0x8, + const VERTICAL_PHANTOM_POINTS = 0x10, + const FONT_SMOOTHING_GRAYSCALE = 0x20, + const SUBPIXEL_AA_ENABLED = 0x40, + const SUBPIXEL_AA_COMPATIBLE_WIDTHS_ENABLED = 0x80, + const SUBPIXEL_AA_HORIZONTAL_LCD_STRIPE_ORIENTATION = 0x100, + const SUBPIXEL_AA_BGR_LCD_STRIPE_ORDER = 0x200, + const SUBPIXEL_AA_SUBPIXEL_POSITIONED_TEXT_ENABLED = 0x400, + const SUBPIXEL_AA_SYMMETRIC_RENDERING_ENABLED = 0x800, + const SUBPIXEL_AA_GRAY_RENDERING_ENABLED = 0x1000, + } +} + +pub const INFO_RESULT_VERSION_SHIFT: i32 = 0; +pub const INFO_RESULT_FONT_SMOOTHING_GRAYSCALE_SHIFT: i32 = 12; + diff --git a/src/lib.rs b/src/lib.rs index 915128fd..d323411b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,7 @@ extern crate flate2; extern crate gl; #[cfg(test)] extern crate memmap; +extern crate num_traits; #[cfg(test)] #[macro_use] extern crate quickcheck; diff --git a/src/tables/glyf.rs b/src/tables/glyf.rs index 3f5cc3eb..a0e23d75 100644 --- a/src/tables/glyf.rs +++ b/src/tables/glyf.rs @@ -14,19 +14,15 @@ use euclid::Point2D; use font::{FontTable, Point, PointKind}; use outline::GlyphBounds; use std::mem; -use std::ops::Mul; use tables::head::HeadTable; use tables::loca::LocaTable; -use util::Jump; +use util::{F2DOT14_ONE, F2DOT14_ZERO, F2Dot14, Jump}; pub const TAG: u32 = ((b'g' as u32) << 24) | ((b'l' as u32) << 16) | ((b'y' as u32) << 8) | (b'f' as u32); -const F2DOT14_ZERO: F2Dot14 = F2Dot14(0); -const F2DOT14_ONE: F2Dot14 = F2Dot14(0b0100_0000_0000_0000); - bitflags! { flags SimpleFlags: u8 { const ON_CURVE = 1 << 0, @@ -438,15 +434,3 @@ impl Mat3x2 { } } -#[derive(Copy, Clone, Debug)] -struct F2Dot14(i16); - -impl Mul for F2Dot14 { - type Output = i16; - - #[inline] - fn mul(self, other: i16) -> i16 { - ((self.0 as i32 * other as i32) >> 14) as i16 - } -} - diff --git a/src/util.rs b/src/util.rs index f56e60bd..6480c3da 100644 --- a/src/util.rs +++ b/src/util.rs @@ -8,6 +8,118 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use num_traits::identities::Zero; +use std::ops::{Add, Div, Mul, Neg, Sub}; + +pub const F26DOT6_ZERO: F26Dot6 = F26Dot6(0); + +pub const F2DOT14_ZERO: F2Dot14 = F2Dot14(0); +pub const F2DOT14_ONE: F2Dot14 = F2Dot14(1 << 14); + +/// 26.6 fixed point. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct F26Dot6(pub i32); + +impl Zero for F26Dot6 { + #[inline] + fn zero() -> F26Dot6 { + F26DOT6_ZERO + } + + #[inline] + fn is_zero(&self) -> bool { + *self == F26DOT6_ZERO + } +} + +impl Add for F26Dot6 { + type Output = F26Dot6; + + #[inline] + fn add(self, other: F26Dot6) -> F26Dot6 { + F26Dot6(self.0 + other.0) + } +} + +impl Sub for F26Dot6 { + type Output = F26Dot6; + + #[inline] + fn sub(self, other: F26Dot6) -> F26Dot6 { + F26Dot6(self.0 - other.0) + } +} + +impl Mul for F26Dot6 { + type Output = F26Dot6; + + #[inline] + fn mul(self, other: F26Dot6) -> F26Dot6 { + F26Dot6(((self.0 as i64 * other.0 as i64 + 1 << 5) >> 6) as i32) + } +} + +impl Div for F26Dot6 { + type Output = F26Dot6; + + #[inline] + fn div(self, other: F26Dot6) -> F26Dot6 { + F26Dot6((((self.0 as i64) << 6) / other.0 as i64) as i32) + } +} + +impl Neg for F26Dot6 { + type Output = F26Dot6; + + #[inline] + fn neg(self) -> F26Dot6 { + F26Dot6(-self.0) + } +} + +/// 2.14 fixed point. +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct F2Dot14(pub i16); + +impl Zero for F2Dot14 { + #[inline] + fn zero() -> F2Dot14 { + F2DOT14_ZERO + } + + #[inline] + fn is_zero(&self) -> bool { + *self == F2DOT14_ZERO + } +} + +impl Add for F2Dot14 { + type Output = F2Dot14; + + #[inline] + fn add(self, other: F2Dot14) -> F2Dot14 { + F2Dot14(self.0 + other.0) + } +} + +impl Mul for F2Dot14 { + type Output = i16; + + #[inline] + fn mul(self, other: i16) -> i16 { + ((self.0 as i32 * other as i32) >> 14) as i16 + } +} + +impl Neg for F2Dot14 { + type Output = F2Dot14; + + #[inline] + fn neg(self) -> F2Dot14 { + F2Dot14(-self.0) + } +} + /// A faster version of `Seek` that supports only forward motion from the current position. pub trait Jump { /// Moves the pointer forward `n` bytes from the *current* position.