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