use crate::model::Error;
use crate::{
model::glyph::outline::PathSink,
ps::{
cff::{blend::BlendState, charset::Charset, index::Index, stack::Stack},
num,
string::Sid,
transform::{FontMatrix, Transform},
},
tables::cff::Cff,
types::{BoundingBox, Fixed, Point},
Cursor, FontData, FontRead,
};
pub const NESTING_DEPTH_LIMIT: u32 = 10;
const MAX_OPERATIONS: u32 = 1_000_000;
const STORAGE_SIZE: usize = 32;
const INLINE_BUILD_CHAR_LEN: usize = 32;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum CharstringKind {
Type1,
Type2,
}
pub trait CharstringContext {
fn kind(&self) -> CharstringKind;
fn seac_components(&self, base_code: i32, accent_code: i32) -> Result<[&[u8]; 2], Error>;
fn global_subr(&self, index: i32) -> Result<&[u8], Error>;
fn subr(&self, index: i32) -> Result<&[u8], Error>;
fn weight_vector(&self) -> &[Fixed] {
&[]
}
fn build_char_len(&self) -> usize {
0
}
}
impl<'a> CharstringContext for (&'a [u8], &'a Index<'a>, &'a Index<'a>, &'a Index<'a>) {
fn kind(&self) -> CharstringKind {
CharstringKind::Type2
}
fn seac_components(&self, base_code: i32, accent_code: i32) -> Result<[&[u8]; 2], Error> {
let cff = Cff::read(FontData::new(self.0))?;
let charset = cff
.charset(0)
.or_else(|| Charset::new(FontData::default(), 0, self.1.count()))
.ok_or(Error::MissingResource)?;
let seac_to_gid = |code: i32| {
let code: u8 = code.try_into().ok()?;
let sid = *super::encoding::STANDARD_ENCODING.get(code as usize)?;
charset.glyph_id(Sid::new(sid as u16))
};
let accent_gid = seac_to_gid(accent_code).ok_or(Error::InvalidOperation)?;
let base_gid = seac_to_gid(base_code).ok_or(Error::InvalidOperation)?;
let accent_charstring = self
.1
.get(accent_gid.to_u32() as usize)
.ok_or(Error::Malformed)?;
let base_charstring = self
.1
.get(base_gid.to_u32() as usize)
.ok_or(Error::Malformed)?;
Ok([base_charstring, accent_charstring])
}
fn global_subr(&self, index: i32) -> Result<&[u8], Error> {
self.2
.get((index + self.2.subr_bias()) as usize)
.ok_or(Error::Malformed)
}
fn subr(&self, index: i32) -> Result<&[u8], Error> {
self.3
.get((index + self.3.subr_bias()) as usize)
.ok_or(Error::Malformed)
}
}
#[allow(unused_variables)]
pub trait CommandSink {
fn move_to(&mut self, x: Fixed, y: Fixed);
fn line_to(&mut self, x: Fixed, y: Fixed);
fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed);
fn close(&mut self);
fn hstem(&mut self, y: Fixed, dy: Fixed) {}
fn vstem(&mut self, x: Fixed, dx: Fixed) {}
fn hint_mask(&mut self, mask: &[u8]) {}
fn counter_mask(&mut self, mask: &[u8]) {}
fn clear_hints(&mut self) {}
fn finish(&mut self) {}
}
pub struct NullSink;
impl CommandSink for NullSink {
fn move_to(&mut self, _x: Fixed, _y: Fixed) {}
fn line_to(&mut self, _x: Fixed, _y: Fixed) {}
fn curve_to(
&mut self,
_cx0: Fixed,
_cy0: Fixed,
_cx1: Fixed,
_cy1: Fixed,
_x: Fixed,
_y: Fixed,
) {
}
fn close(&mut self) {}
}
#[derive(Clone, Default, Debug)]
pub struct ControlBoundsSink {
bounds: Option<BoundingBox<Fixed>>,
}
impl ControlBoundsSink {
pub fn new() -> Self {
Self::default()
}
pub fn bounding_box(&self) -> Option<BoundingBox<Fixed>> {
self.bounds
}
fn update_bounds(&mut self, x: Fixed, y: Fixed) {
self.bounds = Some(match self.bounds {
Some(b) => BoundingBox {
x_min: b.x_min.min(x),
y_min: b.y_min.min(y),
x_max: b.x_max.max(x),
y_max: b.y_max.max(y),
},
None => BoundingBox {
x_min: x,
y_min: y,
x_max: x,
y_max: y,
},
});
}
}
impl CommandSink for ControlBoundsSink {
fn move_to(&mut self, x: Fixed, y: Fixed) {
self.update_bounds(x, y);
}
fn line_to(&mut self, x: Fixed, y: Fixed) {
self.update_bounds(x, y);
}
fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
self.update_bounds(cx0, cy0);
self.update_bounds(cx1, cy1);
self.update_bounds(x, y);
}
fn close(&mut self) {}
}
pub fn evaluate<'a>(
context: &'a impl CharstringContext,
blend_state: Option<BlendState<'a>>,
charstring_data: &[u8],
sink: &'a mut impl CommandSink,
) -> Result<Option<Fixed>, Error> {
let mut evaluator = Evaluator::new(context, blend_state, sink);
evaluator.evaluate(charstring_data)?;
let width = evaluator.have_read_width.then_some(evaluator.wx);
sink.finish();
Ok(width)
}
pub fn evaluate_width<'a>(
context: &'a impl CharstringContext,
blend_state: Option<BlendState<'a>>,
charstring_data: &[u8],
) -> Result<Option<Fixed>, Error> {
let mut sink = NullSink;
let mut evaluator = Evaluator::new(context, blend_state, &mut sink);
evaluator.width_only = true;
evaluator.evaluate(charstring_data)?;
Ok(evaluator.have_read_width.then_some(evaluator.wx))
}
fn fixed_sqrt(value: Fixed) -> Fixed {
if value.to_bits() <= 0 {
return Fixed::ZERO;
}
let mut root = if value.to_bits() < 10 {
value
} else {
Fixed::from_bits(value.to_bits() >> 1)
};
loop {
let next = Fixed::from_bits((root.to_bits() + (value / root).to_bits() + 1) >> 1);
if next == root {
return next;
}
root = next;
}
}
#[derive(PartialEq)]
enum SeacMode {
Explicit,
Implicit,
}
struct Evaluator<'a> {
context: &'a dyn CharstringContext,
is_type1: bool,
blend_state: Option<BlendState<'a>>,
sink: &'a mut dyn CommandSink,
is_open: bool,
is_flexing: bool,
seen_width_command: bool,
width_only: bool,
have_read_width: bool,
stem_count: usize,
x: Fixed,
y: Fixed,
sbx: Fixed,
wx: Fixed,
stack: Stack,
stack_ix: usize,
in_seac: bool,
ops_done: u32,
storage: [Fixed; STORAGE_SIZE],
build_char: smallvec::SmallVec<[Fixed; INLINE_BUILD_CHAR_LEN]>,
}
impl<'a> Evaluator<'a> {
fn new(
context: &'a dyn CharstringContext,
blend_state: Option<BlendState<'a>>,
sink: &'a mut dyn CommandSink,
) -> Self {
let is_type1 = context.kind() == CharstringKind::Type1;
Self {
context,
is_type1,
blend_state,
sink,
is_open: false,
is_flexing: false,
seen_width_command: false,
width_only: false,
have_read_width: false,
stem_count: 0,
stack: Stack::new(),
x: Fixed::ZERO,
y: Fixed::ZERO,
sbx: Fixed::ZERO,
wx: Fixed::ZERO,
stack_ix: 0,
in_seac: false,
ops_done: 0,
storage: [Fixed::ZERO; STORAGE_SIZE],
build_char: smallvec::SmallVec::new(),
}
}
fn evaluate(&mut self, charstring_data: &[u8]) -> Result<(), Error> {
let seen_endchar = self.evaluate_impl(charstring_data, 0)?;
if !self.is_type1 && !seen_endchar {
self.evaluate_operator(
Operator::EndChar,
&mut crate::FontData::default().cursor(),
0,
)?;
}
if self.is_open {
self.sink.close();
}
Ok(())
}
fn evaluate_impl(&mut self, charstring_data: &[u8], nesting_depth: u32) -> Result<bool, Error> {
if nesting_depth > NESTING_DEPTH_LIMIT {
return Err(Error::LimitExceeded);
}
let mut cursor = crate::FontData::new(charstring_data).cursor();
let mut seen_endchar = false;
while cursor.remaining_bytes() != 0 {
self.ops_done += 1;
if self.ops_done > MAX_OPERATIONS {
return Err(Error::LimitExceeded);
}
let b0 = cursor.read::<u8>()?;
match b0 {
28 | 32..=254 => {
self.stack.push(num::parse_int(&mut cursor, b0)?)?;
}
255 => {
let val = cursor.read::<i32>()?;
if self.is_type1 {
self.stack.push(val)?;
} else {
self.stack.push(Fixed::from_bits(val))?;
}
}
_ => {
if let Some(operator) = Operator::read(&mut cursor, b0) {
seen_endchar |= operator == Operator::EndChar;
if !self.evaluate_operator(operator, &mut cursor, nesting_depth)? {
break;
}
if self.width_only && self.seen_width_command {
break;
}
} else {
self.reset_stack();
}
}
}
}
Ok(seen_endchar)
}
fn evaluate_operator(
&mut self,
operator: Operator,
cursor: &mut Cursor,
nesting_depth: u32,
) -> Result<bool, Error> {
use Operator::*;
use PointMode::*;
match operator {
Flex => {
self.emit_curves([DxDy; 6])?;
self.reset_stack();
}
HFlex => {
self.emit_curves([DxY, DxDy, DxY, DxY, DxInitialY, DxY])?;
self.reset_stack();
}
HFlex1 => {
self.emit_curves([DxDy, DxDy, DxY, DxY, DxDy, DxInitialY])?;
self.reset_stack();
}
Flex1 => {
self.emit_curves([DxDy, DxDy, DxDy, DxDy, DxDy, DLargerCoordDist])?;
self.reset_stack();
}
VariationStoreIndex => {
if !self.is_type1 {
let blend_state = self.blend_state.as_mut().ok_or(Error::MissingResource)?;
let store_index = self.stack.pop_i32()? as u16;
blend_state.set_store_index(store_index)?;
}
}
Blend => {
if !self.is_type1 {
let blend_state = self.blend_state.as_ref().ok_or(Error::MissingResource)?;
self.stack.apply_blend(blend_state)?;
}
}
Return => {
return Ok(false);
}
EndChar => {
let stack_len = self.stack.len();
if (stack_len == 1 || stack_len == 5) && !self.seen_width_command {
self.read_width()?;
}
self.seen_width_command = true;
if stack_len > 1 && !self.width_only {
self.handle_seac(SeacMode::Implicit, nesting_depth)?;
}
return Ok(false);
}
HStem | VStem | HStemHm | VStemHm => {
let mut i = 0;
let len = if self.stack.len_is_odd() && !self.seen_width_command {
self.read_width()?;
i = 1;
self.stack.len() - 1
} else {
self.stack.len()
};
self.seen_width_command = true;
let is_horizontal = matches!(operator, HStem | HStemHm);
let mut u = Fixed::ZERO;
while i < self.stack.len() {
let args = self.stack.fixed_array::<2>(i)?;
u += args[0];
let w = args[1];
let v = u.wrapping_add(w);
if is_horizontal {
self.sink.hstem(u, v);
} else {
self.sink.vstem(u, v);
}
u = v;
i += 2;
}
self.stem_count += len / 2;
self.reset_stack();
}
HintMask | CntrMask => {
let mut i = 0;
let len = if self.stack.len_is_odd() && !self.seen_width_command {
self.read_width()?;
i = 1;
self.stack.len() - 1
} else {
self.stack.len()
};
self.seen_width_command = true;
let mut u = Fixed::ZERO;
while i < self.stack.len() {
let args = self.stack.fixed_array::<2>(i)?;
u += args[0];
let w = args[1];
let v = u + w;
self.sink.vstem(u, v);
u = v;
i += 2;
}
self.stem_count += len / 2;
let count = self.stem_count.div_ceil(8);
let mask = cursor.read_array::<u8>(count)?;
if operator == HintMask {
self.sink.hint_mask(mask);
} else {
self.sink.counter_mask(mask);
}
self.reset_stack();
}
RMoveTo => {
if self.stack.len() > 2 && !self.seen_width_command {
self.read_width()?;
}
self.seen_width_command = true;
if !self.is_flexing {
let dy = self.stack.pop_fixed()?;
let dx = self.stack.pop_fixed()?;
self.x += dx;
self.y += dy;
if !self.is_open {
self.is_open = true;
} else {
self.sink.close();
}
self.sink.move_to(self.x, self.y);
self.reset_stack();
}
}
HMoveTo | VMoveTo => {
if self.stack.len() > 1 && !self.seen_width_command {
self.read_width()?;
}
self.seen_width_command = true;
if self.is_flexing {
self.stack.push(0)?;
if operator == VMoveTo {
self.stack.exch()?;
}
} else {
let delta = self.stack.pop_fixed()?;
if operator == HMoveTo {
self.x += delta;
} else {
self.y += delta;
}
if !self.is_open {
self.is_open = true;
} else {
self.sink.close();
}
self.sink.move_to(self.x, self.y);
self.reset_stack();
}
}
RLineTo => {
let mut i = 0;
while i < self.stack.len() {
let [dx, dy] = self.stack.fixed_array::<2>(i)?;
self.x += dx;
self.y += dy;
self.emit_line(self.x, self.y);
i += 2;
}
self.reset_stack();
}
HLineTo | VLineTo => {
let mut is_x = operator == HLineTo;
for i in 0..self.stack.len() {
let delta = self.stack.get_fixed(i)?;
if is_x {
self.x += delta;
} else {
self.y += delta;
}
is_x = !is_x;
self.emit_line(self.x, self.y);
}
self.reset_stack();
}
HhCurveTo => {
let count1 = self.stack.len();
let count = count1 & !2;
self.stack_ix = count1 - count;
while self.stack_ix < count {
if (count - self.stack_ix) & 1 != 0 {
self.y += self.stack.get_fixed(self.stack_ix)?;
self.stack_ix += 1;
}
self.emit_curves([DxY, DxDy, DxY])?;
}
self.reset_stack();
}
HvCurveTo | VhCurveTo => {
let count1 = self.stack.len();
let count = count1 & !2;
let mut is_horizontal = operator == HvCurveTo;
self.stack_ix = count1 - count;
while self.stack_ix < count {
let do_last_delta = count - self.stack_ix == 5;
if is_horizontal {
self.emit_curves([DxY, DxDy, MaybeDxDy(do_last_delta)])?;
} else {
self.emit_curves([XDy, DxDy, DxMaybeDy(do_last_delta)])?;
}
is_horizontal = !is_horizontal;
}
self.reset_stack();
}
RrCurveTo | RCurveLine => {
while self.coords_remaining() >= 6 {
self.emit_curves([DxDy; 3])?;
}
if operator == RCurveLine {
let [dx, dy] = self.stack.fixed_array::<2>(self.stack_ix)?;
self.x += dx;
self.y += dy;
self.emit_line(self.x, self.y);
}
self.reset_stack();
}
RLineCurve => {
while self.coords_remaining() > 6 {
let [dx, dy] = self.stack.fixed_array::<2>(self.stack_ix)?;
self.x += dx;
self.y += dy;
self.emit_line(self.x, self.y);
self.stack_ix += 2;
}
while self.coords_remaining() >= 6 {
self.emit_curves([DxDy; 3])?;
}
self.reset_stack();
}
VvCurveTo => {
let count1 = self.stack.len();
let count = count1 & !2;
self.stack_ix = count1 - count;
while self.stack_ix < count {
if (count - self.stack_ix) & 1 != 0 {
self.x += self.stack.get_fixed(self.stack_ix)?;
self.stack_ix += 1;
}
self.emit_curves([XDy, DxDy, XDy])?;
}
self.reset_stack();
}
CallSubr | CallGsubr => {
let index = self.stack.pop_i32()?;
let subr_charstring = if operator == CallSubr {
self.context.subr(index)?
} else {
self.context.global_subr(index)?
};
self.evaluate_impl(subr_charstring, nesting_depth + 1)?;
}
Hsbw => {
if self.is_type1 {
let [sbx, wx] = self.stack.fixed_array(0)?;
self.sbx += sbx;
self.x += sbx;
self.wx = wx;
self.seen_width_command = true;
self.have_read_width = true;
self.reset_stack();
}
}
Seac => {
if !self.width_only {
self.handle_seac(SeacMode::Explicit, nesting_depth)?;
}
}
Sbw => {
if self.is_type1 {
let [x, y, wx, _wy] = self.stack.fixed_array(0)?;
self.x += x;
self.y += y;
self.sbx += x;
self.wx = wx;
self.seen_width_command = true;
self.have_read_width = true;
self.reset_stack();
}
}
DotSection => {
}
HStem3 | VStem3 => {
self.reset_stack();
}
Div => {
self.stack.div(self.is_type1)?;
}
And | Or | Eq => {
let b = self.stack.pop_fixed()?;
let a = self.stack.pop_fixed()?;
let answer = match operator {
And => a != Fixed::ZERO && b != Fixed::ZERO,
Or => a != Fixed::ZERO || b != Fixed::ZERO,
_ => a == b,
};
self.stack.push(answer as i32)?;
}
Not => {
let a = self.stack.pop_fixed()?;
self.stack.push((a == Fixed::ZERO) as i32)?;
}
Abs | Neg => {
let a = self.stack.pop_fixed()?;
let answer = if a.to_bits() < -Fixed::MAX.to_bits() {
Fixed::MAX
} else if operator == Abs {
Fixed::from_bits(a.to_bits().abs())
} else {
Fixed::from_bits(-a.to_bits())
};
self.stack.push(answer)?;
}
Add | Sub => {
let b = self.stack.pop_fixed()?;
let a = self.stack.pop_fixed()?;
let answer = if operator == Add {
a.to_bits().wrapping_add(b.to_bits())
} else {
a.to_bits().wrapping_sub(b.to_bits())
};
self.stack.push(Fixed::from_bits(answer))?;
}
Mul => {
let b = self.stack.pop_fixed()?;
let a = self.stack.pop_fixed()?;
self.stack.push(a * b)?;
}
Sqrt => {
let a = self.stack.pop_fixed()?;
self.stack.push(fixed_sqrt(a))?;
}
Drop => {
self.stack.pop_fixed()?;
}
Dup => {
let a = self.stack.pop_fixed()?;
self.stack.push(a)?;
self.stack.push(a)?;
}
Exch => {
self.stack.exch()?;
}
Index => {
let index = self.stack.pop_i32()?;
let len = self.stack.len();
if len > 0 {
let from_bottom = if index < 0 {
len - 1
} else if index as usize >= len {
0
} else {
len - 1 - index as usize
};
let value = self.stack.get_fixed(from_bottom)?;
self.stack.push(value)?;
}
}
Roll => {
let shift = self.stack.pop_i32()?;
let count = self.stack.pop_i32()?;
self.stack.roll(count, shift)?;
}
Put => {
let index = self.stack.pop_i32()?;
let value = self.stack.pop_fixed()?;
if let Some(slot) = usize::try_from(index)
.ok()
.and_then(|i| self.storage.get_mut(i))
{
*slot = value;
}
}
Get => {
let index = self.stack.pop_i32()?;
if let Some(value) = usize::try_from(index)
.ok()
.and_then(|i| self.storage.get(i))
{
self.stack.push(*value)?;
}
}
IfElse => {
let cond2 = self.stack.pop_fixed()?;
let cond1 = self.stack.pop_fixed()?;
let arg2 = self.stack.pop_fixed()?;
let arg1 = self.stack.pop_fixed()?;
self.stack.push(if cond1 <= cond2 { arg1 } else { arg2 })?;
}
CallOtherSubr => {
let subr_idx = self.stack.pop_i32()?;
let num_args = self.stack.pop_i32()? as usize;
let weight_vector = self.context.weight_vector();
match (subr_idx, num_args) {
(0, 3) => {
self.is_flexing = false;
self.ensure_open();
self.handle_flex()?;
}
(1, 0) => {
self.is_flexing = true;
}
(12 | 13, _) => {
self.reset_stack();
}
(14..=18, _) => {
if weight_vector.is_empty() {
return Err(Error::InvalidOperation);
}
self.handle_mm_blend(subr_idx, num_args)?;
}
(19..=25 | 27, _) => {
self.handle_build_char(subr_idx, num_args)?;
}
_ => {
self.stack.drop(num_args);
}
}
}
Pop => {
}
SetCurrentPoint => {
if self.is_type1 {
let [x, y] = self.stack.fixed_array(0)?;
self.x = x;
self.y = y;
self.reset_stack();
}
}
}
Ok(true)
}
fn read_width(&mut self) -> Result<(), Error> {
self.wx = self.stack.get_fixed(0)?;
self.seen_width_command = true;
self.have_read_width = true;
Ok(())
}
fn handle_seac(&mut self, mode: SeacMode, nesting_depth: u32) -> Result<(), Error> {
if self.in_seac {
return Err(Error::LimitExceeded);
}
self.in_seac = true;
let accent_code = self.stack.pop_i32()?;
let base_code = self.stack.pop_i32()?;
let [base_charstring, accent_charstring] =
self.context.seac_components(base_code, accent_code)?;
let dy = self.stack.pop_fixed()?;
let dx = self.stack.pop_fixed()?;
let sb = if self.is_type1 {
self.stack.pop_fixed()?
} else if !self.stack.is_empty() && !self.seen_width_command {
self.wx = self.stack.pop_fixed()?;
self.seen_width_command = true;
Fixed::ZERO
} else {
Fixed::ZERO
};
let mut sbx = self.sbx;
let mut wx = self.wx;
let seen_width = self.seen_width_command;
let read_width = self.have_read_width;
struct Component<'a> {
charstring: &'a [u8],
x: Fixed,
y: Fixed,
maybe_use_metrics: bool,
}
let x = self.x;
let y = self.y;
let [bx, by] = if mode == SeacMode::Explicit {
[Fixed::ZERO; 2]
} else {
[x, y]
};
let mut components = [
Component {
charstring: base_charstring,
x: bx,
y: by,
maybe_use_metrics: mode == SeacMode::Explicit,
},
Component {
charstring: accent_charstring,
x: dx + self.sbx - sb,
y: dy,
maybe_use_metrics: false,
},
];
if mode == SeacMode::Implicit {
components.swap(0, 1);
}
for component in components {
self.reset_stack();
self.seen_width_command = false;
self.sink.clear_hints();
self.stem_count = 0;
self.x = component.x;
self.y = component.y;
self.evaluate_impl(component.charstring, nesting_depth + 1)?;
if component.maybe_use_metrics && !seen_width {
sbx = self.sbx;
wx = self.wx;
}
}
self.seen_width_command = seen_width;
self.have_read_width = read_width;
self.sbx = sbx;
self.wx = wx;
self.in_seac = false;
Ok(())
}
fn handle_flex(&mut self) -> Result<(), Error> {
let final_y = self.stack.pop_fixed()?;
let final_x = self.stack.pop_fixed()?;
let _ = self.stack.pop_fixed()?;
let p3y = self.stack.pop_fixed()?;
let p3x = self.stack.pop_fixed()?;
let bcp4y = self.stack.pop_fixed()?;
let bcp4x = self.stack.pop_fixed()?;
let bcp3y = self.stack.pop_fixed()?;
let bcp3x = self.stack.pop_fixed()?;
let p2y = self.stack.pop_fixed()?;
let p2x = self.stack.pop_fixed()?;
let bcp2y = self.stack.pop_fixed()?;
let bcp2x = self.stack.pop_fixed()?;
let bcp1y = self.stack.pop_fixed()?;
let bcp1x = self.stack.pop_fixed()?;
let rpy = self.stack.pop_fixed()?;
let rpx = self.stack.pop_fixed()?;
self.reset_stack();
self.stack.push(bcp1x + rpx)?;
self.stack.push(bcp1y + rpy)?;
self.stack.push(bcp2x)?;
self.stack.push(bcp2y)?;
self.stack.push(p2x)?;
self.stack.push(p2y)?;
self.emit_curves([PointMode::DxDy; 3])?;
self.reset_stack();
self.stack.push(bcp3x)?;
self.stack.push(bcp3y)?;
self.stack.push(bcp4x)?;
self.stack.push(bcp4y)?;
self.stack.push(p3x)?;
self.stack.push(p3y)?;
self.emit_curves([PointMode::DxDy; 3])?;
self.reset_stack();
self.stack.push(final_x)?;
self.stack.push(final_y)?;
Ok(())
}
fn handle_mm_blend(&mut self, subr_idx: i32, num_args: usize) -> Result<(), Error> {
let weight_vector = self.context.weight_vector();
let num_points = (subr_idx - 13) as usize + (subr_idx == 18) as usize;
if num_args != num_points * weight_vector.len() {
return Err(Error::Malformed);
}
let stack_base = self
.stack
.len()
.checked_sub(num_args)
.ok_or(Error::InvalidOperation)?;
let mut delta_idx = stack_base + num_points;
for i in 0..num_points {
let mut val = self.stack.get_fixed(stack_base + i)?;
for &weight in &weight_vector[1..] {
val += self.stack.get_fixed(delta_idx)? * weight;
delta_idx += 1;
}
self.stack.set(stack_base + i, val)?;
}
self.stack.drop(num_args.saturating_sub(num_points));
Ok(())
}
fn handle_build_char(&mut self, subr_idx: i32, num_args: usize) -> Result<(), Error> {
let weights = self.context.weight_vector();
let has_blend = !weights.is_empty();
let len = self.context.build_char_len();
match (subr_idx, num_args) {
(19, 1) if has_blend => {
let idx = self.stack.pop_i32()? as u32 as usize;
if len < weights.len() || len - weights.len() < idx {
return Err(Error::InvalidOperation);
}
self.build_char(len)[idx..idx + weights.len()].copy_from_slice(weights);
}
(20..=23, 2) => {
let b = self.stack.pop_fixed()?;
let a = self.stack.pop_fixed()?;
let result = match subr_idx {
20 => Fixed::from_bits(a.to_bits().wrapping_add(b.to_bits())),
21 => Fixed::from_bits(a.to_bits().wrapping_sub(b.to_bits())),
22 => a * b,
_ if b == Fixed::ZERO => return Err(Error::InvalidOperation),
_ => a / b,
};
self.stack.push(result)?;
}
(24, 2) if has_blend => {
let idx = self.stack.pop_i32()? as u32 as usize;
if idx >= len {
return Err(Error::InvalidOperation);
}
let value = self.stack.pop_fixed()?;
self.build_char(len)[idx] = value;
}
(25, 1) if has_blend => {
let idx = self.stack.pop_i32()? as u32 as usize;
if idx >= len {
return Err(Error::InvalidOperation);
}
let value = self.build_char(len)[idx];
self.stack.push(value)?;
}
(27, 4) => {
let d = self.stack.pop_fixed()?;
let c = self.stack.pop_fixed()?;
let b = self.stack.pop_fixed()?;
let a = self.stack.pop_fixed()?;
self.stack.push(if c <= d { a } else { b })?;
}
_ => return Err(Error::InvalidOperation),
}
Ok(())
}
fn build_char(&mut self, len: usize) -> &mut [Fixed] {
if self.build_char.len() != len {
self.build_char.resize(len, Fixed::ZERO);
}
&mut self.build_char
}
fn coords_remaining(&self) -> usize {
self.stack.len().saturating_sub(self.stack_ix)
}
fn ensure_open(&mut self) {
if !self.is_open {
self.sink.move_to(Fixed::ZERO, Fixed::ZERO);
self.is_open = true;
}
}
fn emit_line(&mut self, x: Fixed, y: Fixed) {
self.ensure_open();
self.sink.line_to(x, y);
}
fn emit_curves<const N: usize>(&mut self, modes: [PointMode; N]) -> Result<(), Error> {
use PointMode::*;
let initial_x = self.x;
let initial_y = self.y;
let mut count = 0;
let mut points = [Point::default(); 2];
self.ensure_open();
for mode in modes {
let stack_used = match mode {
DxDy => {
self.x += self.stack.get_fixed(self.stack_ix)?;
self.y += self.stack.get_fixed(self.stack_ix + 1)?;
2
}
XDy => {
self.y += self.stack.get_fixed(self.stack_ix)?;
1
}
DxY => {
self.x += self.stack.get_fixed(self.stack_ix)?;
1
}
DxInitialY => {
self.x += self.stack.get_fixed(self.stack_ix)?;
self.y = initial_y;
1
}
DLargerCoordDist => {
let delta = self.stack.get_fixed(self.stack_ix)?;
if (self.x - initial_x).abs() > (self.y - initial_y).abs() {
self.x += delta;
self.y = initial_y;
} else {
self.y += delta;
self.x = initial_x;
}
1
}
DxMaybeDy(do_dy) => {
self.x += self.stack.get_fixed(self.stack_ix)?;
if do_dy {
self.y += self.stack.get_fixed(self.stack_ix + 1)?;
2
} else {
1
}
}
MaybeDxDy(do_dx) => {
self.y += self.stack.get_fixed(self.stack_ix)?;
if do_dx {
self.x += self.stack.get_fixed(self.stack_ix + 1)?;
2
} else {
1
}
}
};
self.stack_ix += stack_used;
if count == 2 {
self.sink.curve_to(
points[0].x,
points[0].y,
points[1].x,
points[1].y,
self.x,
self.y,
);
count = 0;
} else {
points[count] = Point::new(self.x, self.y);
count += 1;
}
}
Ok(())
}
fn reset_stack(&mut self) {
self.stack.clear();
self.stack_ix = 0;
}
}
#[derive(Copy, Clone)]
enum PointMode {
DxDy,
XDy,
DxY,
DxInitialY,
DLargerCoordDist,
DxMaybeDy(bool),
MaybeDxDy(bool),
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum Operator {
HStem,
VStem,
VMoveTo,
RLineTo,
HLineTo,
VLineTo,
RrCurveTo,
CallSubr,
Return,
Hsbw,
EndChar,
VariationStoreIndex,
Blend,
HStemHm,
HintMask,
CntrMask,
RMoveTo,
HMoveTo,
VStemHm,
RCurveLine,
RLineCurve,
VvCurveTo,
HhCurveTo,
CallGsubr,
VhCurveTo,
HvCurveTo,
DotSection,
VStem3,
HStem3,
Seac,
Sbw,
Div,
CallOtherSubr,
Pop,
SetCurrentPoint,
HFlex,
Flex,
HFlex1,
Flex1,
And,
Or,
Not,
Abs,
Add,
Sub,
Neg,
Eq,
Drop,
Put,
Get,
IfElse,
Mul,
Sqrt,
Dup,
Exch,
Index,
Roll,
}
impl Operator {
#[inline]
fn read(cursor: &mut Cursor, b0: u8) -> Option<Self> {
const ESCAPE: u8 = 12;
if b0 == ESCAPE {
Self::from_two_byte_opcode(cursor.read::<u8>().ok()?)
} else {
Self::from_opcode(b0)
}
}
fn from_opcode(opcode: u8) -> Option<Self> {
use Operator::*;
Some(match opcode {
1 => HStem,
3 => VStem,
4 => VMoveTo,
5 => RLineTo,
6 => HLineTo,
7 => VLineTo,
8 => RrCurveTo,
10 => CallSubr,
11 => Return,
13 => Hsbw,
14 => EndChar,
15 => VariationStoreIndex,
16 => Blend,
18 => HStemHm,
19 => HintMask,
20 => CntrMask,
21 => RMoveTo,
22 => HMoveTo,
23 => VStemHm,
24 => RCurveLine,
25 => RLineCurve,
26 => VvCurveTo,
27 => HhCurveTo,
29 => CallGsubr,
30 => VhCurveTo,
31 => HvCurveTo,
_ => return None,
})
}
pub fn from_two_byte_opcode(opcode: u8) -> Option<Self> {
use Operator::*;
Some(match opcode {
0 => DotSection,
1 => VStem3,
2 => HStem3,
3 => And,
4 => Or,
5 => Not,
6 => Seac,
7 => Sbw,
9 => Abs,
10 => Add,
11 => Sub,
12 => Div,
14 => Neg,
15 => Eq,
18 => Drop,
20 => Put,
21 => Get,
22 => IfElse,
24 => Mul,
26 => Sqrt,
27 => Dup,
28 => Exch,
29 => Index,
30 => Roll,
16 => CallOtherSubr,
17 => Pop,
33 => SetCurrentPoint,
34 => HFlex,
35 => Flex,
36 => HFlex1,
37 => Flex1,
_ => return None,
})
}
}
const ONE_OVER_64: Fixed = Fixed::from_bits(0x400);
pub struct TransformSink<'a, S> {
inner: &'a mut S,
matrix: Option<FontMatrix>,
scale: Option<Fixed>,
}
impl<'a, S> TransformSink<'a, S> {
pub fn new(sink: &'a mut S, transform: Transform) -> Self {
Self::from_matrix_scale(sink, transform.matrix, transform.scale)
}
pub fn from_matrix_scale(sink: &'a mut S, matrix: FontMatrix, scale: Option<Fixed>) -> Self {
Self {
inner: sink,
matrix: (matrix != FontMatrix::IDENTITY).then_some(matrix),
scale,
}
}
fn transform(&self, x: Fixed, y: Fixed) -> (Fixed, Fixed) {
let ax = x * ONE_OVER_64;
let ay = y * ONE_OVER_64;
let bx = Fixed::from_bits(ax.to_bits() >> 10);
let by = Fixed::from_bits(ay.to_bits() >> 10);
let (cx, cy) = self
.matrix
.as_ref()
.map(|mat| mat.transform(bx, by))
.unwrap_or((bx, by));
if let Some(scale) = self.scale {
let dx = cx * scale;
let dy = cy * scale;
(
Fixed::from_bits(dx.to_bits() << 10),
Fixed::from_bits(dy.to_bits() << 10),
)
} else {
(
Fixed::from_bits(cx.to_bits() << 16),
Fixed::from_bits(cy.to_bits() << 16),
)
}
}
}
impl<S: CommandSink> CommandSink for TransformSink<'_, S> {
fn hstem(&mut self, y: Fixed, dy: Fixed) {
self.inner.hstem(y, dy);
}
fn vstem(&mut self, x: Fixed, dx: Fixed) {
self.inner.vstem(x, dx);
}
fn hint_mask(&mut self, mask: &[u8]) {
self.inner.hint_mask(mask);
}
fn counter_mask(&mut self, mask: &[u8]) {
self.inner.counter_mask(mask);
}
fn clear_hints(&mut self) {
self.inner.clear_hints();
}
fn move_to(&mut self, x: Fixed, y: Fixed) {
let (x, y) = self.transform(x, y);
self.inner.move_to(x, y);
}
fn line_to(&mut self, x: Fixed, y: Fixed) {
let (x, y) = self.transform(x, y);
self.inner.line_to(x, y);
}
fn curve_to(&mut self, cx1: Fixed, cy1: Fixed, cx2: Fixed, cy2: Fixed, x: Fixed, y: Fixed) {
let (cx1, cy1) = self.transform(cx1, cy1);
let (cx2, cy2) = self.transform(cx2, cy2);
let (x, y) = self.transform(x, y);
self.inner.curve_to(cx1, cy1, cx2, cy2, x, y);
}
fn close(&mut self) {
self.inner.close();
}
fn finish(&mut self) {
self.inner.finish();
}
}
#[derive(Copy, Clone)]
enum PendingElement {
Move([Fixed; 2]),
Line([Fixed; 2]),
Curve([Fixed; 6]),
}
impl PendingElement {
fn target_point(&self) -> [Fixed; 2] {
match self {
Self::Move(xy) | Self::Line(xy) => *xy,
Self::Curve([.., x, y]) => [*x, *y],
}
}
}
pub struct NopFilterSink<'a, S> {
is_open: bool,
start: Option<(Fixed, Fixed)>,
pending_element: Option<PendingElement>,
inner: &'a mut S,
}
impl<'a, S> NopFilterSink<'a, S>
where
S: CommandSink,
{
pub fn new(inner: &'a mut S) -> Self {
Self {
is_open: false,
start: None,
pending_element: None,
inner,
}
}
fn flush_pending(&mut self, for_close: bool) {
if let Some(pending) = self.pending_element.take() {
match pending {
PendingElement::Move([x, y]) => {
if !for_close {
self.is_open = true;
self.inner.move_to(x, y);
self.start = Some((x, y));
}
}
PendingElement::Line([x, y]) => {
if !for_close || self.start != Some((x, y)) {
self.inner.line_to(x, y);
}
}
PendingElement::Curve([cx0, cy0, cx1, cy1, x, y]) => {
self.inner.curve_to(cx0, cy0, cx1, cy1, x, y);
}
}
}
}
}
impl<S> CommandSink for NopFilterSink<'_, S>
where
S: CommandSink,
{
fn hstem(&mut self, y: Fixed, dy: Fixed) {
self.inner.hstem(y, dy);
}
fn vstem(&mut self, x: Fixed, dx: Fixed) {
self.inner.vstem(x, dx);
}
fn hint_mask(&mut self, mask: &[u8]) {
self.inner.hint_mask(mask);
}
fn counter_mask(&mut self, mask: &[u8]) {
self.inner.counter_mask(mask);
}
fn clear_hints(&mut self) {
self.inner.clear_hints();
}
fn move_to(&mut self, x: Fixed, y: Fixed) {
self.pending_element = Some(PendingElement::Move([x, y]));
}
fn line_to(&mut self, x: Fixed, y: Fixed) {
if self
.pending_element
.map(|element| element.target_point() == [x, y])
.unwrap_or_default()
{
return;
}
self.flush_pending(false);
self.pending_element = Some(PendingElement::Line([x, y]));
}
fn curve_to(&mut self, cx1: Fixed, cy1: Fixed, cx2: Fixed, cy2: Fixed, x: Fixed, y: Fixed) {
self.flush_pending(false);
self.pending_element = Some(PendingElement::Curve([cx1, cy1, cx2, cy2, x, y]));
}
fn close(&mut self) {
self.flush_pending(true);
if self.is_open {
self.inner.close();
self.is_open = false;
}
}
fn finish(&mut self) {
self.close();
self.inner.finish();
}
}
impl<P: PathSink> CommandSink for P {
fn move_to(&mut self, x: Fixed, y: Fixed) {
self.move_to(x.to_f32(), y.to_f32());
}
fn line_to(&mut self, x: Fixed, y: Fixed) {
self.line_to(x.to_f32(), y.to_f32());
}
fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
self.curve_to(
cx0.to_f32(),
cy0.to_f32(),
cx1.to_f32(),
cy1.to_f32(),
x.to_f32(),
y.to_f32(),
);
}
fn close(&mut self) {
self.close()
}
}
#[cfg(test)]
pub(crate) mod test_helpers {
use super::{CommandSink, Fixed};
#[derive(Copy, Clone, PartialEq, Debug)]
#[allow(clippy::enum_variant_names)]
pub enum Command {
MoveTo(Fixed, Fixed),
LineTo(Fixed, Fixed),
CurveTo(Fixed, Fixed, Fixed, Fixed, Fixed, Fixed),
}
#[derive(PartialEq, Default, Debug)]
pub struct CaptureCommandSink(pub Vec<Command>);
impl CommandSink for CaptureCommandSink {
fn move_to(&mut self, x: Fixed, y: Fixed) {
self.0.push(Command::MoveTo(x, y))
}
fn line_to(&mut self, x: Fixed, y: Fixed) {
self.0.push(Command::LineTo(x, y))
}
fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
self.0.push(Command::CurveTo(cx0, cy0, cx1, cy1, x, y))
}
fn close(&mut self) {
let mut last_move = [Fixed::ZERO; 2];
for command in self.0.iter().rev() {
if let Command::MoveTo(x, y) = command {
last_move = [*x, *y];
break;
}
}
self.0.push(Command::LineTo(last_move[0], last_move[1]));
}
}
impl CaptureCommandSink {
pub fn to_svg(&self) -> String {
use core::fmt::Write;
let mut buf = String::default();
for cmd in &self.0 {
if !buf.is_empty() {
buf.push(' ');
}
match cmd {
Command::MoveTo(x, y) => write!(buf, "M{},{}", x.to_f32(), y.to_f32()).unwrap(),
Command::LineTo(x, y) => write!(buf, "L{},{}", x.to_f32(), y.to_f32()).unwrap(),
Command::CurveTo(x0, y0, x1, y1, x, y) => write!(
buf,
"C{},{} {},{} {},{}",
x0.to_f32(),
y0.to_f32(),
x1.to_f32(),
y1.to_f32(),
x.to_f32(),
y.to_f32()
)
.unwrap(),
}
}
buf
}
}
#[derive(Default)]
pub struct CharstringCommandCounter(pub usize);
impl CommandSink for CharstringCommandCounter {
fn move_to(&mut self, _x: Fixed, _y: Fixed) {
self.0 += 1;
}
fn line_to(&mut self, _x: Fixed, _y: Fixed) {
self.0 += 1;
}
fn curve_to(
&mut self,
_cx0: Fixed,
_cy0: Fixed,
_cx1: Fixed,
_cy1: Fixed,
_x: Fixed,
_y: Fixed,
) {
self.0 += 1;
}
fn close(&mut self) {
self.0 += 1;
}
}
}
#[cfg(test)]
mod tests {
use super::{test_helpers::*, *};
use crate::{tables::variations::ItemVariationStore, types::F2Dot14, FontData, FontRead};
#[test]
fn cff2_example_subr() {
use Command::*;
let charstring = &font_test_data::cff2::EXAMPLE[0xc8..=0xe1];
let store =
ItemVariationStore::read(FontData::new(&font_test_data::cff2::EXAMPLE[18..])).unwrap();
let coords = &[F2Dot14::from_f32(0.0)];
let blend_state = BlendState::new(store, coords, 0).unwrap();
let mut commands = CaptureCommandSink::default();
evaluate(
&NullContext(CharstringKind::Type2),
Some(blend_state),
charstring,
&mut commands,
)
.unwrap();
let expected = &[
MoveTo(Fixed::from_f64(50.0), Fixed::ZERO),
LineTo(Fixed::from_f64(550.0), Fixed::ZERO),
LineTo(Fixed::from_f64(550.0), Fixed::from_f64(500.0)),
LineTo(Fixed::from_f64(50.0), Fixed::from_f64(500.0)),
LineTo(Fixed::from_f64(50.0), Fixed::ZERO),
];
assert_eq!(&commands.0, expected);
}
#[test]
fn all_path_ops() {
let charstring = &[
251, 29, 253, 12, 21, 173, 134, 159, 133, 5, 140, 141, 142, 6, 251, 71, 129, 142, 7,
109, 154, 161, 147, 89, 165, 125, 97, 98, 158, 124, 164, 8, 109, 154, 161, 147, 27,
147, 109, 154, 161, 147, 27, 163, 159, 154, 180, 181, 119, 153, 115, 114, 120, 125, 97,
98, 158, 124, 164, 31, 159, 4, 119, 153, 115, 114, 120, 125, 143, 144, 24, 119, 153,
115, 114, 120, 125, 143, 144, 25, 84, 116, 117, 80, 30, 109, 154, 161, 147, 26, 147,
109, 154, 161, 147, 26, 163, 159, 154, 180, 181, 119, 153, 115, 114, 120, 125, 97, 162,
12, 35, 163, 159, 154, 180, 181, 119, 153, 12, 34, 152, 22, 180, 181, 119, 153, 115,
114, 120, 125, 97, 12, 36, 154, 180, 181, 119, 153, 115, 114, 120, 125, 97, 147, 12,
37, 14,
];
use Command::*;
let mut commands = CaptureCommandSink::default();
evaluate(
&NullContext(CharstringKind::Type2),
None,
charstring,
&mut commands,
)
.unwrap();
let expected = &[
MoveTo(Fixed::from_i32(-137), Fixed::from_i32(-632)),
LineTo(Fixed::from_i32(-103), Fixed::from_i32(-637)),
LineTo(Fixed::from_i32(-83), Fixed::from_i32(-643)),
LineTo(Fixed::from_i32(-82), Fixed::from_i32(-643)),
LineTo(Fixed::from_i32(-82), Fixed::from_i32(-641)),
LineTo(Fixed::from_i32(-79), Fixed::from_i32(-641)),
LineTo(Fixed::from_i32(-79), Fixed::from_i32(-820)),
LineTo(Fixed::from_i32(-89), Fixed::from_i32(-820)),
LineTo(Fixed::from_i32(-89), Fixed::from_i32(-817)),
CurveTo(
Fixed::from_i32(-119),
Fixed::from_i32(-802),
Fixed::from_i32(-97),
Fixed::from_i32(-794),
Fixed::from_i32(-147),
Fixed::from_i32(-768),
),
CurveTo(
Fixed::from_i32(-161),
Fixed::from_i32(-810),
Fixed::from_i32(-202),
Fixed::from_i32(-791),
Fixed::from_i32(-217),
Fixed::from_i32(-766),
),
CurveTo(
Fixed::from_i32(-247),
Fixed::from_i32(-766),
Fixed::from_i32(-232),
Fixed::from_i32(-744),
Fixed::from_i32(-224),
Fixed::from_i32(-744),
),
CurveTo(
Fixed::from_i32(-254),
Fixed::from_i32(-736),
Fixed::from_i32(-239),
Fixed::from_i32(-714),
Fixed::from_i32(-231),
Fixed::from_i32(-714),
),
CurveTo(
Fixed::from_i32(-207),
Fixed::from_i32(-714),
Fixed::from_i32(-187),
Fixed::from_i32(-699),
Fixed::from_i32(-187),
Fixed::from_i32(-658),
),
CurveTo(
Fixed::from_i32(-187),
Fixed::from_i32(-616),
Fixed::from_i32(-207),
Fixed::from_i32(-602),
Fixed::from_i32(-231),
Fixed::from_i32(-602),
),
CurveTo(
Fixed::from_i32(-256),
Fixed::from_i32(-602),
Fixed::from_i32(-275),
Fixed::from_i32(-616),
Fixed::from_i32(-275),
Fixed::from_i32(-658),
),
CurveTo(
Fixed::from_i32(-275),
Fixed::from_i32(-699),
Fixed::from_i32(-256),
Fixed::from_i32(-714),
Fixed::from_i32(-231),
Fixed::from_i32(-714),
),
LineTo(Fixed::from_i32(-137), Fixed::from_i32(-632)),
MoveTo(Fixed::from_i32(-231), Fixed::from_i32(-694)),
CurveTo(
Fixed::from_i32(-251),
Fixed::from_i32(-680),
Fixed::from_i32(-275),
Fixed::from_i32(-705),
Fixed::from_i32(-294),
Fixed::from_i32(-719),
),
LineTo(Fixed::from_i32(-290), Fixed::from_i32(-714)),
LineTo(Fixed::from_i32(-310), Fixed::from_i32(-700)),
CurveTo(
Fixed::from_i32(-334),
Fixed::from_i32(-725),
Fixed::from_i32(-353),
Fixed::from_i32(-739),
Fixed::from_i32(-349),
Fixed::from_i32(-734),
),
CurveTo(
Fixed::from_i32(-349),
Fixed::from_i32(-789),
Fixed::from_i32(-372),
Fixed::from_i32(-811),
Fixed::from_i32(-431),
Fixed::from_i32(-811),
),
CurveTo(
Fixed::from_i32(-431),
Fixed::from_i32(-841),
Fixed::from_i32(-416),
Fixed::from_i32(-819),
Fixed::from_i32(-416),
Fixed::from_i32(-811),
),
CurveTo(
Fixed::from_i32(-408),
Fixed::from_i32(-841),
Fixed::from_i32(-393),
Fixed::from_i32(-819),
Fixed::from_i32(-393),
Fixed::from_i32(-811),
),
CurveTo(
Fixed::from_i32(-369),
Fixed::from_i32(-791),
Fixed::from_i32(-354),
Fixed::from_i32(-750),
Fixed::from_i32(-312),
Fixed::from_i32(-770),
),
CurveTo(
Fixed::from_i32(-298),
Fixed::from_i32(-794),
Fixed::from_i32(-323),
Fixed::from_i32(-813),
Fixed::from_i32(-337),
Fixed::from_i32(-855),
),
CurveTo(
Fixed::from_i32(-313),
Fixed::from_i32(-855),
Fixed::from_i32(-293),
Fixed::from_i32(-840),
Fixed::from_i32(-252),
Fixed::from_i32(-840),
),
CurveTo(
Fixed::from_i32(-210),
Fixed::from_i32(-840),
Fixed::from_i32(-230),
Fixed::from_i32(-855),
Fixed::from_i32(-216),
Fixed::from_i32(-855),
),
LineTo(Fixed::from_i32(-231), Fixed::from_i32(-694)),
MoveTo(Fixed::from_i32(-203), Fixed::from_i32(-855)),
CurveTo(
Fixed::from_i32(-162),
Fixed::from_i32(-813),
Fixed::from_i32(-182),
Fixed::from_i32(-799),
Fixed::from_i32(-206),
Fixed::from_i32(-799),
),
CurveTo(
Fixed::from_i32(-231),
Fixed::from_i32(-799),
Fixed::from_i32(-250),
Fixed::from_i32(-813),
Fixed::from_i32(-292),
Fixed::from_i32(-855),
),
CurveTo(
Fixed::from_i32(-277),
Fixed::from_i32(-814),
Fixed::from_i32(-235),
Fixed::from_i32(-834),
Fixed::from_i32(-221),
Fixed::from_i32(-858),
),
CurveTo(
Fixed::from_i32(-246),
Fixed::from_i32(-877),
Fixed::from_i32(-260),
Fixed::from_i32(-919),
Fixed::from_i32(-292),
Fixed::from_i32(-911),
),
LineTo(Fixed::from_i32(-203), Fixed::from_i32(-855)),
];
assert_eq!(&commands.0, expected);
}
#[test]
fn coords_remaining_avoid_overflow() {
let mut commands = CaptureCommandSink::default();
let mut evaluator =
Evaluator::new(&NullContext(CharstringKind::Type2), None, &mut commands);
evaluator.stack.push(0).unwrap();
evaluator.stack.push(0).unwrap();
let mut cursor = FontData::new(&[]).cursor();
let _ = evaluator.evaluate_operator(Operator::HhCurveTo, &mut cursor, 0);
}
#[test]
fn ignore_reserved_operators() {
let charstring = &[
0u8, 32, 22, 2, ];
let mut commands = CaptureCommandSink::default();
evaluate(
&NullContext(CharstringKind::Type2),
None,
charstring,
&mut commands,
)
.unwrap();
let x = Fixed::from_i32(-107);
assert_eq!(
commands.0,
[
Command::MoveTo(x, Fixed::ZERO),
Command::LineTo(x, Fixed::ZERO)
]
);
}
#[test]
fn operation_limit() {
let charstring = vec![0u8; MAX_OPERATIONS as usize + 1];
let mut commands = CaptureCommandSink::default();
evaluate(
&NullContext(CharstringKind::Type2),
None,
&charstring[..MAX_OPERATIONS as usize],
&mut commands,
)
.unwrap();
let err = evaluate(
&NullContext(CharstringKind::Type2),
None,
&charstring,
&mut commands,
)
.unwrap_err();
assert!(matches!(err, Error::LimitExceeded));
}
#[test]
fn op_div() {
let mut commands = CaptureCommandSink::default();
let mut eval = Evaluator::new(&NullContext(CharstringKind::Type2), None, &mut commands);
let mut cursor = FontData::new(&[]).cursor();
eval.stack.push(Fixed::from_f64(512.5)).unwrap();
eval.stack.push(2).unwrap();
eval.evaluate_operator(Operator::Div, &mut cursor, 0)
.unwrap();
assert_eq!(
eval.stack.pop_fixed().unwrap(),
Fixed::from_f64(512.5 / 2.0)
);
}
#[test]
fn op_div_type1_large_int() {
let mut commands = CaptureCommandSink::default();
let mut eval = Evaluator::new(&NullContext(CharstringKind::Type1), None, &mut commands);
let mut cursor = FontData::new(&[]).cursor();
eval.stack.push(32001).unwrap();
eval.stack.push(2).unwrap();
eval.evaluate_operator(Operator::Div, &mut cursor, 0)
.unwrap();
assert_eq!(
eval.stack.pop_fixed().unwrap(),
Fixed::from_f64(32001.0 / 2.0)
);
}
fn eval_h_sbw(operator: Operator, kind: CharstringKind) -> [Fixed; 2] {
let mut commands = CaptureCommandSink::default();
let ctx = &NullContext(kind);
let mut eval = Evaluator::new(ctx, None, &mut commands);
let mut cursor = FontData::new(&[]).cursor();
eval.stack.push(Fixed::from_f64(42.5)).unwrap();
if operator == Operator::Sbw {
eval.stack.push(0).unwrap();
}
eval.stack.push(501).unwrap();
eval.stack.push(1000).unwrap();
eval.evaluate_operator(operator, &mut cursor, 0).unwrap();
[eval.sbx, eval.wx]
}
#[test]
fn op_sbw_type1() {
let [sbx, wx] = eval_h_sbw(Operator::Sbw, CharstringKind::Type1);
assert_eq!(sbx, Fixed::from_f64(42.5));
assert_eq!(wx, Fixed::from_f64(501.0));
}
#[test]
fn op_hsbw_type1() {
let [sbx, wx] = eval_h_sbw(Operator::Hsbw, CharstringKind::Type1);
assert_eq!(sbx, Fixed::from_f64(42.5));
assert_eq!(wx, Fixed::from_f64(501.0));
}
#[test]
fn op_sbw_type2_no_effect() {
let [sbx, wx] = eval_h_sbw(Operator::Sbw, CharstringKind::Type2);
assert_eq!(sbx, Fixed::ZERO);
assert_eq!(wx, Fixed::ZERO);
}
#[test]
fn op_hsbw_type2_no_effect() {
let [sbx, wx] = eval_h_sbw(Operator::Hsbw, CharstringKind::Type2);
assert_eq!(sbx, Fixed::ZERO);
assert_eq!(wx, Fixed::ZERO);
}
#[test]
fn op_callothersubr_flex() {
let mut commands = CaptureCommandSink::default();
let mut eval = Evaluator::new(&NullContext(CharstringKind::Type1), None, &mut commands);
let mut cursor = FontData::new(&[]).cursor();
macro_rules! op {
($nums:expr) => {
for n in $nums {
eval.stack.push(n).unwrap();
}
};
($nums:expr, $op:ident) => {
op!($nums);
eval.evaluate_operator(Operator::$op, &mut cursor, 0)
.unwrap();
};
}
op!([0, 1], CallOtherSubr);
for vec in [[1, 2]; 7] {
op!(vec, RMoveTo);
}
op!([0, 100, 200]);
op!([3, 0], CallOtherSubr);
let none: [i32; 0] = [];
op!(none, SetCurrentPoint);
let expected = [
Command::MoveTo(Fixed::ZERO, Fixed::ZERO),
Command::CurveTo(
Fixed::from_i32(2),
Fixed::from_i32(4),
Fixed::from_i32(3),
Fixed::from_i32(6),
Fixed::from_i32(4),
Fixed::from_i32(8),
),
Command::CurveTo(
Fixed::from_i32(5),
Fixed::from_i32(10),
Fixed::from_i32(6),
Fixed::from_i32(12),
Fixed::from_i32(7),
Fixed::from_i32(14),
),
];
assert_eq!(eval.x, Fixed::from_i32(100));
assert_eq!(eval.y, Fixed::from_i32(200));
assert_eq!(commands.0, expected);
}
struct NullContext(CharstringKind);
impl CharstringContext for NullContext {
fn kind(&self) -> CharstringKind {
self.0
}
fn seac_components(&self, _base_code: i32, _accent_code: i32) -> Result<[&[u8]; 2], Error> {
Err(Error::InvalidOperation)
}
fn global_subr(&self, _index: i32) -> Result<&[u8], Error> {
Err(Error::MissingResource)
}
fn subr(&self, _index: i32) -> Result<&[u8], Error> {
Err(Error::MissingResource)
}
}
#[test]
fn nop_filter_sink() {
let mut commands = CaptureCommandSink::default();
let mut nop_filter = NopFilterSink::new(&mut commands);
let (sx, sy) = (Fixed::from_f64(10.2), Fixed::from_f64(20.4));
nop_filter.move_to(Fixed::from_f64(0.0), Fixed::from_f64(0.0));
nop_filter.move_to(sx, sy);
nop_filter.line_to(sx, sy);
nop_filter.curve_to(
Fixed::from_f64(5.0),
Fixed::from_f64(-5.0),
Fixed::from_f64(1.5),
Fixed::from_f64(2.0),
Fixed::from_f64(4.5),
Fixed::from_f64(-10.0),
);
nop_filter.line_to(Fixed::from_f64(4.5), Fixed::from_f64(-10.0));
nop_filter.line_to(sx, sy);
nop_filter.close();
assert_eq!(
commands.0,
[
Command::MoveTo(sx, sy),
Command::CurveTo(
Fixed::from_f64(5.0),
Fixed::from_f64(-5.0),
Fixed::from_f64(1.5),
Fixed::from_f64(2.0),
Fixed::from_f64(4.5),
Fixed::from_f64(-10.0),
),
Command::LineTo(sx, sy),
]
)
}
#[test]
fn scaled_matrix_transform_sink() {
let input = [(150i32, 46i32), (176, 8), (217, -13), (267, -13)]
.map(|(x, y)| (Fixed::from_bits(x << 16), Fixed::from_bits(y << 16)));
let expected = [(404, 118i32), (453, 20), (550, -33), (678, -33)]
.map(|(x, y)| (Fixed::from_bits(x << 10), Fixed::from_bits(y << 10)));
let mut dummy = ();
let sink =
TransformSink::from_matrix_scale(&mut dummy, TRANSFORM, Some(Fixed::from_bits(167772)));
let transformed = input.map(|(x, y)| sink.transform(x, y));
assert_eq!(transformed, expected);
}
#[test]
fn unscaled_matrix_transform_sink() {
let input = [(150i32, 46i32), (176, 8), (217, -13), (267, -13)]
.map(|(x, y)| (Fixed::from_bits(x << 16), Fixed::from_bits(y << 16)));
let expected = [(158, 46i32), (177, 8), (215, -13), (265, -13)]
.map(|(x, y)| (Fixed::from_bits(x << 16), Fixed::from_bits(y << 16)));
let mut dummy = ();
let sink = TransformSink::from_matrix_scale(&mut dummy, TRANSFORM, None);
let transformed = input.map(|(x, y)| sink.transform(x, y));
assert_eq!(transformed, expected);
}
const TRANSFORM: FontMatrix = FontMatrix::from_elements([
Fixed::ONE,
Fixed::ZERO,
Fixed::from_bits(10945),
Fixed::ONE,
Fixed::ZERO,
Fixed::ZERO,
]);
#[test]
fn unscaled_transform_sink_produces_integers() {
let nothing = &mut ();
let sink = TransformSink::new(nothing, Transform::default());
for coord in [50.0, 50.1, 50.125, 50.5, 50.9] {
assert_eq!(
sink.transform(Fixed::from_f64(coord), Fixed::ZERO)
.0
.to_f32(),
50.0
);
}
}
#[test]
fn scaled_transform_sink() {
let ppem = 20.0;
let upem = 1000.0;
let scale = Fixed::from_bits((ppem * 64.) as i32) / Fixed::from_bits(upem as i32);
let nothing = &mut ();
let sink = TransformSink::from_matrix_scale(nothing, FontMatrix::IDENTITY, Some(scale));
let inputs = [
(0.0, 0.0),
(8.0, 0.15625),
(16.0, 0.3125),
(32.0, 0.640625),
(72.0, 1.4375),
(128.0, 2.5625),
];
for (coord, expected) in inputs {
assert_eq!(
sink.transform(Fixed::from_f64(coord), Fixed::ZERO)
.0
.to_f32(),
expected,
"scaling coord {coord}"
);
}
}
#[test]
fn mm_blend() {
let mut commands = CaptureCommandSink::default();
let ctx = MmContext([0.0, -0.25, 1.0].map(Fixed::from_f64));
let mut eval = Evaluator::new(&ctx, None, &mut commands);
let mut cursor = FontData::new(&[]).cursor();
for i in [[1, 0], [2, 3], [4, -8], [6, 15]].into_iter().flatten() {
eval.stack.push(i).unwrap();
}
eval.evaluate_operator(Operator::CallOtherSubr, &mut cursor, 0)
.unwrap();
let [a, b] = eval.stack.fixed_array(0).unwrap();
assert_eq!(a.to_f32(), 3.5);
assert_eq!(b.to_f32(), -9.0);
}
struct MmContext([Fixed; 3]);
impl CharstringContext for MmContext {
fn kind(&self) -> CharstringKind {
CharstringKind::Type1
}
fn seac_components(&self, _base_code: i32, _accent_code: i32) -> Result<[&[u8]; 2], Error> {
Err(Error::InvalidOperation)
}
fn global_subr(&self, _index: i32) -> Result<&[u8], Error> {
Err(Error::MissingResource)
}
fn subr(&self, _index: i32) -> Result<&[u8], Error> {
Err(Error::MissingResource)
}
fn weight_vector(&self) -> &[Fixed] {
&self.0
}
fn build_char_len(&self) -> usize {
8
}
}
fn othersubr(eval: &mut Evaluator, args: &[f64]) -> Result<Vec<f64>, Error> {
let mut cursor = FontData::new(&[]).cursor();
for &arg in args {
if arg.fract() == 0.0 {
eval.stack.push(arg as i32)?;
} else {
eval.stack.push(Fixed::from_f64(arg))?;
}
}
eval.evaluate_operator(Operator::CallOtherSubr, &mut cursor, 0)?;
let stack = eval.stack.fixed_values().map(|v| v.to_f64()).collect();
Ok(stack)
}
#[test]
fn othersubr_arithmetic() {
let ctx = MmContext([Fixed::ZERO; 3]);
for (args, result) in [
(&[7.0, 2.0, 2.0, 20.0][..], 9.0),
(&[7.0, 2.0, 2.0, 21.0], 5.0),
(&[7.0, 2.0, 2.0, 22.0], 14.0),
(&[7.0, 2.0, 2.0, 23.0], 3.5),
(&[1.0, 2.0, 3.0, 4.0, 4.0, 27.0], 1.0),
(&[1.0, 2.0, 5.0, 4.0, 4.0, 27.0], 2.0),
] {
let mut commands = CaptureCommandSink::default();
let mut eval = Evaluator::new(&ctx, None, &mut commands);
assert_eq!(othersubr(&mut eval, args).unwrap(), [result], "{args:?}");
}
let mut commands = CaptureCommandSink::default();
let mut eval = Evaluator::new(&ctx, None, &mut commands);
assert!(othersubr(&mut eval, &[7.0, 0.0, 2.0, 23.0]).is_err());
assert!(othersubr(&mut eval, &[7.0, 2.0, 1.0, 20.0]).is_err());
}
#[test]
fn othersubr_build_char_array() {
let ctx = MmContext([0.25, 0.5, 0.25].map(Fixed::from_f64));
let mut commands = CaptureCommandSink::default();
let mut eval = Evaluator::new(&ctx, None, &mut commands);
assert!(othersubr(&mut eval, &[5.0, 3.0, 2.0, 24.0])
.unwrap()
.is_empty());
assert_eq!(othersubr(&mut eval, &[3.0, 1.0, 25.0]).unwrap(), [5.0]);
eval.stack.clear();
assert!(othersubr(&mut eval, &[2.0, 1.0, 19.0]).unwrap().is_empty());
assert_eq!(othersubr(&mut eval, &[3.0, 1.0, 25.0]).unwrap(), [0.5]);
eval.stack.clear();
assert!(othersubr(&mut eval, &[1.0, 8.0, 2.0, 24.0]).is_err());
assert!(othersubr(&mut eval, &[8.0, 1.0, 25.0]).is_err());
assert!(othersubr(&mut eval, &[6.0, 1.0, 19.0]).is_err());
}
#[test]
fn othersubrs_without_a_blend_fail() {
struct Plain;
impl CharstringContext for Plain {
fn kind(&self) -> CharstringKind {
CharstringKind::Type1
}
fn seac_components(&self, _: i32, _: i32) -> Result<[&[u8]; 2], Error> {
Err(Error::InvalidOperation)
}
fn global_subr(&self, _: i32) -> Result<&[u8], Error> {
Err(Error::MissingResource)
}
fn subr(&self, _: i32) -> Result<&[u8], Error> {
Err(Error::MissingResource)
}
}
for args in [
&[1.0, 2.0, 2.0, 14.0][..],
&[0.0, 1.0, 19.0],
&[5.0, 0.0, 2.0, 24.0],
&[0.0, 1.0, 25.0],
] {
let mut commands = CaptureCommandSink::default();
let mut eval = Evaluator::new(&Plain, None, &mut commands);
assert!(othersubr(&mut eval, args).is_err(), "{args:?}");
}
let mut commands = CaptureCommandSink::default();
let mut eval = Evaluator::new(&Plain, None, &mut commands);
assert_eq!(othersubr(&mut eval, &[7.0, 2.0, 2.0, 20.0]).unwrap(), [9.0]);
}
fn eval(ops: &[u8]) -> Vec<Command> {
let mut charstring = ops.to_vec();
charstring.push(21); charstring.push(14); let mut commands = CaptureCommandSink::default();
evaluate(
&NullContext(CharstringKind::Type2),
None,
&charstring,
&mut commands,
)
.unwrap();
commands.0
}
fn n(v: i32) -> Vec<u8> {
assert!((-107..=107).contains(&v));
std::vec![(v + 139) as u8]
}
fn esc(op: u8) -> Vec<u8> {
std::vec![12, op]
}
fn binary(a: i32, b: i32, op: u8) -> (Fixed, Fixed) {
let mut cs = n(0);
cs.extend(n(a));
cs.extend(n(b));
cs.extend(esc(op));
match eval(&cs).first() {
Some(Command::MoveTo(x, y)) => (*x, *y),
other => panic!("expected a move, got {other:?}"),
}
}
#[test]
fn arithmetic() {
assert_eq!(binary(7, 5, 10).1, Fixed::from_i32(12)); assert_eq!(binary(7, 5, 11).1, Fixed::from_i32(2)); assert_eq!(binary(7, 5, 24).1, Fixed::from_i32(35)); assert_eq!(binary(10, 4, 12).1, Fixed::from_f64(2.5)); }
#[test]
fn logic_answers_with_one_or_zero() {
for (a, b, op, expected) in [
(1, 1, 3, 1),
(1, 0, 3, 0),
(0, 0, 3, 0), (1, 0, 4, 1),
(0, 0, 4, 0), (5, 5, 15, 1),
(5, 6, 15, 0), ] {
assert_eq!(
binary(a, b, op).1,
Fixed::from_i32(expected),
"12 {op} on {a}, {b}"
);
}
}
#[test]
fn unary() {
let mut cs = n(0);
cs.extend(n(0));
cs.extend(esc(5));
assert_eq!(
match eval(&cs).first() {
Some(Command::MoveTo(_, y)) => *y,
_ => panic!(),
},
Fixed::from_i32(1)
);
for (v, op, expected) in [(-5, 9, 5), (5, 9, 5), (5, 14, -5), (-5, 14, 5)] {
let mut cs = n(0);
cs.extend(n(v));
cs.extend(esc(op));
assert_eq!(
match eval(&cs).first() {
Some(Command::MoveTo(_, y)) => *y,
_ => panic!(),
},
Fixed::from_i32(expected),
"12 {op} on {v}"
);
}
}
#[test]
fn sqrt_of_a_square_is_the_root() {
for v in [1i32, 4, 9, 16, 100] {
let mut cs = n(0);
cs.extend(n(v));
cs.extend(esc(26));
let got = match eval(&cs).first() {
Some(Command::MoveTo(_, y)) => *y,
_ => panic!(),
};
let want = Fixed::from_f64((v as f64).sqrt());
assert!(
(got.to_bits() - want.to_bits()).abs() <= 2,
"sqrt({v}) = {got:?}, wanted about {want:?}"
);
}
for v in [0i32, -4] {
let mut cs = n(0);
cs.extend(n(v));
cs.extend(esc(26));
assert_eq!(
match eval(&cs).first() {
Some(Command::MoveTo(_, y)) => *y,
_ => panic!(),
},
Fixed::ZERO
);
}
}
#[test]
fn the_stack_operators_rearrange_it() {
let mut cs = n(0);
cs.extend(n(7));
cs.extend(esc(27));
assert_eq!(
eval(&cs).first().cloned(),
Some(Command::MoveTo(Fixed::from_i32(7), Fixed::from_i32(7)))
);
let mut cs = n(3);
cs.extend(n(9));
cs.extend(esc(28));
assert_eq!(
eval(&cs).first().cloned(),
Some(Command::MoveTo(Fixed::from_i32(9), Fixed::from_i32(3)))
);
let mut cs = n(4);
cs.extend(n(5));
cs.extend(n(6));
cs.extend(esc(18));
assert_eq!(
eval(&cs).first().cloned(),
Some(Command::MoveTo(Fixed::from_i32(4), Fixed::from_i32(5)))
);
let mut cs = n(0);
cs.extend(n(8));
cs.extend(n(0));
cs.extend(esc(29));
assert_eq!(
eval(&cs).first().cloned(),
Some(Command::MoveTo(Fixed::from_i32(8), Fixed::from_i32(8)))
);
}
#[test]
fn roll_rotates_the_values_it_is_given() {
let mut cs = n(1);
cs.extend(n(2));
cs.extend(n(3));
cs.extend(n(3));
cs.extend(n(1));
cs.extend(esc(30));
assert_eq!(
eval(&cs).first().cloned(),
Some(Command::MoveTo(Fixed::from_i32(1), Fixed::from_i32(2)))
);
}
#[test]
fn roll_leaves_the_values_below_alone() {
let mut cs = n(99);
cs.extend(n(1));
cs.extend(n(2));
cs.extend(n(2));
cs.extend(n(1));
cs.extend(esc(30));
assert_eq!(
eval(&cs).first().cloned(),
Some(Command::MoveTo(Fixed::from_i32(2), Fixed::from_i32(1)))
);
}
#[test]
fn the_transient_array_remembers_between_operators() {
let mut cs = n(0);
cs.extend(n(42));
cs.extend(n(3));
cs.extend(esc(20)); cs.extend(n(3));
cs.extend(esc(21)); assert_eq!(
eval(&cs).first().cloned(),
Some(Command::MoveTo(Fixed::ZERO, Fixed::from_i32(42)))
);
}
#[test]
fn a_slot_the_array_does_not_have_is_ignored() {
let mut cs = n(3);
cs.extend(n(9));
cs.extend(n(99));
cs.extend(esc(20)); cs.extend(n(4));
assert_eq!(
eval(&cs).first().cloned(),
Some(Command::MoveTo(Fixed::from_i32(3), Fixed::from_i32(4)))
);
}
#[test]
fn ifelse_picks_the_first_when_the_conditions_are_ordered() {
for (c1, c2, expected) in [(1, 2, 11), (2, 1, 22), (2, 2, 11)] {
let mut cs = n(0);
cs.extend(n(11));
cs.extend(n(22));
cs.extend(n(c1));
cs.extend(n(c2));
cs.extend(esc(22));
assert_eq!(
match eval(&cs).first() {
Some(Command::MoveTo(_, y)) => *y,
_ => panic!(),
},
Fixed::from_i32(expected),
"conditions {c1}, {c2}"
);
}
}
}