diff --git a/examples/dump-outlines.rs b/examples/dump-outlines.rs index 50135494..2167e6aa 100644 --- a/examples/dump-outlines.rs +++ b/examples/dump-outlines.rs @@ -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 }; diff --git a/src/error.rs b/src/error.rs index c71d9eb5..60ded817 100644 --- a/src/error.rs +++ b/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, +} + diff --git a/src/font.rs b/src/font.rs index a091b9d3..a1894743 100644 --- a/src/font.rs +++ b/src/font.rs @@ -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)] diff --git a/src/hinting/insns.rs b/src/hinting/insns.rs index 2516a74d..69bafc59 100644 --- a/src/hinting/insns.rs +++ b/src/hinting/insns.rs @@ -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), diff --git a/src/hinting/interp.rs b/src/hinting/interp.rs index 3b8f98df..5888f024 100644 --- a/src/hinting/interp.rs +++ b/src/hinting/interp.rs @@ -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 { + self.stack.pop().ok_or(HintingExecutionError::StackUnderflow) + } +} + +pub struct Script<'a> { bytecode: &'a [u8], branch_targets: Vec, } -impl<'a> ScriptInterpreter<'a> { - pub fn new<'b>(bytecode: &'b [u8]) -> Result, HintingAnalysisError> { - let mut interpreter = ScriptInterpreter { +impl<'a> Script<'a> { + pub fn new<'b>(bytecode: &'b [u8]) -> Result, 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, diff --git a/src/hinting/mod.rs b/src/hinting/mod.rs index eb87f056..d4be4dc0 100644 --- a/src/hinting/mod.rs +++ b/src/hinting/mod.rs @@ -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>, + // The VM's evaluation stack. + stack: Vec, + // The VM's call stack. + call_stack: Vec, + // The set of defined functions. + functions: Vec>, // The Control Value Table: the VM's initialized memory. control_value_table: Vec, // The Storage Area: the VM's uninitialized memory. - storage_area: Vec, + storage_area: Vec, + // The current font size. + point_size: f32, // The projection vector, in 2.14 fixed point. projection_vector: Point2D, // 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 { +impl<'a> Hinter<'a> { + pub fn new<'b>(font: &'b Font) -> Result, 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)]