// Copyright 2026 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.

///|
pub struct CffBounds {
  x_min : Double
  y_min : Double
  x_max : Double
  y_max : Double
} derive(Eq, Show, ToJson)

priv struct CffBoundsBuilder {
  mut has_bounds : Bool
  mut min_x : Double
  mut min_y : Double
  mut max_x : Double
  mut max_y : Double
}

fn CffBoundsBuilder::new() -> CffBoundsBuilder {
  CffBoundsBuilder::{
    has_bounds: false,
    min_x: 0.0,
    min_y: 0.0,
    max_x: 0.0,
    max_y: 0.0,
  }
}

fn CffBoundsBuilder::update(self : CffBoundsBuilder, x : Double, y : Double) -> Unit {
  if !self.has_bounds {
    self.has_bounds = true
    self.min_x = x
    self.max_x = x
    self.min_y = y
    self.max_y = y
    return
  }
  if x < self.min_x {
    self.min_x = x
  }
  if x > self.max_x {
    self.max_x = x
  }
  if y < self.min_y {
    self.min_y = y
  }
  if y > self.max_y {
    self.max_y = y
  }
}

fn CffBoundsBuilder::merge(self : CffBoundsBuilder, bounds : CffBounds) -> Unit {
  self.update(bounds.x_min, bounds.y_min)
  self.update(bounds.x_max, bounds.y_max)
}

fn CffBoundsBuilder::result(self : CffBoundsBuilder) -> CffBounds? {
  if !self.has_bounds {
    return None
  }
  Some(CffBounds::{
    x_min: self.min_x,
    y_min: self.min_y,
    x_max: self.max_x,
    y_max: self.max_y,
  })
}

priv struct CffPoint {
  x : Double
  y : Double
}

fn CffPoint::offset(self : CffPoint, dx : Double, dy : Double) -> CffPoint {
  CffPoint::{ x: self.x + dx, y: self.y + dy }
}

fn CffPoint::offset_x(self : CffPoint, dx : Double) -> CffPoint {
  CffPoint::{ x: self.x + dx, y: self.y }
}

fn CffPoint::offset_y(self : CffPoint, dy : Double) -> CffPoint {
  CffPoint::{ x: self.x, y: self.y + dy }
}

priv struct CffCharstringState {
  mut x : Double
  mut y : Double
  mut has_width : Bool
  mut stem_count : Int
  bounds : CffBoundsBuilder
}

fn CffCharstringState::new() -> CffCharstringState {
  CffCharstringState::{
    x: 0.0,
    y: 0.0,
    has_width: false,
    stem_count: 0,
    bounds: CffBoundsBuilder::new(),
  }
}

fn CffCharstringState::current_point(self : CffCharstringState) -> CffPoint {
  CffPoint::{ x: self.x, y: self.y }
}

fn CffCharstringState::moveto(self : CffCharstringState, pt : CffPoint) -> Unit {
  self.x = pt.x
  self.y = pt.y
  self.bounds.update(pt.x, pt.y)
}

fn CffCharstringState::line_to(self : CffCharstringState, pt : CffPoint) -> Unit {
  self.bounds.update(pt.x, pt.y)
  self.x = pt.x
  self.y = pt.y
}

fn CffCharstringState::curve_to(
  self : CffCharstringState,
  pt1 : CffPoint,
  pt2 : CffPoint,
  pt3 : CffPoint,
) -> Unit {
  self.bounds.update(pt1.x, pt1.y)
  self.bounds.update(pt2.x, pt2.y)
  self.bounds.update(pt3.x, pt3.y)
  self.x = pt3.x
  self.y = pt3.y
}

priv struct Cff1SeacContext {
  charstrings : CffIndex
  charset_offset : Int
  charset_map : Map[Int, Int]?
  glyph_count : Int
}

fn Cff1SeacContext::sid_to_gid(self : Cff1SeacContext, sid : Int) -> Int? {
  cff1_sid_to_gid(self.charset_offset, self.charset_map, sid, self.glyph_count)
}

priv struct Cff2BlendState {
  mut ivs : Int
  mut seen_vsindex : Bool
  mut seen_blend : Bool
  var_store : @ot_var.ItemVariationStore?
  coords : Array[Int]
}

fn Cff2BlendState::new(
  var_store : @ot_var.ItemVariationStore?,
  ivs : Int,
  coords : Array[Int],
) -> Cff2BlendState {
  Cff2BlendState::{
    ivs,
    seen_vsindex: false,
    seen_blend: false,
    var_store,
    coords,
  }
}

fn Cff2BlendState::region_count(self : Cff2BlendState) -> Result[Int, CffError] {
  let store = match self.var_store {
    None => return Err(InvalidFormat)
    Some(value) => value
  }
  match store.region_count(self.ivs) {
    None => Err(InvalidFormat)
    Some(value) => Ok(value)
  }
}

