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() { fn main() {
let hint_arg = Arg::with_name("hint").short("H") let hint_arg = Arg::with_name("hint").short("H")
.long("hint") .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.)") let font_arg = Arg::with_name("FONT-FILE").help("Select the font file (`.ttf`, `.otf`, etc.)")
.required(true) .required(true)
.index(1); .index(1);
@ -27,8 +29,10 @@ fn main() {
unsafe { unsafe {
let font = Font::new(file.as_slice(), &mut buffer).unwrap(); let font = Font::new(file.as_slice(), &mut buffer).unwrap();
let hinter = if matches.is_present("hint") { let hinter = if let Some(point_size) = matches.value_of("hint") {
Some(Hinter::new(&font).unwrap()) let mut hinter = Hinter::new(&font).unwrap();
hinter.set_point_size(point_size.parse().unwrap()).unwrap();
Some(hinter)
} else { } else {
None None
}; };

View File

@ -124,13 +124,17 @@ pub enum GlyphStoreCreationError {
GlError(GlError), GlError(GlError),
} }
/// An error in hinting instruction evaluation. /// An error in construction of a hinter.
#[derive(Clone, Copy, PartialEq, Debug)] #[derive(Clone, Copy, PartialEq, Debug)]
pub enum HintingError { pub enum HinterCreationError {
/// A miscellaneous error occurred. /// A miscellaneous error occurred.
Failed, Failed,
/// An error was encountered during hinting program analysis. /// An error was encountered while analyzing the font program.
AnalysisError(HintingAnalysisError), 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. /// An error encountered during parsing of the TrueType hinting bytecode.
@ -160,3 +164,12 @@ pub enum HintingAnalysisError {
MismatchedBranchInstruction, 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, 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)] #[derive(Clone, Copy, PartialEq, Debug)]

View File

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

View File

@ -10,17 +10,106 @@
//! The TrueType interpreter. //! The TrueType interpreter.
use error::{HintingAnalysisError, HintingParseError}; use byteorder::{BigEndian, ByteOrder};
use error::{HintingAnalysisError, HintingExecutionError, HintingParseError};
use hinting::Hinter;
use hinting::insns::Instruction; 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], bytecode: &'a [u8],
branch_targets: Vec<BranchTarget>, branch_targets: Vec<BranchTarget>,
} }
impl<'a> ScriptInterpreter<'a> { impl<'a> Script<'a> {
pub fn new<'b>(bytecode: &'b [u8]) -> Result<ScriptInterpreter<'b>, HintingAnalysisError> { pub fn new<'b>(bytecode: &'b [u8]) -> Result<Script<'b>, HintingAnalysisError> {
let mut interpreter = ScriptInterpreter { let mut interpreter = Script {
bytecode: bytecode, bytecode: bytecode,
branch_targets: vec![], branch_targets: vec![],
}; };
@ -28,6 +117,13 @@ impl<'a> ScriptInterpreter<'a> {
Ok(interpreter) 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> { fn populate_branch_targets(&mut self) -> Result<(), HintingAnalysisError> {
let (mut pc, mut pending_branch_targets) = (0, vec![]); let (mut pc, mut pending_branch_targets) = (0, vec![]);
loop { loop {
@ -40,45 +136,46 @@ impl<'a> ScriptInterpreter<'a> {
match instruction { match instruction {
Instruction::If | Instruction::Fdef | Instruction::Idef => { 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 => { Instruction::Endf => {
match pending_branch_targets.pop() { let (index, branch_instruction) = try!(pending_branch_targets.pop().ok_or(
Some((branch_location, Instruction::Fdef)) | HintingAnalysisError::BranchTargetMissingBranch));
Some((branch_location, Instruction::Idef)) => { match branch_instruction {
self.branch_targets.push(BranchTarget { Instruction::Fdef | Instruction::Idef => {
branch_location: branch_location, self.branch_targets[index].target_location = location
target_location: location,
})
} }
Some(_) => return Err(HintingAnalysisError::MismatchedBranchInstruction), _ => return Err(HintingAnalysisError::MismatchedBranchInstruction),
None => return Err(HintingAnalysisError::BranchTargetMissingBranch),
} }
} }
Instruction::Eif => { Instruction::Eif => {
match pending_branch_targets.pop() { let (index, branch_instruction) = try!(pending_branch_targets.pop().ok_or(
Some((branch_location, Instruction::If)) | HintingAnalysisError::BranchTargetMissingBranch));
Some((branch_location, Instruction::Else)) => { match branch_instruction {
self.branch_targets.push(BranchTarget { Instruction::If | Instruction::Else => {
branch_location: branch_location, self.branch_targets[index].target_location = location
target_location: location,
})
} }
Some(_) => return Err(HintingAnalysisError::MismatchedBranchInstruction), _ => return Err(HintingAnalysisError::MismatchedBranchInstruction),
None => return Err(HintingAnalysisError::BranchTargetMissingBranch),
} }
} }
Instruction::Else => { Instruction::Else => {
match pending_branch_targets.pop() { let (index, branch_instruction) = try!(pending_branch_targets.pop().ok_or(
Some((branch_location, Instruction::If)) => { 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 { self.branch_targets.push(BranchTarget {
branch_location: branch_location, branch_location: location,
target_location: location, target_location: 0,
}); });
pending_branch_targets.push((location, instruction))
} }
Some(_) => return Err(HintingAnalysisError::MismatchedBranchInstruction), _ => return Err(HintingAnalysisError::MismatchedBranchInstruction),
None => return Err(HintingAnalysisError::BranchTargetMissingBranch),
} }
} }
_ => {} _ => {}
@ -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)] #[derive(Clone, Copy, Debug)]
struct BranchTarget { struct BranchTarget {
branch_location: usize, branch_location: usize,

View File

@ -15,20 +15,33 @@
//! See: https://www.microsoft.com/typography/otspec/ttinst.htm //! See: https://www.microsoft.com/typography/otspec/ttinst.htm
use byteorder::{BigEndian, ByteOrder}; use byteorder::{BigEndian, ByteOrder};
use error::HintingError; use error::{HinterCreationError, HintingExecutionError};
use euclid::Point2D; use euclid::Point2D;
use font::Font; use font::Font;
use hinting::interp::ScriptInterpreter; use hinting::interp::{Frame, Script};
mod insns; mod insns;
mod interp; mod interp;
const FONT_PROGRAM: usize = 0;
const CONTROL_VALUE_PROGRAM: usize = 1;
/// A TrueType hinting virtual machine. /// 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. // The Control Value Table: the VM's initialized memory.
control_value_table: Vec<i16>, control_value_table: Vec<i16>,
// The Storage Area: the VM's uninitialized memory. // 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. // The projection vector, in 2.14 fixed point.
projection_vector: Point2D<i16>, projection_vector: Point2D<i16>,
// The dual projection vector, in 2.14 fixed point. // The dual projection vector, in 2.14 fixed point.
@ -69,12 +82,29 @@ pub struct Hinter {
graphics_state_flags: GraphicsStateFlags, graphics_state_flags: GraphicsStateFlags,
} }
impl Hinter { impl<'a> Hinter<'a> {
pub fn new(font: &Font) -> Result<Hinter, HintingError> { 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 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, control_value_table: cvt,
storage_area: vec![], storage_area: vec![],
point_size: 0.0,
projection_vector: Point2D::zero(), projection_vector: Point2D::zero(),
dual_projection_vector: Point2D::zero(), dual_projection_vector: Point2D::zero(),
freedom_vector: Point2D::zero(), freedom_vector: Point2D::zero(),
@ -96,10 +126,19 @@ impl Hinter {
graphics_state_flags: AUTO_FLIP, graphics_state_flags: AUTO_FLIP,
}; };
try!(ScriptInterpreter::new(font.font_program()).map_err(HintingError::AnalysisError)); try!(hinter.exec().map_err(HinterCreationError::FontProgramExecutionError));
Ok(hinter) 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)] #[derive(Copy, Clone, Debug)]