Skip to content

Commit

Permalink
[skrifa] tthint: instruction dispatch
Browse files Browse the repository at this point in the history
Based on #773, uses the decoder in that PR to handle dispatch to the currently implemented instructions.

No tests in this one. Will add in a later PR when more functionality has landed.
  • Loading branch information
dfrg committed Feb 7, 2024
1 parent 9839450 commit cf7cd30
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 5 deletions.
6 changes: 6 additions & 0 deletions read-fonts/src/tables/truetype/bytecode/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,12 @@ impl Opcode {
}
}

impl std::fmt::Display for Opcode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.name())
}
}

use Opcode::*;

const OPCODE_FROM_BYTE: [Opcode; 256] = [
Expand Down
181 changes: 181 additions & 0 deletions skrifa/src/outline/glyf/hint/engine/dispatch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
//! Instruction decoding and dispatch.

use read_fonts::tables::truetype::bytecode::Opcode;

use super::{Engine, HintError, HintErrorKind, Instruction};

impl<'a> Engine<'a> {
/// Decodes and dispatches all instructions until completion or error.
///
/// On success, returns the number of instructions executed.
pub fn run(&mut self) -> Result<usize, HintError> {
let mut count = 0;
while let Some(ins) = self.decode() {
self.dispatch(&ins?)?;
count += 1;
}
Ok(count)
}

/// Decodes the next instruction from the current program.
pub fn decode(&mut self) -> Option<Result<Instruction<'a>, HintError>> {
let ins = self.decoder.decode()?;
Some(ins.map_err(|_| HintError {
program: self.initial_program,
glyph_id: None,
pc: self.decoder.pc,
kind: HintErrorKind::UnexpectedEndOfBytecode,
}))
}

/// Executes the appropriate code for the given instruction.
pub fn dispatch(&mut self, ins: &Instruction) -> Result<(), HintError> {
let current_pc = self.decoder.pc;
let current_program = self.initial_program;
self.dispatch_inner(ins).map_err(|kind| HintError {
program: current_program,
glyph_id: None,
pc: current_pc,
kind,
})
}

fn dispatch_inner(&mut self, ins: &Instruction) -> Result<(), HintErrorKind> {
use Opcode::*;
let opcode = ins.opcode;
let raw_opcode = opcode as u8;
match ins.opcode {
SVTCA0 | SVTCA1 | SPVTCA0 | SPVTCA1 | SFVTCA0 | SFVTCA1 => {
self.op_svtca(ins.opcode as u8)?
}
SPVTL0 | SPVTL1 | SFVTL0 | SFVTL1 => self.op_svtl(raw_opcode)?,
SPVFS => self.op_spvfs()?,
SFVFS => self.op_sfvfs()?,
GPV => self.op_gpv()?,
GFV => self.op_gfv()?,
SFVTPV => self.op_sfvtpv()?,
// ISECT => {}
SRP0 => self.op_srp0()?,
SRP1 => self.op_srp1()?,
SRP2 => self.op_srp2()?,
SZP0 => self.op_szp0()?,
SZP1 => self.op_szp1()?,
SZP2 => self.op_szp2()?,
SZPS => self.op_szps()?,
SLOOP => self.op_sloop()?,
RTG => self.op_rtg()?,
RTHG => self.op_rthg()?,
SMD => self.op_smd()?,
// ELSE => {}
// JMPR => {}
SCVTCI => self.op_scvtci()?,
SSWCI => self.op_sswci()?,
DUP => self.op_dup()?,
POP => self.op_pop()?,
CLEAR => self.op_clear()?,
SWAP => self.op_swap()?,
DEPTH => self.op_depth()?,
CINDEX => self.op_cindex()?,
MINDEX => self.op_mindex()?,
// ALIGNPTS => {}
// ? 0x28
// UTP => {}
// LOOPCALL => {}
// CALL => {}
// FDEF => {}
// ENDF => {}
// MDAP0 | MDAP1 => {}
// IUP0 | IUP1 => {}
// SHP0 | SHP1 => {}
// SHC0 | SHC1 => {}
// SHZ0 | SHZ1 => {}
// SHPIX => {}
// IP => {}
// MSIRP0 | MISRP1 => {}
// ALIGNRP => {}
NPUSHB | NPUSHW => self.op_push(&ins.inline_operands)?,
// WS => {}
// RS => {}
// WCVTP => {}
// RCVT => {}
// SCFS => {}
// MD0 | MD1 => {}
// MPPEM => {}
// MPS => {}
FLIPON => self.op_flipon()?,
FLIPOFF => self.op_flipoff()?,
// DEBUG => {}
LT => self.op_lt()?,
LTEQ => self.op_lteq()?,
GT => self.op_gt()?,
GTEQ => self.op_gteq()?,
EQ => self.op_eq()?,
NEQ => self.op_neq()?,
ODD => self.op_odd()?,
EVEN => self.op_even()?,
// IF => {}
// EIF => {}
AND => self.op_and()?,
OR => self.op_or()?,
NOT => self.op_not()?,
// DELTAP1 => {}
SDB => self.op_sdb()?,
SDS => self.op_sds()?,
ADD => self.op_add()?,
SUB => self.op_sub()?,
DIV => self.op_div()?,
MUL => self.op_mul()?,
ABS => self.op_abs()?,
NEG => self.op_neg()?,
FLOOR => self.op_floor()?,
CEILING => self.op_ceiling()?,
// ROUND00 | ROUND01 | ROUND10 | ROUND11 => {}
// "No round" means do nothing :)
NROUND00 | NROUND01 | NROUND10 | NROUND11 => {}
// WCVTF => {}
// DELTAP2 | DELTAP3 => {}
// DELTAC1 | DELTAC2 | DELTAC3 => {}
SROUND => self.op_sround()?,
S45ROUND => self.op_s45round()?,
// JROT => {}
// JROF => {}
ROFF => self.op_roff()?,
// ? 0x7B
RUTG => self.op_rutg()?,
RDTG => self.op_rdtg()?,
SANGW => self.op_sangw()?,
// Unsupported instruction, do nothing
AA => {}
// FLIPPT => {}
// FLIPRGON => {}
// FLIPRGOFF => {}
// ? 0x83 | 0x84
SCANCTRL => self.op_scanctrl()?,
SDPVTL0 | SDPVTL1 => self.op_sdpvtl(raw_opcode)?,
// GETINFO => {}
// IDEF => {}
ROLL => self.op_roll()?,
MAX => self.op_max()?,
MIN => self.op_min()?,
SCANTYPE => self.op_scantype()?,
// ? 0x8F | 0x90 (ADJUST?)
// GETVARIATION => {}
// GETDATA => {}
_ => {
// FreeType handles MIRP, MDRP and pushes here.
// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L7629>
// if opcode >= MIRP00000 {
// self.op_mirp(raw_opcode)?
// } else if opcode >= MDRP00000 {
// self.op_mdrp(raw_opcode)?
// } else
if opcode >= PUSHB000 {
self.op_push(&ins.inline_operands)?;
} else {
return Err(HintErrorKind::UnhandledOpcode(opcode));
}
}
}
Ok(())
}
}
11 changes: 9 additions & 2 deletions skrifa/src/outline/glyf/hint/engine/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
//! TrueType bytecode interpreter.