fn Cff2BlendState::region_scalars(self : Cff2BlendState) -> Result[Array[Double]?, CffError] {
  if self.coords.is_empty() {
    return Ok(None)
  }
  let store = match self.var_store {
    None => return Err(InvalidFormat)
    Some(value) => value
  }
  match store.region_scalars(self.ivs, self.coords) {
    None => Err(InvalidFormat)
    Some(values) => Ok(Some(values))
  }
}

///|
pub fn cff_charstring_bounds(
  data : BytesView,
  global_subrs : CffIndex?,
  local_subrs : CffIndex?,
) -> Result[CffBounds?, CffError] {
  let state = CffCharstringState::new()
  let stack : Array[Double] = []
  match interpret_charstring(
    data,
    state,
    stack,
    global_subrs,
    local_subrs,
    0,
    false,
    None,
    false,
  ) {
    Err(err) => Err(err)
    Ok(_) => Ok(state.bounds.result())
  }
}

fn cff1_charstring_bounds(
  data : BytesView,
  global_subrs : CffIndex?,
  local_subrs : CffIndex?,
  seac : Cff1SeacContext,
) -> Result[CffBounds?, CffError] {
  let state = CffCharstringState::new()
  let stack : Array[Double] = []
  match interpret_charstring(
    data,
    state,
    stack,
    global_subrs,
    local_subrs,
    0,
    false,
    Some(seac),
    false,
  ) {
    Err(err) => Err(err)
    Ok(_) => Ok(state.bounds.result())
  }
}

fn cff1_charstring_seac_components(
  data : BytesView,
  global_subrs : CffIndex?,
  local_subrs : CffIndex?,
  seac : Cff1SeacContext,
) -> Result[(Int, Int)?, CffError] {
  let state = CffCharstringState::new()
  let stack : Array[Double] = []
  let records : Array[(Int, Int)] = []
  let report = (base : Int, accent : Int) => {
    if records.is_empty() {
      records.push((base, accent))
    }
  }
  match interpret_charstring(
    data,
    state,
    stack,
    global_subrs,
    local_subrs,
    0,
    false,
    Some(seac),
    false,
    seac_report=report,
  ) {
    Err(err) => Err(err)
    Ok(_) =>
      if records.is_empty() { Ok(None) } else { Ok(Some(records[0])) }
  }
}

///|
pub fn cff2_charstring_bounds(
  data : BytesView,
  global_subrs : CffIndex?,
  local_subrs : CffIndex?,
  var_store : @ot_var.ItemVariationStore?,
  ivs : Int,
  coords : Array[Int],
) -> Result[CffBounds?, CffError] {
  let state = CffCharstringState::new()
  let blend_state = Cff2BlendState::new(var_store, ivs, coords)
  let stack : Array[Double] = []
  match interpret_charstring_cff2(
    data,
    state,
    stack,
    blend_state,
    global_subrs,
    local_subrs,
    0,
    false,
  ) {
    Err(err) => Err(err)
    Ok(_) => Ok(state.bounds.result())
  }
}

fn subr_bias(count : Int) -> Int {
  if count < 1240 {
    107
  } else if count < 33900 {
    1131
  } else {
    32768
  }
}

