Begin to implement the interpreter

This commit is contained in:
Patrick Walton 2017-02-26 12:12:34 -08:00
parent 37736b27b9
commit 6d66cfc30a
6 changed files with 231 additions and 46 deletions

View File

@ -16,7 +16,9 @@ use std::char;
fn main() {
let hint_arg = Arg::with_name("hint").short("H")
.long("hint")
.help("Apply hinting instructions");
.value_name("POINT-SIZE")
.help("Apply hinting instructions for a point size")
.takes_value(true);
let font_arg = Arg::with_name("FONT-FILE").help("Select the font file (`.ttf`, `.otf`, etc.)")
.required(true)
.index(1);
@ -27,8 +29,10 @@ fn main() {
unsafe {
let font = Font::new(file.as_slice(), &mut buffer).unwrap();
let hinter = if matches.is_present("hint") {
Some(Hinter::new(&font).unwrap())
let hinter = if let Some(point_size) = matches.value_of("hint") {
let mut hinter = Hinter::new(&font).unwrap();
hinter.set_point_size(point_size.parse().unwrap()).unwrap();
Some(hinter)
} else {
None
};

View File

@ -124,13 +124,17 @@ pub enum GlyphStoreCreationError {
GlError(GlError),
}
/// An error in hinting instruction evaluation.
/// An error in construction of a hinter.
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum HintingError {
pub enum HinterCreationError {
/// A miscellaneous error occurred.
Failed,
/// An error was encountered during hinting program analysis.
AnalysisError(HintingAnalysisError),
/// An error was encountered while analyzing the font program.
FontProgramAnalysisError(HintingAnalysisError),
/// An error was encountered while analyzing the control value program.
ControlValueProgramAnalysisError(HintingAnalysisError),
/// An error was encountered during execution of the font program.
FontProgramExecutionError(HintingExecutionError),
}
/// An error encountered during parsing of the TrueType hinting bytecode.
@ -160,3 +164,12 @@ pub enum HintingAnalysisError {
MismatchedBranchInstruction,
}
/// An error encountered during execution of the TrueType hinting bytecode.
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum HintingExecutionError {
/// An error occurred while parsing the instruction stream.
ParseError(HintingParseError),
/// An instruction expected more values than were on the stack.
StackUnderflow,
}

View File

@ -218,6 +218,15 @@ impl<'a> Font<'a> {
Some(fpgm) => fpgm.bytes,
}
}
/// Returns the control value program, which is run whenever the point size changes.
#[inline]
pub fn control_value_program(&self) -> &[u8] {
match self.tables.prep {
None => &[],
Some(prep) => prep.bytes,
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]

View File

@ -91,6 +91,8 @@ pub enum Instruction<'a> {
Instctrl,
/// Scan Conversion Control (0x85) (ttinst1.doc, 244-245)
Scanctrl,
/// ScanType (0x8d) (ttinst1.doc, 246)
Scantype,
/// Set Control Value Table Cut In (0x1d) (ttinst1.doc, 249)
Scvtci,
/// Set Single Width Cut In (0x1e) (ttinst1.doc, 250)
@ -332,6 +334,7 @@ impl<'a> Instruction<'a> {
0x1a => Ok(Instruction::Smd),
0x8e => Ok(Instruction::Instctrl),
0x85 => Ok(Instruction::Scanctrl),
0x8d => Ok(Instruction::Scantype),
0x1d => Ok(Instruction::Scvtci),
0x1e => Ok(Instruction::Sswci),
0x1f => Ok(Instruction::Ssw),

View File

@ -10,17 +10,106 @@
//! The TrueType interpreter.
use error::{HintingAnalysisError, HintingParseError};
use byteorder::{BigEndian, ByteOrder};
use error::{HintingAnalysisError, HintingExecutionError, HintingParseError};
use hinting::Hinter;
use hinting::insns::Instruction;
pub struct ScriptInterpreter<'a> {
impl<'a> Hinter<'a> {
pub fn exec(&mut self) -> Result<(), HintingExecutionError> {
loop {
// Fetch the current frame.
let frame = match self.call_stack.last() {
None => return Ok(()),
Some(&frame) if frame.pc == frame.end => {
self.call_stack.pop();
continue
}
Some(&frame) => frame,
};
// Decode the next instruction, and advance the program counter.
let mut new_pc = frame.pc;
let bytecode = self.scripts[frame.script].bytecode;
let instruction =
try!(Instruction::parse(bytecode,
&mut new_pc).map_err(HintingExecutionError::ParseError));
// Execute it.
match instruction {
Instruction::Pushb(bytes) => {
self.stack.extend(bytes.iter().map(|&b| b as i32))
}
Instruction::Pushw(bytes) => {
self.stack.extend(bytes.chunks(2).map(|bs| BigEndian::read_i16(bs) as i32))
}
Instruction::Rs => {
// We should throw an exception here if the storage area isn't big enough, but
// let's follow Postel's law.
let addr = try!(self.pop()) as usize;
match self.storage_area.get(addr) {
Some(&value) => self.stack.push(value),
None => self.stack.push(0),
}
}
Instruction::Ws => {
// We should throw an exception here if the storage area isn't big enough, but
// let's follow Postel's law.
//
// FIXME(pcwalton): Cap the size of the storage area?
let (value, addr) = (try!(self.pop()), try!(self.pop()) as usize);
if self.storage_area.len() < addr + 1 {
self.storage_area.resize(addr + 1, 0)
}
self.storage_area[addr] = value
}
Instruction::Fdef => {
// We should throw an exception here if the function definition list isn't big
// enough, but let's follow Postel's law.
//
// FIXME(pcwalton): Cap the size of the function definitions?
let id = try!(self.pop()) as usize;
if self.functions.len() < id + 1 {
self.functions.resize(id + 1, None)
}
let branch_target_index = self.scripts[frame.script]
.branch_targets
.binary_search_by(|script| {
script.branch_location.cmp(&frame.pc)
}).unwrap();
let end_pc = self.scripts[frame.script]
.branch_targets[branch_target_index]
.target_location;
self.functions[id] = Some(Frame::new(new_pc, end_pc, frame.script));
new_pc = end_pc + 1
}
_ => {
println!("TODO: {:?}", instruction);
}
}
// Advance the program counter.
self.call_stack.last_mut().unwrap().pc = new_pc;
}
}
#[inline]
fn pop(&mut self) -> Result<i32, HintingExecutionError> {
self.stack.pop().ok_or(HintingExecutionError::StackUnderflow)
}
}
pub struct Script<'a> {
bytecode: &'a [u8],
branch_targets: Vec<BranchTarget>,
}
impl<'a> ScriptInterpreter<'a> {
pub fn new<'b>(bytecode: &'b [u8]) -> Result<ScriptInterpreter<'b>, HintingAnalysisError> {
let mut interpreter = ScriptInterpreter {
impl<'a> Script<'a> {
pub fn new<'b>(bytecode: &'b [u8]) -> Result<Script<'b>, HintingAnalysisError> {
let mut interpreter = Script {
bytecode: bytecode,
branch_targets: vec![],
};
@ -28,6 +117,13 @@ impl<'a> ScriptInterpreter<'a> {
Ok(interpreter)
}
#[inline]
pub fn len(&self) -> usize {
self.bytecode.len()
}
// This is a little bit tricky because we have to maintain sorted order of the `branch_targets`
// array for future binary searches.
fn populate_branch_targets(&mut self) -> Result<(), HintingAnalysisError> {
let (mut pc, mut pending_branch_targets) = (0, vec![]);
loop {
@ -40,45 +136,46 @@ impl<'a> ScriptInterpreter<'a> {
match instruction {
Instruction::If | Instruction::Fdef | Instruction::Idef => {
pending_branch_targets.push((location, instruction))
pending_branch_targets.push((self.branch_targets.len(), instruction));
self.branch_targets.push(BranchTarget {
branch_location: location,
target_location: 0,
});
}
Instruction::Endf => {
match pending_branch_targets.pop() {
Some((branch_location, Instruction::Fdef)) |
Some((branch_location, Instruction::Idef)) => {
self.branch_targets.push(BranchTarget {
branch_location: branch_location,
target_location: location,
})
let (index, branch_instruction) = try!(pending_branch_targets.pop().ok_or(
HintingAnalysisError::BranchTargetMissingBranch));
match branch_instruction {
Instruction::Fdef | Instruction::Idef => {
self.branch_targets[index].target_location = location
}
Some(_) => return Err(HintingAnalysisError::MismatchedBranchInstruction),
None => return Err(HintingAnalysisError::BranchTargetMissingBranch),
_ => return Err(HintingAnalysisError::MismatchedBranchInstruction),
}
}
Instruction::Eif => {
match pending_branch_targets.pop() {
Some((branch_location, Instruction::If)) |
Some((branch_location, Instruction::Else)) => {
self.branch_targets.push(BranchTarget {
branch_location: branch_location,
target_location: location,
})
let (index, branch_instruction) = try!(pending_branch_targets.pop().ok_or(
HintingAnalysisError::BranchTargetMissingBranch));
match branch_instruction {
Instruction::If | Instruction::Else => {
self.branch_targets[index].target_location = location
}
Some(_) => return Err(HintingAnalysisError::MismatchedBranchInstruction),
None => return Err(HintingAnalysisError::BranchTargetMissingBranch),
_ => return Err(HintingAnalysisError::MismatchedBranchInstruction),
}
}
Instruction::Else => {
match pending_branch_targets.pop() {
Some((branch_location, Instruction::If)) => {
let (index, branch_instruction) = try!(pending_branch_targets.pop().ok_or(
HintingAnalysisError::BranchTargetMissingBranch));
match branch_instruction {
Instruction::If => {
self.branch_targets[index].target_location = location;
pending_branch_targets.push((self.branch_targets.len(), instruction));
self.branch_targets.push(BranchTarget {
branch_location: branch_location,
target_location: location,
branch_location: location,
target_location: 0,
});
pending_branch_targets.push((location, instruction))
}
Some(_) => return Err(HintingAnalysisError::MismatchedBranchInstruction),
None => return Err(HintingAnalysisError::BranchTargetMissingBranch),
_ => return Err(HintingAnalysisError::MismatchedBranchInstruction),
}
}
_ => {}
@ -93,6 +190,26 @@ impl<'a> ScriptInterpreter<'a> {
}
}
#[derive(Clone, Copy, Debug)]
pub struct Frame {
/// The current program counter.
pc: usize,
/// The PC at which to stop execution.
end: usize,
/// The index of the script.
script: usize,
}
impl Frame {
pub fn new(pc: usize, end: usize, script_index: usize) -> Frame {
Frame {
pc: pc,
end: end,
script: script_index,
}
}
}
#[derive(Clone, Copy, Debug)]
struct BranchTarget {
branch_location: usize,

View File

@ -15,20 +15,33 @@
//! See: https://www.microsoft.com/typography/otspec/ttinst.htm
use byteorder::{BigEndian, ByteOrder};
use error::HintingError;
use error::{HinterCreationError, HintingExecutionError};
use euclid::Point2D;
use font::Font;
use hinting::interp::ScriptInterpreter;
use hinting::interp::{Frame, Script};
mod insns;
mod interp;
const FONT_PROGRAM: usize = 0;
const CONTROL_VALUE_PROGRAM: usize = 1;
/// A TrueType hinting virtual machine.
pub struct Hinter {
pub struct Hinter<'a> {
// Scripts that we've analyzed so far.
scripts: Vec<Script<'a>>,
// The VM's evaluation stack.
stack: Vec<i32>,
// The VM's call stack.
call_stack: Vec<Frame>,
// The set of defined functions.
functions: Vec<Option<Frame>>,
// The Control Value Table: the VM's initialized memory.
control_value_table: Vec<i16>,
// The Storage Area: the VM's uninitialized memory.
storage_area: Vec<u32>,
storage_area: Vec<i32>,
// The current font size.
point_size: f32,
// The projection vector, in 2.14 fixed point.
projection_vector: Point2D<i16>,
// The dual projection vector, in 2.14 fixed point.
@ -69,12 +82,29 @@ pub struct Hinter {
graphics_state_flags: GraphicsStateFlags,
}
impl Hinter {
pub fn new(font: &Font) -> Result<Hinter, HintingError> {
impl<'a> Hinter<'a> {
pub fn new<'b>(font: &'b Font) -> Result<Hinter<'b>, HinterCreationError> {
let font_program = font.font_program();
let control_value_program = font.control_value_program();
let scripts = vec![
try!(Script::new(font_program).map_err(HinterCreationError::FontProgramAnalysisError)),
try!(Script::new(control_value_program).map_err(
HinterCreationError::ControlValueProgramAnalysisError)),
];
let cvt = font.control_value_table().chunks(2).map(BigEndian::read_i16).collect();
let hinter = Hinter {
// 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)];
let mut hinter = Hinter {
scripts: scripts,
stack: vec![],
call_stack: call_stack,
functions: vec![],
control_value_table: cvt,
storage_area: vec![],
point_size: 0.0,
projection_vector: Point2D::zero(),
dual_projection_vector: Point2D::zero(),
freedom_vector: Point2D::zero(),
@ -96,10 +126,19 @@ impl Hinter {
graphics_state_flags: AUTO_FLIP,
};
try!(ScriptInterpreter::new(font.font_program()).map_err(HintingError::AnalysisError));
try!(hinter.exec().map_err(HinterCreationError::FontProgramExecutionError));
Ok(hinter)
}
/// Sets the point size and reevaluates the control value program (`prep`).
pub fn set_point_size(&mut self, new_point_size: f32) -> Result<(), HintingExecutionError> {
self.point_size = new_point_size;
self.call_stack.push(Frame::new(0,
self.scripts[CONTROL_VALUE_PROGRAM].len(),
CONTROL_VALUE_PROGRAM));
self.exec()
}
}
#[derive(Copy, Clone, Debug)]