Implement most of the basic arithmetic and control structures in the hinting VM
This commit is contained in:
parent
6d66cfc30a
commit
a9c4c7aa3e
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
112
src/util.rs
112
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<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.
|
||||
|
|
Loading…
Reference in New Issue