fn interpret_charstring(
  data : BytesView,
  state : CffCharstringState,
  stack : Array[Double],
  global_subrs : CffIndex?,
  local_subrs : CffIndex?,
  depth : Int,
  allow_return : Bool,
  seac : Cff1SeacContext?,
  in_seac : Bool,
  seac_report? : (Int, Int) -> Unit = (_, _) => (),
) -> Result[Unit, CffError] {
  if depth > 16 {
    return Err(InvalidFormat)
  }
  let mut pos = 0
  while pos < data.length() {
    let b0 = data[pos].to_int()
    if b0 >= 32 || b0 == 28 || b0 == 255 {
      let parsed = parse_charstring_number(data, pos)
      match parsed {
        Err(err) => return Err(err)
        Ok((value, next_pos)) => {
          stack.push(value)
          pos = next_pos
          continue
        }
      }
    }
    match b0 {
      1 | 3 | 18 | 23 => {
        let offset = width_offset(state, stack)
        let count = stack.length() - offset
        if count < 0 || (count % 2) != 0 {
          return Err(InvalidFormat)
        }
        state.stem_count = state.stem_count + (count / 2)
        stack.clear()
        pos = pos + 1
      }
      19 | 20 => {
        let offset = width_offset(state, stack)
        let count = stack.length() - offset
        if count < 0 || (count % 2) != 0 {
          return Err(InvalidFormat)
        }
        state.stem_count = state.stem_count + (count / 2)
        stack.clear()
        pos = pos + 1
        let mask_bytes = (state.stem_count + 7) / 8
        if pos + mask_bytes > data.length() {
          return Err(UnexpectedEof)
        }
        pos = pos + mask_bytes
      }
      4 => {
        let offset = width_offset(state, stack)
        if stack.length() - offset < 1 {
          return Err(InvalidFormat)
        }
        let dy = stack[offset]
          let pt = state.current_point().offset(0.0, dy)
        state.moveto(pt)
        stack.clear()
        pos = pos + 1
      }
      5 => {
        if (stack.length() % 2) != 0 {
          return Err(InvalidFormat)
        }
        let mut i = 0
        while i + 1 < stack.length() {
          let dx = stack[i]
          let dy = stack[i + 1]
          let pt = state.current_point().offset(dx, dy)
          state.line_to(pt)
          i = i + 2
        }
        stack.clear()
        pos = pos + 1
      }
      6 | 7 => {
        let mut horizontal = b0 == 6
        for value in stack {
          let pt =
            if horizontal {
              state.current_point().offset(value, 0.0)
            } else {
              state.current_point().offset(0.0, value)
            }
          state.line_to(pt)
          horizontal = !horizontal
        }
        stack.clear()
        pos = pos + 1
      }
      8 => {
        if (stack.length() % 6) != 0 {
          return Err(InvalidFormat)
        }
        let mut i = 0
        while i + 5 < stack.length() {
          let pt1 = state.current_point().offset(stack[i], stack[i + 1])
          let pt2 = pt1.offset(stack[i + 2], stack[i + 3])
          let pt3 = pt2.offset(stack[i + 4], stack[i + 5])
          state.curve_to(pt1, pt2, pt3)
          i = i + 6
        }
        stack.clear()
        pos = pos + 1
      }
      10 => {
        if stack.is_empty() {
          return Err(InvalidFormat)
        }
        let subr_index = stack[stack.length() - 1].round().to_int()
        stack.truncate(stack.length() - 1)
        let local_subrs_value = match local_subrs {
          None => return Err(InvalidFormat)
          Some(value) => value
        }
        let bias = subr_bias(local_subrs_value.count)
        let idx = subr_index + bias
        let bytes = match local_subrs_value.item_bytes(idx) {
          Err(err) => return Err(err)
          Ok(value) => value
        }
        match interpret_charstring(
          bytes[:],
          state,
          stack,
          global_subrs,
          local_subrs,
          depth + 1,
          true,
          seac,
          in_seac,
          seac_report~,
        ) {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        pos = pos + 1
      }
      11 => {
        if allow_return {
          return Ok(())
        }
        return Err(InvalidFormat)
      }
      12 => {
        if pos + 1 >= data.length() {
          return Err(UnexpectedEof)
        }
        let op = data[pos + 1].to_int()
        let err = match op {
          0 => Ok(())
          34 => handle_hflex(state, stack)
          35 => handle_flex(state, stack)
          36 => handle_hflex1(state, stack)
          37 => handle_flex1(state, stack)
          _ => Err(InvalidFormat)
        }
        match err {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        stack.clear()
        pos = pos + 2
      }
      14 => {
        let offset = width_offset(state, stack)
        let count = stack.length() - offset
        if count >= 4 {
          let seac_context = match seac {
            None => return Err(InvalidFormat)
            Some(value) => value
          }
          if in_seac {
            return Err(InvalidFormat)
          }
          let base_index = offset + count - 4
          let adx = stack[base_index]
          let ady = stack[base_index + 1]
          let bchar = stack[base_index + 2].round().to_int()
          let achar = stack[base_index + 3].round().to_int()
          let base_sid = match cff1_standard_sid(bchar) {
            None => return Err(InvalidFormat)
            Some(value) => value
          }
          let accent_sid = match cff1_standard_sid(achar) {
            None => return Err(InvalidFormat)
            Some(value) => value
          }
          let base_gid = match seac_context.sid_to_gid(base_sid) {
            None => return Err(InvalidFormat)
            Some(value) => value
          }
          let accent_gid = match seac_context.sid_to_gid(accent_sid) {
            None => return Err(InvalidFormat)
            Some(value) => value
          }
          seac_report(base_gid, accent_gid)
          let base_bytes = match seac_context.charstrings.item_bytes(base_gid) {
            Err(err) => return Err(err)
            Ok(value) => value
          }
          let accent_bytes = match seac_context.charstrings.item_bytes(accent_gid) {
            Err(err) => return Err(err)
            Ok(value) => value
          }
          let base_state = CffCharstringState::new()
          let base_stack : Array[Double] = []
          match interpret_charstring(
            base_bytes[:],
            base_state,
            base_stack,
            global_subrs,
            local_subrs,
            depth + 1,
            false,
            seac,
            true,
            seac_report~,
          ) {
            Err(err) => return Err(err)
            Ok(_) => ()
          }
          let accent_state = CffCharstringState::new()
          let accent_stack : Array[Double] = []
          match interpret_charstring(
            accent_bytes[:],
            accent_state,
            accent_stack,
            global_subrs,
            local_subrs,
            depth + 1,
            false,
            seac,
            true,
            seac_report~,
          ) {
            Err(err) => return Err(err)
            Ok(_) => ()
          }
          match (base_state.bounds.result(), accent_state.bounds.result()) {
            (Some(base_bounds), Some(accent_bounds)) => {
              state.bounds.merge(base_bounds)
              state.bounds.merge(offset_bounds(accent_bounds, adx, ady))
            }
            _ => return Err(InvalidFormat)
          }
        } else if count != 0 {
          return Err(InvalidFormat)
        }
        stack.clear()
        pos = pos + 1
        return Ok(())
      }
      21 => {
        let offset = width_offset(state, stack)
        if stack.length() - offset < 2 {
          return Err(InvalidFormat)
        }
        let dx = stack[offset]
        let dy = stack[offset + 1]
        let pt = state.current_point().offset(dx, dy)
        state.moveto(pt)
        stack.clear()
        pos = pos + 1
      }
      22 => {
        let offset = width_offset(state, stack)
        if stack.length() - offset < 1 {
          return Err(InvalidFormat)
        }
        let dx = stack[offset]
        let pt = state.current_point().offset(dx, 0.0)
        state.moveto(pt)
        stack.clear()
        pos = pos + 1
      }
      24 => {
        if stack.length() < 8 || ((stack.length() - 2) % 6) != 0 {
          return Err(InvalidFormat)
        }
        let mut i = 0
        let line_index = stack.length() - 2
        while i + 5 < line_index {
          let pt1 = state.current_point().offset(stack[i], stack[i + 1])
          let pt2 = pt1.offset(stack[i + 2], stack[i + 3])
          let pt3 = pt2.offset(stack[i + 4], stack[i + 5])
          state.curve_to(pt1, pt2, pt3)
          i = i + 6
        }
        let dx = stack[line_index]
        let dy = stack[line_index + 1]
        let pt = state.current_point().offset(dx, dy)
        state.line_to(pt)
        stack.clear()
        pos = pos + 1
      }
      25 => {
        if stack.length() < 8 || ((stack.length() - 6) % 2) != 0 {
          return Err(InvalidFormat)
        }
        let mut i = 0
        let curve_index = stack.length() - 6
        while i + 1 < curve_index {
        let pt = state.current_point().offset(stack[i], stack[i + 1])
          state.line_to(pt)
          i = i + 2
        }
        let pt1 = state.current_point().offset(stack[curve_index], stack[curve_index + 1])
        let pt2 = pt1.offset(stack[curve_index + 2], stack[curve_index + 3])
        let pt3 = pt2.offset(stack[curve_index + 4], stack[curve_index + 5])
        state.curve_to(pt1, pt2, pt3)
        stack.clear()
        pos = pos + 1
      }
      26 => {
        let err = handle_vvcurveto(state, stack)
        match err {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        stack.clear()
        pos = pos + 1
      }
      27 => {
        let err = handle_hhcurveto(state, stack)
        match err {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        stack.clear()
        pos = pos + 1
      }
      29 => {
        if stack.is_empty() {
          return Err(InvalidFormat)
        }
        let subr_index = stack[stack.length() - 1].round().to_int()
        stack.truncate(stack.length() - 1)
        let global = match global_subrs {
          None => return Err(InvalidFormat)
          Some(value) => value
        }
        let bias = subr_bias(global.count)
        let idx = subr_index + bias
        let bytes = match global.item_bytes(idx) {
          Err(err) => return Err(err)
          Ok(value) => value
        }
        match interpret_charstring(
          bytes[:],
          state,
          stack,
          global_subrs,
          local_subrs,
          depth + 1,
          true,
          seac,
          in_seac,
          seac_report~,
        ) {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        pos = pos + 1
      }
      30 => {
        let err = handle_vhcurveto(state, stack)
        match err {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        stack.clear()
        pos = pos + 1
      }
      31 => {
        let err = handle_hvcurveto(state, stack)
        match err {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        stack.clear()
        pos = pos + 1
      }
      _ => return Err(InvalidFormat)
    }
  }
  Ok(())
}

fn interpret_charstring_cff2(
  data : BytesView,
  state : CffCharstringState,
  stack : Array[Double],
  blend_state : Cff2BlendState,
  global_subrs : CffIndex?,
  local_subrs : CffIndex?,
  depth : Int,
  allow_return : Bool,
) -> Result[Unit, CffError] {
  if depth > 16 {
    return Err(InvalidFormat)
  }
  let mut pos = 0
  while pos < data.length() {
    let b0 = data[pos].to_int()
    if b0 >= 32 || b0 == 28 || b0 == 255 {
      let parsed = parse_charstring_number(data, pos)
      match parsed {
        Err(err) => return Err(err)
        Ok((value, next_pos)) => {
          stack.push(value)
          pos = next_pos
          continue
        }
      }
    }
    match b0 {
      1 | 3 | 18 | 23 => {
        if (stack.length() % 2) != 0 {
          return Err(InvalidFormat)
        }
        state.stem_count = state.stem_count + (stack.length() / 2)
        stack.clear()
        pos = pos + 1
      }
      19 | 20 => {
        if (stack.length() % 2) != 0 {
          return Err(InvalidFormat)
        }
        state.stem_count = state.stem_count + (stack.length() / 2)
        stack.clear()
        pos = pos + 1
        let mask_bytes = (state.stem_count + 7) / 8
        if pos + mask_bytes > data.length() {
          return Err(UnexpectedEof)
        }
        pos = pos + mask_bytes
      }
      4 => {
        if stack.length() < 1 {
          return Err(InvalidFormat)
        }
        let dy = stack[0]
        let pt = state.current_point().offset(0.0, dy)
        state.moveto(pt)
        stack.clear()
        pos = pos + 1
      }
      5 => {
        if (stack.length() % 2) != 0 {
          return Err(InvalidFormat)
        }
        let mut i = 0
        while i + 1 < stack.length() {
          let dx = stack[i]
          let dy = stack[i + 1]
          let pt = state.current_point().offset(dx, dy)
          state.line_to(pt)
          i = i + 2
        }
        stack.clear()
        pos = pos + 1
      }
      6 | 7 => {
        let mut horizontal = b0 == 6
        for value in stack {
          let pt =
            if horizontal {
              state.current_point().offset(value, 0.0)
            } else {
              state.current_point().offset(0.0, value)
            }
          state.line_to(pt)
          horizontal = !horizontal
        }
        stack.clear()
        pos = pos + 1
      }
      8 => {
        if (stack.length() % 6) != 0 {
          return Err(InvalidFormat)
        }
        let mut i = 0
        while i + 5 < stack.length() {
          let pt1 = state.current_point().offset(stack[i], stack[i + 1])
          let pt2 = pt1.offset(stack[i + 2], stack[i + 3])
          let pt3 = pt2.offset(stack[i + 4], stack[i + 5])
          state.curve_to(pt1, pt2, pt3)
          i = i + 6
        }
        stack.clear()
        pos = pos + 1
      }
      10 => {
        if stack.is_empty() {
          return Err(InvalidFormat)
        }
        let subr_index = stack[stack.length() - 1].round().to_int()
        stack.truncate(stack.length() - 1)
        let local_subrs_value = match local_subrs {
          None => return Err(InvalidFormat)
          Some(value) => value
        }
        let bias = subr_bias(local_subrs_value.count)
        let idx = subr_index + bias
        let bytes = match local_subrs_value.item_bytes(idx) {
          Err(err) => return Err(err)
          Ok(value) => value
        }
        match interpret_charstring_cff2(
          bytes[:],
          state,
          stack,
          blend_state,
          global_subrs,
          local_subrs,
          depth + 1,
          true,
        ) {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        pos = pos + 1
      }
      11 => {
        if allow_return {
          return Ok(())
        }
        return Err(InvalidFormat)
      }
      12 => {
        if pos + 1 >= data.length() {
          return Err(UnexpectedEof)
        }
        let op = data[pos + 1].to_int()
        let err = match op {
          34 => handle_hflex(state, stack)
          35 => handle_flex(state, stack)
          36 => handle_hflex1(state, stack)
          37 => handle_flex1(state, stack)
          _ => Err(InvalidFormat)
        }
        match err {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        stack.clear()
        pos = pos + 2
      }
      14 => {
        stack.clear()
        pos = pos + 1
        return Ok(())
      }
      15 => {
        if stack.is_empty() {
          return Err(InvalidFormat)
        }
        if blend_state.seen_vsindex || blend_state.seen_blend {
          return Err(InvalidFormat)
        }
        let ivs = stack[stack.length() - 1].round().to_int()
        ignore(stack.pop())
        if ivs < 0 {
          return Err(InvalidFormat)
        }
        blend_state.ivs = ivs
        blend_state.seen_vsindex = true
        stack.clear()
        pos = pos + 1
      }
      16 => {
        if stack.is_empty() {
          return Err(InvalidFormat)
        }
        let n = stack[stack.length() - 1].round().to_int()
        ignore(stack.pop())
        if n < 0 {
          return Err(InvalidFormat)
        }
        let k = match blend_state.region_count() {
          Err(err) => return Err(err)
          Ok(value) => value
        }
        let start = stack.length() - (k + 1) * n
        if start < 0 {
          return Err(InvalidFormat)
        }
        let scalars = match blend_state.region_scalars() {
          Err(err) => return Err(err)
          Ok(value) => value
        }
        for i in 0.. ()
            Some(values) => {
              let base = start + n + (i * k)
              for j in 0.. 0 {
          ignore(stack.pop())
          to_pop = to_pop - 1
        }
        blend_state.seen_blend = true
        pos = pos + 1
      }
      21 => {
        if stack.length() < 2 {
          return Err(InvalidFormat)
        }
        let dx = stack[0]
        let dy = stack[1]
        let pt = state.current_point().offset(dx, dy)
        state.moveto(pt)
        stack.clear()
        pos = pos + 1
      }
      22 => {
        if stack.length() < 1 {
          return Err(InvalidFormat)
        }
        let dx = stack[0]
        let pt = state.current_point().offset(dx, 0.0)
        state.moveto(pt)
        stack.clear()
        pos = pos + 1
      }
      24 => {
        if stack.length() < 8 || ((stack.length() - 2) % 6) != 0 {
          return Err(InvalidFormat)
        }
        let mut i = 0
        let line_index = stack.length() - 2
        while i + 5 < line_index {
          let pt1 = state.current_point().offset(stack[i], stack[i + 1])
          let pt2 = pt1.offset(stack[i + 2], stack[i + 3])
          let pt3 = pt2.offset(stack[i + 4], stack[i + 5])
          state.curve_to(pt1, pt2, pt3)
          i = i + 6
        }
        let dx = stack[line_index]
        let dy = stack[line_index + 1]
        let pt = state.current_point().offset(dx, dy)
        state.line_to(pt)
        stack.clear()
        pos = pos + 1
      }
      25 => {
        if stack.length() < 8 || ((stack.length() - 6) % 2) != 0 {
          return Err(InvalidFormat)
        }
        let mut i = 0
        let curve_index = stack.length() - 6
        while i + 1 < curve_index {
          let pt = state.current_point().offset(stack[i], stack[i + 1])
          state.line_to(pt)
          i = i + 2
        }
        let pt1 = state.current_point().offset(stack[curve_index], stack[curve_index + 1])
        let pt2 = pt1.offset(stack[curve_index + 2], stack[curve_index + 3])
        let pt3 = pt2.offset(stack[curve_index + 4], stack[curve_index + 5])
        state.curve_to(pt1, pt2, pt3)
        stack.clear()
        pos = pos + 1
      }
      26 => {
        let err = handle_vvcurveto(state, stack)
        match err {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        stack.clear()
        pos = pos + 1
      }
      27 => {
        let err = handle_hhcurveto(state, stack)
        match err {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        stack.clear()
        pos = pos + 1
      }
      29 => {
        if stack.is_empty() {
          return Err(InvalidFormat)
        }
        let subr_index = stack[stack.length() - 1].round().to_int()
        stack.truncate(stack.length() - 1)
        let global = match global_subrs {
          None => return Err(InvalidFormat)
          Some(value) => value
        }
        let bias = subr_bias(global.count)
        let idx = subr_index + bias
        let bytes = match global.item_bytes(idx) {
          Err(err) => return Err(err)
          Ok(value) => value
        }
        match interpret_charstring_cff2(
          bytes[:],
          state,
          stack,
          blend_state,
          global_subrs,
          local_subrs,
          depth + 1,
          true,
        ) {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        pos = pos + 1
      }
      30 => {
        let err = handle_vhcurveto(state, stack)
        match err {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        stack.clear()
        pos = pos + 1
      }
      31 => {
        let err = handle_hvcurveto(state, stack)
        match err {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
        stack.clear()
        pos = pos + 1
      }
      _ => return Err(InvalidFormat)
    }
  }
  Ok(())
}

fn offset_bounds(bounds : CffBounds, dx : Double, dy : Double) -> CffBounds {
  CffBounds::{
    x_min: bounds.x_min + dx,
    y_min: bounds.y_min + dy,
    x_max: bounds.x_max + dx,
    y_max: bounds.y_max + dy,
  }
}

fn width_offset(state : CffCharstringState, stack : Array[Double]) -> Int {
  if state.has_width {
    return 0
  }
  if (stack.length() % 2) != 0 {
    state.has_width = true
    return 1
  }
  0
}

fn parse_charstring_number(
  data : BytesView,
  pos : Int,
) -> Result[(Double, Int), CffError] {
  if pos < 0 || pos >= data.length() {
    return Err(UnexpectedEof)
  }
  let b0 = data[pos].to_int()
  if b0 >= 32 && b0 <= 246 {
    return Ok(((b0 - 139).to_double(), pos + 1))
  }
  if b0 >= 247 && b0 <= 250 {
    if pos + 1 >= data.length() {
      return Err(UnexpectedEof)
    }
    let b1 = data[pos + 1].to_int()
    let value = (b0 - 247) * 256 + b1 + 108
    return Ok((value.to_double(), pos + 2))
  }
  if b0 >= 251 && b0 <= 254 {
    if pos + 1 >= data.length() {
      return Err(UnexpectedEof)
    }
    let b1 = data[pos + 1].to_int()
    let value = -(b0 - 251) * 256 - b1 - 108
    return Ok((value.to_double(), pos + 2))
  }
  if b0 == 28 {
    if pos + 2 >= data.length() {
      return Err(UnexpectedEof)
    }
    let hi = data[pos + 1].to_int()
    let lo = data[pos + 2].to_int()
    let raw = (hi << 8) | lo
    let value = if raw >= 0x8000 { raw - 0x10000 } else { raw }
    return Ok((value.to_double(), pos + 3))
  }
  if b0 == 255 {
    if pos + 4 >= data.length() {
      return Err(UnexpectedEof)
    }
    let b1 = data[pos + 1].to_int64()
    let b2 = data[pos + 2].to_int64()
    let b3 = data[pos + 3].to_int64()
    let b4 = data[pos + 4].to_int64()
    let raw : Int64 = (b1 << 24) | (b2 << 16) | (b3 << 8) | b4
    let signed : Int64 = if raw >= 2147483648 { raw - 4294967296 } else { raw }
    return Ok((signed.to_double() / 65536.0, pos + 5))
  }
  Err(InvalidFormat)
}

fn handle_vvcurveto(state : CffCharstringState, stack : Array[Double]) -> Result[Unit, CffError] {
  let mut i = 0
  let count = stack.length()
  let mut pt1 = state.current_point()
  if (count & 1) != 0 {
    pt1 = pt1.offset_x(stack[i])
    i = i + 1
  }
  while i + 3 < count {
    pt1 = pt1.offset_y(stack[i])
    let pt2 = pt1.offset(stack[i + 1], stack[i + 2])
    let pt3 = pt2.offset_y(stack[i + 3])
    state.curve_to(pt1, pt2, pt3)
    pt1 = state.current_point()
    i = i + 4
  }
  Ok(())
}

fn handle_hhcurveto(state : CffCharstringState, stack : Array[Double]) -> Result[Unit, CffError] {
  let mut i = 0
  let count = stack.length()
  let mut pt1 = state.current_point()
  if (count & 1) != 0 {
    pt1 = pt1.offset_y(stack[i])
    i = i + 1
  }
  while i + 3 < count {
    pt1 = pt1.offset_x(stack[i])
    let pt2 = pt1.offset(stack[i + 1], stack[i + 2])
    let pt3 = pt2.offset_x(stack[i + 3])
    state.curve_to(pt1, pt2, pt3)
    pt1 = state.current_point()
    i = i + 4
  }
  Ok(())
}

fn handle_vhcurveto(state : CffCharstringState, stack : Array[Double]) -> Result[Unit, CffError] {
  let count = stack.length()
  let mut i = 0
  if (count % 8) >= 4 {
    let mut pt1 = state.current_point().offset_y(stack[i])
    let mut pt2 = pt1.offset(stack[i + 1], stack[i + 2])
    let mut pt3 = pt2.offset_x(stack[i + 3])
    i = i + 4
    while i + 7 < count {
      state.curve_to(pt1, pt2, pt3)
      pt1 = state.current_point().offset_x(stack[i])
      pt2 = pt1.offset(stack[i + 1], stack[i + 2])
      pt3 = pt2.offset_y(stack[i + 3])
      state.curve_to(pt1, pt2, pt3)
      pt1 = pt3.offset_y(stack[i + 4])
      pt2 = pt1.offset(stack[i + 5], stack[i + 6])
      pt3 = pt2.offset_x(stack[i + 7])
      i = i + 8
    }
    if i < count {
      pt3 = pt3.offset_y(stack[i])
    }
    state.curve_to(pt1, pt2, pt3)
    return Ok(())
  }
  while i + 7 < count {
    let mut pt1 = state.current_point().offset_y(stack[i])
    let mut pt2 = pt1.offset(stack[i + 1], stack[i + 2])
    let mut pt3 = pt2.offset_x(stack[i + 3])
    state.curve_to(pt1, pt2, pt3)
    pt1 = pt3.offset_x(stack[i + 4])
    pt2 = pt1.offset(stack[i + 5], stack[i + 6])
    pt3 = pt2.offset_y(stack[i + 7])
    if (count - i < 16) && ((count & 1) != 0) && (i + 8 < count) {
      pt3 = pt3.offset_x(stack[i + 8])
    }
    state.curve_to(pt1, pt2, pt3)
    i = i + 8
  }
  Ok(())
}

fn handle_hvcurveto(state : CffCharstringState, stack : Array[Double]) -> Result[Unit, CffError] {
  let count = stack.length()
  let mut i = 0
  if (count % 8) >= 4 {
    let mut pt1 = state.current_point().offset_x(stack[i])
    let mut pt2 = pt1.offset(stack[i + 1], stack[i + 2])
    let mut pt3 = pt2.offset_y(stack[i + 3])
    i = i + 4
    while i + 7 < count {
      state.curve_to(pt1, pt2, pt3)
      pt1 = state.current_point().offset_y(stack[i])
      pt2 = pt1.offset(stack[i + 1], stack[i + 2])
      pt3 = pt2.offset_x(stack[i + 3])
      state.curve_to(pt1, pt2, pt3)
      pt1 = pt3.offset_x(stack[i + 4])
      pt2 = pt1.offset(stack[i + 5], stack[i + 6])
      pt3 = pt2.offset_y(stack[i + 7])
      i = i + 8
    }
    if i < count {
      pt3 = pt3.offset_x(stack[i])
    }
    state.curve_to(pt1, pt2, pt3)
    return Ok(())
  }
  while i + 7 < count {
    let mut pt1 = state.current_point().offset_x(stack[i])
    let mut pt2 = pt1.offset(stack[i + 1], stack[i + 2])
    let mut pt3 = pt2.offset_y(stack[i + 3])
    state.curve_to(pt1, pt2, pt3)
    pt1 = pt3.offset_y(stack[i + 4])
    pt2 = pt1.offset(stack[i + 5], stack[i + 6])
    pt3 = pt2.offset_x(stack[i + 7])
    if (count - i < 16) && ((count & 1) != 0) && (i + 8 < count) {
      pt3 = pt3.offset_y(stack[i + 8])
    }
    state.curve_to(pt1, pt2, pt3)
    i = i + 8
  }
  Ok(())
}

fn handle_hflex(state : CffCharstringState, stack : Array[Double]) -> Result[Unit, CffError] {
  if stack.length() != 7 {
    return Err(InvalidFormat)
  }
  let pt1 = state.current_point().offset_x(stack[0])
  let pt2 = pt1.offset(stack[1], stack[2])
  let pt3 = pt2.offset_x(stack[3])
  let pt4 = pt3.offset_x(stack[4])
  let pt5 = pt4.offset_x(stack[5])
  let pt5 = CffPoint::{ x: pt5.x, y: pt1.y }
  let pt6 = pt5.offset_x(stack[6])
  state.curve_to(pt1, pt2, pt3)
  state.curve_to(pt4, pt5, pt6)
  Ok(())
}

fn handle_flex(state : CffCharstringState, stack : Array[Double]) -> Result[Unit, CffError] {
  if stack.length() != 13 {
    return Err(InvalidFormat)
  }
  let pt1 = state.current_point().offset(stack[0], stack[1])
  let pt2 = pt1.offset(stack[2], stack[3])
  let pt3 = pt2.offset(stack[4], stack[5])
  let pt4 = pt3.offset(stack[6], stack[7])
  let pt5 = pt4.offset(stack[8], stack[9])
  let pt6 = pt5.offset(stack[10], stack[11])
  state.curve_to(pt1, pt2, pt3)
  state.curve_to(pt4, pt5, pt6)
  Ok(())
}

fn handle_hflex1(state : CffCharstringState, stack : Array[Double]) -> Result[Unit, CffError] {
  if stack.length() != 9 {
    return Err(InvalidFormat)
  }
  let pt1 = state.current_point().offset(stack[0], stack[1])
  let pt2 = pt1.offset(stack[2], stack[3])
  let pt3 = pt2.offset_x(stack[4])
  let pt4 = pt3.offset_x(stack[5])
  let pt5 = pt4.offset(stack[6], stack[7])
  let mut pt6 = pt5.offset_x(stack[8])
  pt6 = CffPoint::{ x: pt6.x, y: state.current_point().y }
  state.curve_to(pt1, pt2, pt3)
  state.curve_to(pt4, pt5, pt6)
  Ok(())
}

fn handle_flex1(state : CffCharstringState, stack : Array[Double]) -> Result[Unit, CffError] {
  if stack.length() != 11 {
    return Err(InvalidFormat)
  }
  let mut dx = 0.0
  let mut dy = 0.0
  let mut i = 0
  while i < 10 {
    dx = dx + stack[i]
    dy = dy + stack[i + 1]
    i = i + 2
  }
  let pt1 = state.current_point().offset(stack[0], stack[1])
  let pt2 = pt1.offset(stack[2], stack[3])
  let pt3 = pt2.offset(stack[4], stack[5])
  let pt4 = pt3.offset(stack[6], stack[7])
  let pt5 = pt4.offset(stack[8], stack[9])
  let mut pt6 = pt5
  if dx.abs() > dy.abs() {
    pt6 = pt6.offset_x(stack[10])
    pt6 = CffPoint::{ x: pt6.x, y: state.current_point().y }
  } else {
    pt6 = CffPoint::{ x: state.current_point().x, y: pt6.y }
    pt6 = pt6.offset_y(stack[10])
  }
  state.curve_to(pt1, pt2, pt3)
  state.curve_to(pt4, pt5, pt6)
  Ok(())
}