Analyze branch targets in TrueType instructions

This commit is contained in:
Patrick Walton 2017-02-24 09:38:25 -08:00
parent 9c07c1a4a6
commit 88d1e3163d
4 changed files with 149 additions and 24 deletions

View File

@ -125,9 +125,38 @@ pub enum GlyphStoreCreationError {
}
/// An error in hinting instruction evaluation.
#[derive(Debug)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum HintingError {
/// A miscellaneous error occurred.
Failed,
/// An error was encountered during hinting program analysis.
AnalysisError(HintingAnalysisError),
}
/// An error encountered during parsing of the TrueType hinting bytecode.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum HintingParseError {
/// The instruction stream terminated normally.
Eof,
/// The instruction stream terminated abnormally.
UnexpectedEof,
/// An unexpected opcode was encountered.
UnknownOpcode,
/// An unexpected value was encountered for `DistanceType`.
InvalidDistanceType,
}
/// An error encountered during semantic analysis of the TrueType hinting bytecode.
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum HintingAnalysisError {
/// An error occurred while parsing the instruction stream.
ParseError(HintingParseError),
/// A branch target (e.g. `Eif`) was found without a corresponding branch instruction.
BranchTargetMissingBranch,
/// A branch target was (e.g. `If`) was found without a corresponding branch target (e.g.
/// `Eif`).
BranchMissingBranchTarget,
/// A branch target was mismatched with its branch instruction (`Eif` vs. `If`, etc.)
MismatchedBranchInstruction,
}

View File