mod arith;
mod dispatch;
mod graphics_state;
mod logical;
mod stack;

use read_fonts::tables::truetype::bytecode::{Decoder, Instruction};

use super::{
code_state::ProgramKind, error::HintErrorKind, graphics_state::GraphicsState,
code_state::ProgramKind,
error::{HintError, HintErrorKind},
graphics_state::GraphicsState,
value_stack::ValueStack,
};

Expand All @@ -17,14 +22,15 @@ pub struct Engine<'a> {
graphics_state: GraphicsState<'a>,
value_stack: ValueStack<'a>,
initial_program: ProgramKind,
decoder: Decoder<'a>,
}

#[cfg(test)]
use mock::MockEngine;

#[cfg(test)]
mod mock {
use super::{Engine, GraphicsState, ProgramKind, ValueStack};
use super::{Decoder, Engine, GraphicsState, ProgramKind, ValueStack};

/// Mock engine for testing.
pub(super) struct MockEngine {
Expand All @@ -43,6 +49,7 @@ mod mock {
graphics_state: GraphicsState::default(),
value_stack: ValueStack::new(&mut self.value_stack),
initial_program: ProgramKind::Font,
decoder: Decoder::new(&[], 0),
}
}
}
Expand Down
37 changes: 34 additions & 3 deletions skrifa/src/outline/glyf/hint/error.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
//! Hinting error definitions.

use read_fonts::tables::truetype::bytecode::DecodeError;
use read_fonts::tables::truetype::bytecode::{DecodeError, Opcode};

use super::code_state::ProgramKind;
use crate::GlyphId;

/// Errors that may occur when interpreting TrueType bytecode.
#[derive(Clone, Debug)]
pub enum HintErrorKind {
UnexpectedEndOfBytecode,
InvalidOpcode(u8),
UnhandledOpcode(Opcode),
DefinitionInGlyphProgram,
NestedDefinition,
InvalidDefintionIndex(usize),
Expand All @@ -30,7 +33,7 @@ impl core::fmt::Display for HintErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnexpectedEndOfBytecode => write!(f, "unexpected end of bytecode"),
Self::InvalidOpcode(opcode) => write!(f, "invalid instruction opcode {opcode}"),
Self::UnhandledOpcode(opcode) => write!(f, "unhandled instruction opcode {opcode}"),
Self::DefinitionInGlyphProgram => {
write!(f, "FDEF or IDEF instruction present in glyph program")
}
Expand Down Expand Up @@ -79,3 +82,31 @@ impl From<DecodeError> for HintErrorKind {
Self::UnexpectedEndOfBytecode
}
}

/// Hinting error with additional context.
#[derive(Clone, Debug)]
pub struct HintError {
pub program: ProgramKind,
pub glyph_id: Option<GlyphId>,
pub pc: usize,
pub kind: HintErrorKind,
}

impl core::fmt::Display for HintError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.program {
ProgramKind::ControlValue => write!(f, "prep")?,
ProgramKind::Font => write!(f, "fpgm")?,
ProgramKind::Glyph => {
write!(f, "glyf[")?;
if let Some(glyph_id) = self.glyph_id {
write!(f, "{}", glyph_id.to_u16())?;
} else {
write!(f, "?")?;
}
write!(f, "]")?
}
}
write!(f, "+{}: {}", self.pc, self.kind)
}
}

0 comments on commit cf7cd30

Please sign in to comment.