Implement most of the basic arithmetic and control structures in the hinting VM

This commit is contained in:
Patrick Walton 2017-02-27 12:52:14 -08:00
parent 6d66cfc30a
commit a9c4c7aa3e
8 changed files with 372 additions and 23 deletions

View File

@ -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]

View File

@ -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,
}

View File

@ -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<F2Dot14> {
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 {

View File

@ -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);
}

View File

@ -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<Option<Frame>>,
// The Control Value Table: the VM's initialized memory.
control_value_table: Vec<i16>,
control_value_table: Vec<F26Dot6>,
// The Storage Area: the VM's uninitialized memory.
storage_area: Vec<i32>,
// The current font size.
point_size: f32,
// The projection vector, in 2.14 fixed point.
projection_vector: Point2D<i16>,
projection_vector: Point2D<F2Dot14>,
// The dual projection vector, in 2.14 fixed point.
dual_projection_vector: Point2D<i16>,
dual_projection_vector: Point2D<F2Dot14>,
// The freedom vector, in 2.14 fixed point.
freedom_vector: Point2D<i16>,
freedom_vector: Point2D<F2Dot14>,
// 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;

View File

@ -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;

View File

@ -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<i16> for F2Dot14 {
type Output = i16;
#[inline]
fn mul(self, other: i16) -> i16 {
((self.0 as i32 * other as i32) >> 14) as i16
}
}

View File

@ -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<F26Dot6> for F26Dot6 {
type Output = F26Dot6;
#[inline]
fn add(self, other: F26Dot6) -> F26Dot6 {
F26Dot6(self.0 + other.0)
}
}
impl Sub<F26Dot6> for F26Dot6 {
type Output = F26Dot6;
#[inline]
fn sub(self, other: F26Dot6) -> F26Dot6 {
F26Dot6(self.0 - other.0)
}
}
impl Mul<F26Dot6> 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<F26Dot6> 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<F2Dot14> for F2Dot14 {
type Output = F2Dot14;
#[inline]
fn add(self, other: F2Dot14) -> F2Dot14 {
F2Dot14(self.0 + other.0)
}
}
impl Mul<i16> 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.