use crate::{
model::glyph::outline::OutlinePen,
ps::{
cff::{blend::BlendState, charset::Charset, index::Index, stack::Stack},
error::Error,
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;
#[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] {
&[]
}
}
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::MissingCharset)?;
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::InvalidSeacCode(accent_code))?;
let base_gid = seac_to_gid(base_code).ok_or(Error::InvalidSeacCode(base_code))?;
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))
}
#[derive(PartialEq)]
enum SeacMode {
Explicit,
Implicit,
}
struct Evaluator<'a, S> {
context: &'a dyn CharstringContext,
is_type1: bool,
blend_state: Option<BlendState<'a>>,
sink: &'a mut S,
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,
}
impl<'a, S> Evaluator<'a, S>
where
S: CommandSink,
{
fn new(
context: &'a dyn CharstringContext,
blend_state: Option<BlendState<'a>>,
sink: &'a mut S,
) -> 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,
}
}
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::CharstringNestingDepthLimitExceeded);
}
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::CharstringNestingDepthLimitExceeded);
}
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::MissingBlendState)?;
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::MissingBlendState)?;
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)?;
}
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.len() > 1 => {
self.handle_mm_blend(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::CharstringNestingDepthLimitExceeded);
}
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::StackUnderflow)?;
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 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,
}
impl Operator {
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,
6 => Seac,
7 => Sbw,
12 => Div,
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: OutlinePen> 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::CharstringNestingDepthLimitExceeded));
}
#[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::InvalidSeacCode(base_code))
}
fn global_subr(&self, _index: i32) -> Result<&[u8], Error> {
Err(Error::MissingSubroutines)
}
fn subr(&self, _index: i32) -> Result<&[u8], Error> {
Err(Error::MissingSubroutines)
}
}
#[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::InvalidSeacCode(base_code))
}
fn global_subr(&self, _index: i32) -> Result<&[u8], Error> {
Err(Error::MissingSubroutines)
}
fn subr(&self, _index: i32) -> Result<&[u8], Error> {
Err(Error::MissingSubroutines)
}
fn weight_vector(&self) -> &[Fixed] {
&self.0
}
}
}