Begin to implement the interpreter
This commit is contained in:
parent
37736b27b9
commit
6d66cfc30a
|
@ -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
|
||||
};
|
||||
|
|
21
src/error.rs
21
src/error.rs
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Reference in New Issue