// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Type 2 charstring evaluation (path ops only; hints ignored).
///
/// Ported from `read-fonts` CFF/CFF2 charstring evaluator logic.
///|
fn type2_read_u16_be(view : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 2 > view.length() {
None
} else {
let b0 = view.at(offset).to_int()
let b1 = view.at(offset + 1).to_int()
Some((b0 << 8) | b1)
}
}
///|
fn type2_read_i16_be(view : BytesView, offset : Int) -> Int? {
match type2_read_u16_be(view, offset) {
None => None
Some(u) => if u >= 0x8000 { Some(u - 0x10000) } else { Some(u) }
}
}
///|
fn type2_read_i32_be(view : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 4 > view.length() {
None
} else {
let b0 = view.at(offset).to_int()
let b1 = view.at(offset + 1).to_int()
let b2 = view.at(offset + 2).to_int()
let b3 = view.at(offset + 3).to_int()
let u = (b0.reinterpret_as_uint() << 24) |
(b1.reinterpret_as_uint() << 16) |
(b2.reinterpret_as_uint() << 8) |
b3.reinterpret_as_uint()
Some(u.reinterpret_as_int())
}
}
///|
fn type2_parse_number(data : BytesView, pos : Int, b0 : Int) -> (Double, Int)? {
// Returns (value, new_pos).
match b0 {
28 =>
match type2_read_i16_be(data, pos) {
None => None
Some(v) => Some((v.to_double(), pos + 2))
}
// 16.16 fixed point (CFF2).
255 =>
match type2_read_i32_be(data, pos) {
None => None
Some(bits) => Some((bits.to_double() / 65536.0, pos + 4))
}
32..=246 => Some(((b0 - 139).to_double(), pos))
247..=250 =>
if pos >= data.length() {
None
} else {
let b1 = data.at(pos).to_int()
Some((((b0 - 247) * 256 + b1 + 108).to_double(), pos + 1))
}
251..=254 =>
if pos >= data.length() {
None
} else {
let b1 = data.at(pos).to_int()
Some(((-(b0 - 251) * 256 - b1 - 108).to_double(), pos + 1))
}
_ => None
}
}
///|
fn type2_subr_bias(count : Int) -> Int {
if count < 1240 {
107
} else if count < 33900 {
1131
} else {
32768
}
}
///|
priv struct Type2Index {
data : BytesView
count : Int
off_size : Int
offsets_base : Int
objects_base : Int
bias : Int
}
///|
fn type2_index1_read(data : BytesView) -> Type2Index? {
// CFF INDEX (count u16, offSize u8) with 1-based offsets.
let count = match type2_read_u16_be(data, 0) {
None => return None
Some(v) => v
}
if count == 0 {
Some({
data,
count: 0,
off_size: 0,
offsets_base: 2,
objects_base: 2,
bias: type2_subr_bias(0),
})
} else {
if 3 > data.length() {
return None
}
let off_size = data.at(2).to_int()
if off_size <= 0 || off_size > 4 {
return None
}
let offsets_base = 3
let objects_base = offsets_base + (count + 1) * off_size
if objects_base > data.length() {
return None
}
Some({
data,
count,
off_size,
offsets_base,
objects_base,
bias: type2_subr_bias(count),
})
}
}
///|
fn type2_index2_read(data : BytesView) -> Type2Index? {
// CFF2 INDEX (count u32, offSize u8) with 1-based offsets.
if data.length() < 4 {
return None
}
let count_u = match type2_read_i32_be(data, 0) {
None => return None
Some(v) => v
}
if count_u < 0 {
return None
}
let count = count_u
if count == 0 {
Some({
data,
count: 0,
off_size: 0,
offsets_base: 4,
objects_base: 4,
bias: type2_subr_bias(0),
})
} else {
if data.length() < 5 {
return None
}
let off_size = data.at(4).to_int()
if off_size <= 0 || off_size > 4 {
return None
}
let offsets_base = 5
let objects_base = offsets_base + (count + 1) * off_size
if objects_base > data.length() {
return None
}
Some({
data,
count,
off_size,
offsets_base,
objects_base,
bias: type2_subr_bias(count),
})
}
}
///|
fn type2_index_read_offset(ix : Type2Index, index : Int) -> Int? {
if ix.count == 0 {
if index == 0 {
Some(0)
} else {
None
}
} else {
if index < 0 || index > ix.count {
return None
}
let pos = ix.offsets_base + index * ix.off_size
if pos < 0 || pos + ix.off_size > ix.data.length() {
return None
}
let mut v = 0
for i in 0.. BytesView? {
if index < 0 || index >= ix.count {
return None
}
let start = match type2_index_read_offset(ix, index) {
None => return None
Some(v) => v
}
let end = match type2_index_read_offset(ix, index + 1) {
None => return None
Some(v) => v
}
let s = ix.objects_base + start
let e = ix.objects_base + end
if s < 0 || e < s || e > ix.data.length() {
None
} else {
Some(ix.data[s:e])
}
}
///|
fn type2_index_subr(ix : Type2Index, subr_number : Int) -> BytesView? {
let idx = subr_number + ix.bias
type2_index_get(ix, idx)
}
///|
const TYPE2_F2DOT14_SCALE : Double = 16384.0
///|
fn type2_read_u32_be_int(view : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 4 > view.length() {
None
} else {
let b0 = view.at(offset).to_int()
let b1 = view.at(offset + 1).to_int()
let b2 = view.at(offset + 2).to_int()
let b3 = view.at(offset + 3).to_int()
Some((b0 << 24) | (b1 << 16) | (b2 << 8) | b3)
}
}
///|
/// Parsed view of an ItemVariationStore (used by CFF2 `vsindex`/`blend`).
priv struct Type2IvsCtx {
store : BytesView
store_off : Int
axis_count : Int
region_count : Int
region_list_off : Int
data_count : Int
data_offsets_off : Int
}
///|
fn type2_ivs_ctx(store : BytesView, store_off : Int) -> Type2IvsCtx? {
// ItemVariationStore header:
// u16 format, u32 regionListOff, u16 dataCount, u32[dataCount] dataOffsets
let format = type2_read_u16_be(store, store_off).unwrap_or(-1)
if format != 1 {
return None
}
let region_list_rel = type2_read_u32_be_int(store, store_off + 2).unwrap_or(
-1,
)
let data_count = type2_read_u16_be(store, store_off + 6).unwrap_or(-1)
if region_list_rel <= 0 || data_count <= 0 {
return None
}
let data_offsets_off = store_off + 8
let region_list_off = store_off + region_list_rel
if region_list_off < 0 || region_list_off + 4 > store.length() {
return None
}
let axis_count = type2_read_u16_be(store, region_list_off).unwrap_or(-1)
let region_count = type2_read_u16_be(store, region_list_off + 2).unwrap_or(-1)
if axis_count <= 0 || region_count <= 0 {
return None
}
// Each region has axis_count AxisRegion records: start/peak/end i16 => 6 bytes.
let regions_len = 4 + region_count * axis_count * 6
if regions_len < 0 || region_list_off + regions_len > store.length() {
return None
}
// Validate dataOffsets array bounds.
let need_offsets = data_offsets_off + data_count * 4
if need_offsets < data_offsets_off || need_offsets > store.length() {
return None
}
Some({
store,
store_off,
axis_count,
region_count,
region_list_off,
data_count,
data_offsets_off,
})
}
///|
fn type2_ivs_item_region_indices(
ctx : Type2IvsCtx,
store_index : Int,
) -> Array[Int]? {
if store_index < 0 || store_index >= ctx.data_count {
return None
}
let data_rel = type2_read_u32_be_int(
ctx.store,
ctx.data_offsets_off + store_index * 4,
).unwrap_or(-1)
if data_rel <= 0 {
return None
}
let data_off = ctx.store_off + data_rel
if data_off < 0 || data_off + 6 > ctx.store.length() {
return None
}
// ItemVariationData header:
// u16 itemCount, u16 shortDeltaCount, u16 regionIndexCount, u16[regionIndexCount] regionIndexes, deltaSets...
let region_index_count = type2_read_u16_be(ctx.store, data_off + 4).unwrap_or(
-1,
)
if region_index_count <= 0 {
return None
}
let region_indexes_off = data_off + 6
let need = region_indexes_off + region_index_count * 2
if need < region_indexes_off || need > ctx.store.length() {
return None
}
let out : Array[Int] = Array::new()
for i in 0.. Double {
if region_index < 0 || region_index >= ctx.region_count {
return 0.0
}
let region_off = ctx.region_list_off + 4 + region_index * ctx.axis_count * 6
if region_off < 0 || region_off + ctx.axis_count * 6 > ctx.store.length() {
return 0.0
}
let mut scalar = 1.0
for i in 0.. peak || peak > end || (start < 0.0 && end > 0.0) {
continue
}
let coord = if i < coords.length() {
coords.at(i).to_double() / TYPE2_F2DOT14_SCALE
} else {
0.0
}
if coord < start || coord > end {
return 0.0
}
if coord == peak {
continue
}
if coord < peak {
scalar = scalar * (coord - start) / (peak - start)
} else {
scalar = scalar * (end - coord) / (end - peak)
}
}
scalar
}
///|
/// State for evaluating CFF2 `vsindex`/`blend` operators.
priv struct Type2BlendState {
ctx : Type2IvsCtx
coords : ArrayView[@moon_skrifa.NormalizedCoord]
mut store_index : Int
mut region_indices : Array[Int]
mut scalars : Array[Double]
}
///|
fn Type2BlendState::from_store(
ctx : Type2IvsCtx,
coords : ArrayView[@moon_skrifa.NormalizedCoord],
store_index : Int,
) -> Type2BlendState? {
let s = Type2BlendState::{
ctx,
coords,
store_index: 0,
region_indices: Array::new(),
scalars: Array::new(),
}
if Type2BlendState::set_store_index(s, store_index) {
Some(s)
} else {
None
}
}
///|
fn Type2BlendState::set_store_index(
self : Type2BlendState,
store_index : Int,
) -> Bool {
if self.store_index == store_index && !self.region_indices.is_empty() {
return true
}
let indices = match type2_ivs_item_region_indices(self.ctx, store_index) {
None => return false
Some(v) => v
}
let scalars : Array[Double] = Array::new()
for ix in indices.iter() {
scalars.push(type2_ivs_region_scalar(self.ctx, ix, self.coords))
}
self.store_index = store_index
self.region_indices = indices
self.scalars = scalars
true
}
///|
fn Type2BlendState::region_count(self : Type2BlendState) -> Int {
self.region_indices.length()
}
///|
fn type2_blend_state_new(
store : BytesView,
store_off : Int,
coords : ArrayView[@moon_skrifa.NormalizedCoord],
) -> Type2BlendState? {
match type2_ivs_ctx(store, store_off) {
None => None
Some(ctx) => Type2BlendState::from_store(ctx, coords, 0)
}
}
///|
priv struct Type2Eval {
global_subrs : Type2Index?
local_subrs : Type2Index?
blend_state : Type2BlendState?
mut cff_hinter : CffHintingSink?
out : Array[PathElement]
mut is_open : Bool
mut have_read_width : Bool
mut stem_count : Int
mut x : Double
mut y : Double
stack : Array[Double]
mut stack_ix : Int
}
///|
fn Type2Eval::Type2Eval(
global_subrs : Type2Index?,
local_subrs : Type2Index?,
blend_state : Type2BlendState?,
) -> Type2Eval {
{
global_subrs,
local_subrs,
blend_state,
cff_hinter: None,
out: Array::new(),
is_open: false,
have_read_width: false,
stem_count: 0,
x: 0.0,
y: 0.0,
stack: Array::new(),
stack_ix: 0,
}
}
///|
fn Type2Eval::reset_stack(self : Type2Eval) -> Unit {
self.stack.clear()
self.stack_ix = 0
}
///|
fn type2_number_to_int(v : Double) -> Int? {
// Charstring stack values are typically integers; reject non-integral values
// for operators that require integer operands (e.g. subr indices).
let r = v.round()
if (v - r).abs() <= 0.000001 {
Some(r.to_int())
} else {
None
}
}
///|
fn Type2Eval::pop_int(self : Type2Eval) -> Int? {
if self.stack.length() <= 0 {
return None
}
let v = self.stack.at(self.stack.length() - 1)
self.stack.pop() |> ignore
type2_number_to_int(v)
}
///|
fn Type2Eval::coords_remaining(self : Type2Eval) -> Int {
let len = self.stack.length()
let ix = self.stack_ix
if len < ix {
0
} else {
len - ix
}
}
///|
fn Type2Eval::apply_blend(self : Type2Eval) -> Bool {
let blend_state = match self.blend_state {
None => return false
Some(s) => s
}
let target_value_count = match Type2Eval::pop_int(self) {
None => return false
Some(v) => v
}
if target_value_count < 0 {
return false
}
let region_count = Type2BlendState::region_count(blend_state)
let operand_count = target_value_count * (region_count + 1)
if operand_count < 0 || self.stack.length() < operand_count {
return false
}
let start = self.stack.length() - operand_count
let values_start = start
let deltas_start = start + target_value_count
for region_ix in 0.. new_len {
self.stack.pop() |> ignore
}
true
}
///|
fn Type2Eval::close_path(self : Type2Eval) -> Unit {
if self.is_open {
match self.cff_hinter {
None => self.out.push(Close)
Some(h) => CffHintingSink::close(h)
}
self.is_open = false
}
}
///|
fn Type2Eval::emit_move_to(self : Type2Eval) -> Unit {
match self.cff_hinter {
None => self.out.push(MoveTo(self.x, self.y))
Some(h) =>
CffHintingSink::move_to(
h,
CffFixed::from_f64(self.x),
CffFixed::from_f64(self.y),
)
}
}
///|
fn Type2Eval::emit_line_to(self : Type2Eval) -> Unit {
match self.cff_hinter {
None => self.out.push(LineTo(self.x, self.y))
Some(h) =>
CffHintingSink::line_to(
h,
CffFixed::from_f64(self.x),
CffFixed::from_f64(self.y),
)
}
}
///|
fn Type2Eval::emit_curve_to(
self : Type2Eval,
cx0 : Double,
cy0 : Double,
cx1 : Double,
cy1 : Double,
x : Double,
y : Double,
) -> Unit {
match self.cff_hinter {
None => self.out.push(CurveTo(cx0, cy0, cx1, cy1, x, y))
Some(h) =>
CffHintingSink::curve_to(
h,
CffFixed::from_f64(cx0),
CffFixed::from_f64(cy0),
CffFixed::from_f64(cx1),
CffFixed::from_f64(cy1),
CffFixed::from_f64(x),
CffFixed::from_f64(y),
)
}
}
///|
priv enum Type2PointMode {
DxDy
XDy
DxY
DxInitialY
DLargerCoordDist
DxMaybeDy(Bool)
MaybeDxDy(Bool)
}
///|
fn Type2Eval::emit_curves(
self : Type2Eval,
modes : ArrayView[Type2PointMode],
) -> Bool {
let initial_x = self.x
let initial_y = self.y
let mut count = 0
let mut cx0 = 0.0
let mut cy0 = 0.0
let mut cx1 = 0.0
let mut cy1 = 0.0
for mode in modes.iter() {
let used = match mode {
DxDy => {
let dx = self.stack.at(self.stack_ix)
let dy = self.stack.at(self.stack_ix + 1)
self.x = self.x + dx
self.y = self.y + dy
2
}
XDy => {
let dy = self.stack.at(self.stack_ix)
self.y = self.y + dy
1
}
DxY => {
let dx = self.stack.at(self.stack_ix)
self.x = self.x + dx
1
}
DxInitialY => {
let dx = self.stack.at(self.stack_ix)
self.x = self.x + dx
self.y = initial_y
1
}
DLargerCoordDist => {
let delta = self.stack.at(self.stack_ix)
let dx = (self.x - initial_x).abs()
let dy = (self.y - initial_y).abs()
if dx > dy {
self.x = self.x + delta
self.y = initial_y
} else {
self.y = self.y + delta
self.x = initial_x
}
1
}
DxMaybeDy(do_dy) => {
let dx = self.stack.at(self.stack_ix)
self.x = self.x + dx
if do_dy {
let dy = self.stack.at(self.stack_ix + 1)
self.y = self.y + dy
2
} else {
1
}
}
MaybeDxDy(do_dx) => {
let dy = self.stack.at(self.stack_ix)
self.y = self.y + dy
if do_dx {
let dx = self.stack.at(self.stack_ix + 1)
self.x = self.x + dx
2
} else {
1
}
}
}
self.stack_ix = self.stack_ix + used
if count == 2 {
Type2Eval::emit_curve_to(self, cx0, cy0, cx1, cy1, self.x, self.y)
count = 0
} else if count == 0 {
cx0 = self.x
cy0 = self.y
count = 1
} else {
cx1 = self.x
cy1 = self.y
count = 2
}
}
true
}
///|
fn Type2Eval::read_operator(
data : BytesView,
pos : Int,
b0 : Int,
) -> (Int, Int)? {
// Returns (operator_code, new_pos). For escaped operators, returns 1200+op2.
if b0 == 12 {
if pos >= data.length() {
None
} else {
let b1 = data.at(pos).to_int()
Some((1200 + b1, pos + 1))
}
} else {
Some((b0, pos))
}
}
///|
fn Type2Eval::eval(self : Type2Eval, data : BytesView, depth : Int) -> Bool {
if depth > 10 {
return false
}
let mut pos = 0
while pos < data.length() {
let b0 = data.at(pos).to_int()
pos = pos + 1
if b0 == 28 || b0 == 255 || (b0 >= 32 && b0 <= 254) {
match type2_parse_number(data, pos, b0) {
None => return false
Some((v, p)) => {
self.stack.push(v)
pos = p
}
}
continue
}
match Type2Eval::read_operator(data, pos, b0) {
None => return false
Some((op, p)) => {
pos = p
match Type2Eval::eval_operator(self, data, op, pos, depth) {
None => return false
Some((cont, new_pos)) => {
pos = new_pos
if !cont {
break
}
}
}
}
}
}
if depth == 0 {
// Some CFF/CFF2 charstrings may terminate without an explicit endchar.
// Upstream closes any pending subpath in this case.
Type2Eval::close_path(self)
match self.cff_hinter {
None => ()
Some(h) => CffHintingSink::finish(h)
}
}
true
}
///|
fn Type2Eval::eval_operator(
self : Type2Eval,
data : BytesView,
op : Int,
pos : Int,
depth : Int,
) -> (Bool, Int)? {
// Returns (continue, new_pos).
let mut new_pos = pos
// Two-byte ops are represented as 1200+op2.
match op {
// return
11 => Some((false, new_pos))
// endchar
14 => {
if self.stack.length() != 0 && !self.have_read_width {
self.have_read_width = true
self.stack.clear()
}
Type2Eval::close_path(self)
self.reset_stack()
Some((false, new_pos))
}
// hstem/vstem/hstemhm/vstemhm: ignore but consume hints (and possible width).
1 | 3 | 18 | 23 => {
let mut i = 0
if !self.have_read_width {
if (self.stack.length() & 1) != 0 {
i = 1
}
self.have_read_width = true
}
let len = self.stack.length() - i
let is_horizontal = op == 1 || op == 18
match self.cff_hinter {
None => ()
Some(h) => {
let mut u = CffFixed::zero()
let mut j = i
while j < self.stack.length() {
let a0 = CffFixed::from_f64(self.stack.at(j))
let w = CffFixed::from_f64(self.stack.at(j + 1))
u = u.add(a0)
let v = u.wrapping_add(w)
if is_horizontal {
CffHintingSink::hstem(h, u, v)
}
u = v
j = j + 2
}
}
}
self.stem_count = self.stem_count + len / 2
self.reset_stack()
Some((true, new_pos))
}
// hintmask/cntrmask
19 | 20 => {
let mut i = 0
if !self.have_read_width {
if (self.stack.length() & 1) != 0 {
i = 1
}
self.have_read_width = true
}
let len = self.stack.length() - i
// Implied VSTEMHM operators. We only track HSTEMs for Y hinting, but we
// still need to consume operands and update stem_count.
match self.cff_hinter {
None => ()
Some(_) => {
let mut u = CffFixed::zero()
let mut j = i
while j < self.stack.length() {
let a0 = CffFixed::from_f64(self.stack.at(j))
let w = CffFixed::from_f64(self.stack.at(j + 1))
u = u.add(a0)
let v = u.wrapping_add(w)
u = v
j = j + 2
}
}
}
self.stem_count = self.stem_count + len / 2
let mask_bytes = (self.stem_count + 7) / 8
if new_pos + mask_bytes > data.length() {
return None
}
let mask = data[new_pos:new_pos + mask_bytes]
match self.cff_hinter {
None => ()
Some(h) =>
if op == 19 {
CffHintingSink::hint_mask(h, mask)
} else {
CffHintingSink::counter_mask(h, mask)
}
}
new_pos = new_pos + mask_bytes
self.reset_stack()
Some((true, new_pos))
}
// rmoveto
21 => {
let mut i = 0
if !self.have_read_width {
if self.stack.length() == 3 {
i = 1
}
self.have_read_width = true
}
Type2Eval::close_path(self)
self.is_open = true
let dx = self.stack.at(i)
let dy = self.stack.at(i + 1)
self.x = self.x + dx
self.y = self.y + dy
Type2Eval::emit_move_to(self)
self.reset_stack()
Some((true, new_pos))
}
// hmoveto/vmoveto
22 | 4 => {
let mut i = 0
if !self.have_read_width {
if self.stack.length() == 2 {
i = 1
}
self.have_read_width = true
}
Type2Eval::close_path(self)
self.is_open = true
let delta = self.stack.at(i)
if op == 22 {
self.x = self.x + delta
} else {
self.y = self.y + delta
}
Type2Eval::emit_move_to(self)
self.reset_stack()
Some((true, new_pos))
}
// rlineto
5 => {
let mut i = 0
while i < self.stack.length() {
let dx = self.stack.at(i)
let dy = self.stack.at(i + 1)
self.x = self.x + dx
self.y = self.y + dy
Type2Eval::emit_line_to(self)
i = i + 2
}
self.reset_stack()
Some((true, new_pos))
}
// hlineto/vlineto
6 | 7 => {
let mut is_x = op == 6
for i in 0.. {
if (self.stack.length() & 1) != 0 {
self.y = self.y + self.stack.at(0)
self.stack_ix = 1
}
while Type2Eval::coords_remaining(self) >= 4 {
let modes = Array::from_fixed_array([DxY, DxDy, DxY])
if !Type2Eval::emit_curves(self, modes.op_as_view()) {
return None
}
}
self.reset_stack()
Some((true, new_pos))
}
// hvcurveto/vhcurveto
31 | 30 => {
let count1 = self.stack.length()
let count = count1 & (2 |> Int::lnot)
let mut is_horizontal = op == 31
self.stack_ix = count1 - count
while self.stack_ix < count {
let do_last_delta = count - self.stack_ix == 5
if is_horizontal {
let modes = Array::from_fixed_array([
DxY,
DxDy,
MaybeDxDy(do_last_delta),
])
if !Type2Eval::emit_curves(self, modes.op_as_view()) {
return None
}
} else {
let modes = Array::from_fixed_array([
XDy,
DxDy,
DxMaybeDy(do_last_delta),
])
if !Type2Eval::emit_curves(self, modes.op_as_view()) {
return None
}
}
is_horizontal = !is_horizontal
}
self.reset_stack()
Some((true, new_pos))
}
// rrcurveto / rcurveline
8 | 24 => {
while Type2Eval::coords_remaining(self) >= 6 {
let modes = Array::from_fixed_array([DxDy, DxDy, DxDy])
if !Type2Eval::emit_curves(self, modes.op_as_view()) {
return None
}
}
if op == 24 {
let dx = self.stack.at(self.stack_ix)
let dy = self.stack.at(self.stack_ix + 1)
self.x = self.x + dx
self.y = self.y + dy
Type2Eval::emit_line_to(self)
}
self.reset_stack()
Some((true, new_pos))
}
// rlinecurve
25 => {
while Type2Eval::coords_remaining(self) > 6 {
let dx = self.stack.at(self.stack_ix)
let dy = self.stack.at(self.stack_ix + 1)
self.x = self.x + dx
self.y = self.y + dy
Type2Eval::emit_line_to(self)
self.stack_ix = self.stack_ix + 2
}
let modes = Array::from_fixed_array([DxDy, DxDy, DxDy])
if !Type2Eval::emit_curves(self, modes.op_as_view()) {
return None
}
self.reset_stack()
Some((true, new_pos))
}
// vvcurveto
26 => {
if (self.stack.length() & 1) != 0 {
self.x = self.x + self.stack.at(0)
self.stack_ix = 1
}
while Type2Eval::coords_remaining(self) > 0 {
let modes = Array::from_fixed_array([XDy, DxDy, XDy])
if !Type2Eval::emit_curves(self, modes.op_as_view()) {
return None
}
}
self.reset_stack()
Some((true, new_pos))
}
// flex/hflex/hflex1/flex1 (12 35..37, 12 34)
1234 | 1235 | 1236 | 1237 => {
let modes = match op {
1235 => Array::from_fixed_array([DxDy, DxDy, DxDy, DxDy, DxDy, DxDy])
1234 => Array::from_fixed_array([DxY, DxDy, DxY, DxY, DxInitialY, DxY])
1236 =>
Array::from_fixed_array([DxDy, DxDy, DxY, DxY, DxDy, DxInitialY])
_ =>
Array::from_fixed_array([
DxDy,
DxDy,
DxDy,
DxDy,
DxDy,
DLargerCoordDist,
])
}
if !Type2Eval::emit_curves(self, modes.op_as_view()) {
return None
}
self.reset_stack()
Some((true, new_pos))
}
// callsubr / callgsubr
10 | 29 => {
let subr_number = match Type2Eval::pop_int(self) {
None => return None
Some(v) => v
}
let ix = if op == 10 { self.local_subrs } else { self.global_subrs }
match ix {
None => None
Some(index) =>
match type2_index_subr(index, subr_number) {
None => None
Some(subr) =>
if !Type2Eval::eval(self, subr, depth + 1) {
None
} else {
Some((true, new_pos))
}
}
}
}
// vsindex (variation store index)
15 =>
match self.blend_state {
None => None
Some(s) =>
match Type2Eval::pop_int(self) {
None => None
Some(v) =>
if Type2BlendState::set_store_index(s, v) {
Some((true, new_pos))
} else {
None
}
}
}
// blend
16 =>
if Type2Eval::apply_blend(self) {
Some((true, new_pos))
} else {
None
}
_ => Some((true, new_pos))
}
}
///|
fn type2_path(
charstring : BytesView,
global_subrs : Type2Index?,
local_subrs : Type2Index?,
blend_state : Type2BlendState?,
) -> Array[PathElement]? {
let eval = Type2Eval(global_subrs, local_subrs, blend_state)
if Type2Eval::eval(eval, charstring, 0) {
Some(eval.out)
} else {
None
}
}
///|
fn type2_path_hinted(
charstring : BytesView,
global_subrs : Type2Index?,
local_subrs : Type2Index?,
blend_state : Type2BlendState?,
hint_state : HintState,
hint_matrix : CffMatrix?,
) -> Array[PathElement]? {
let eval = Type2Eval(global_subrs, local_subrs, blend_state)
eval.cff_hinter = Some(CffHintingSink(hint_state, eval.out, hint_matrix))
if Type2Eval::eval(eval, charstring, 0) {
Some(eval.out)
} else {
None
}
}