@ -10,6 +10,8 @@
//! TrueType instructions.
use error::HintingParseError;
/// All TrueType instructions.
#[derive(Clone, Copy, Debug)]
pub enum Instruction<'a> {
@ -182,7 +184,7 @@ pub enum Instruction<'a> {
/// Else (0x1b) (ttinst2.doc, 315)
Else,
/// End If (0x59) (ttinst2.doc, 316)
EIf,
Eif,
/// Jump Relative on True (0x78) (ttinst2.doc, 317-318)
Jrot,
/// Jump (0x1c) (ttinst2.doc, 319)
@ -256,12 +258,12 @@ pub enum Instruction<'a> {
impl<'a> Instruction<'a> {
#[inline]
pub fn parse<'b, 'c>(data: &'b [u8], pc: &'c mut usize)
-> Result<Instruction<'b>, ParseError> {
let op = try!(get(data, pc).ok_or(ParseError::Eof));
-> Result<Instruction<'b>, HintingParseError> {
let op = try!(get(data, pc).ok_or(HintingParseError::Eof));
match op {
0x40 | 0xb0...0xb7 => {
let count = if op == 0x40 {
try!(get(data, pc).ok_or(ParseError::UnexpectedEof)) as usize
try!(get(data, pc).ok_or(HintingParseError::UnexpectedEof)) as usize
} else {
(op as usize & 7) + 1
};
@ -270,12 +272,12 @@ impl<'a> Instruction<'a> {
*pc += count;
Ok(insn)
} else {
Err(ParseError::UnexpectedEof)
Err(HintingParseError::UnexpectedEof)
}
}
0x41 | 0xb8...0xbf => {
let count = if op == 0x41 {
try!(get(data, pc).ok_or(ParseError::UnexpectedEof)) as usize * 2
try!(get(data, pc).ok_or(HintingParseError::UnexpectedEof)) as usize * 2
} else {
((op as usize & 7) + 1) * 2
};
@ -284,7 +286,7 @@ impl<'a> Instruction<'a> {
*pc += count;
Ok(insn)
} else {
Err(ParseError::UnexpectedEof)
Err(HintingParseError::UnexpectedEof)
}
}
0x43 => Ok(Instruction::Rs),
@ -385,7 +387,7 @@ impl<'a> Instruction<'a> {
0x8a => Ok(Instruction::Roll),
0x58 => Ok(Instruction::If),
0x1b => Ok(Instruction::Else),
0x59 => Ok(Instruction::EIf),
0x59 => Ok(Instruction::Eif),
0x78 => Ok(Instruction::Jrot),
0x1c => Ok(Instruction::Jmpr),
0x79 => Ok(Instruction::Jrof),
@ -420,7 +422,7 @@ impl<'a> Instruction<'a> {
0x4f => Ok(Instruction::Debug),
0x88 => Ok(Instruction::Getinfo),
0x91 => Ok(Instruction::Getvariation),
_ => Err(ParseError::UnknownOpcode),
_ => Err(HintingParseError::UnknownOpcode),
}
}
}
@ -484,25 +486,13 @@ pub enum DistanceType {
}
impl DistanceType {
fn parse(value: u8) -> Result<DistanceType, ParseError> {
fn parse(value: u8) -> Result<DistanceType, HintingParseError> {
match value {
0 => Ok(DistanceType::Gray),
1 => Ok(DistanceType::Black),
2 => Ok(DistanceType::White),
_ => Err(ParseError::InvalidDistanceType),
_ => Err(HintingParseError::InvalidDistanceType),
}
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ParseError {
/// The instruction stream terminated normally.
Eof,
/// The instruction stream terminated abnormally.
UnexpectedEof,
/// An unexpected opcode was encountered.
UnknownOpcode,
/// An unexpected value was encountered for `DistanceType`.
InvalidDistanceType,
}

102
src/hinting/interp.rs Normal file
View File

@ -0,0 +1,102 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! The TrueType interpreter.
use error::{HintingAnalysisError, HintingParseError};
use hinting::insns::Instruction;
pub struct ScriptInterpreter<'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 {
bytecode: bytecode,
branch_targets: vec![],
};
try!(interpreter.populate_branch_targets());
println!("{:#?}", interpreter.branch_targets);
Ok(interpreter)
}
fn populate_branch_targets(&mut self) -> Result<(), HintingAnalysisError> {
let (mut pc, mut pending_branch_targets) = (0, vec![]);
loop {
let location = pc;
let instruction = match Instruction::parse(self.bytecode, &mut pc) {
Ok(instruction) => instruction,
Err(HintingParseError::Eof) => break,
Err(err) => return Err(HintingAnalysisError::ParseError(err)),
};
match instruction {
Instruction::If | Instruction::Fdef | Instruction::Idef => {
pending_branch_targets.push((location, instruction))
}
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,
})
}
Some(_) => return Err(HintingAnalysisError::MismatchedBranchInstruction),
None => return Err(HintingAnalysisError::BranchTargetMissingBranch),
}
}
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,
})
}
Some(_) => return Err(HintingAnalysisError::MismatchedBranchInstruction),
None => return Err(HintingAnalysisError::BranchTargetMissingBranch),
}
}
Instruction::Else => {
match pending_branch_targets.pop() {
Some((branch_location, Instruction::If)) => {
self.branch_targets.push(BranchTarget {
branch_location: branch_location,
target_location: location,
});
pending_branch_targets.push((location, instruction))
}
Some(_) => return Err(HintingAnalysisError::MismatchedBranchInstruction),
None => return Err(HintingAnalysisError::BranchTargetMissingBranch),
}
}
_ => {}
}
}
if pending_branch_targets.is_empty() {
Ok(())
} else {
Err(HintingAnalysisError::BranchMissingBranchTarget)
}
}
}
#[derive(Clone, Copy, Debug)]
struct BranchTarget {
branch_location: usize,
target_location: usize,
}

View File

@ -18,8 +18,10 @@ use byteorder::{BigEndian, ByteOrder};
use error::HintingError;
use euclid::Point2D;
use font::Font;
use hinting::interp::ScriptInterpreter;
mod insns;
mod interp;
/// A TrueType hinting virtual machine.
pub struct Hinter {
@ -94,6 +96,8 @@ impl Hinter {
graphics_state_flags: AUTO_FLIP,
};
try!(ScriptInterpreter::new(font.font_program()).map_err(HintingError::AnalysisError));
Ok(hinter)
}
}