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

///|
/// Minimal outline extraction for the autohinter.
///
/// This mirrors upstream's `outline/autohint/outline.rs` "unscaled sink" stage,
/// but uses `PathElement` streams (generated by `OutlineGlyph::path`) as the
/// source representation.

///|
priv enum AutoHintDirection {
  None_
  Right
  Left
  Up
  Down
}

///|
impl Eq for AutoHintDirection with fn equal(a, b) {
  match (a, b) {
    (None_, None_) => true
    (Right, Right) => true
    (Left, Left) => true
    (Up, Up) => true
    (Down, Down) => true
    _ => false
  }
}

///|
fn AutoHintDirection::to_int(self : AutoHintDirection) -> Int {
  match self {
    None_ => 4
    Right => 1
    Left => -1
    Up => 2
    Down => -2
  }
}

///|
fn AutoHintDirection::from_delta(dx : Int, dy : Int) -> AutoHintDirection {
  let (dir, long_arm, short_arm) = if dy >= dx {
    if dy >= -dx {
      (AutoHintDirection::Up, dy, dx)
    } else {
      (Left, -dx, dy)
    }
  } else if dy >= -dx {
    (Right, dx, dy)
  } else {
    (Down, -dy, dx)
  }
  if long_arm <= 14 * short_arm.abs() {
    None_
  } else {
    dir
  }
}

///|
fn AutoHintDirection::is_opposite(
  self : AutoHintDirection,
  other : AutoHintDirection,
) -> Bool {
  self.to_int() + other.to_int() == 0
}

///|
fn AutoHintDirection::is_same_axis(
  self : AutoHintDirection,
  other : AutoHintDirection,
) -> Bool {
  self.to_int().abs() == other.to_int().abs()
}

///|
fn AutoHintDirection::normalize(self : AutoHintDirection) -> AutoHintDirection {
  match self {
    Left => Right
    Down => Up
    _ => self
  }
}

///|
priv enum AutoHintOrientation {
  Clockwise
  CounterClockwise
}

///|
priv struct AutoHintPointFlags {
  mut bits : Int
}

///|
fn AutoHintPointFlags::default() -> AutoHintPointFlags {
  { bits: 0 }
}

///|
priv enum AutoHintPointMarker {
  Near
  WeakInterpolation
  TouchedX
  TouchedY
}

///|
fn AutoHintPointMarker::bit(self : AutoHintPointMarker) -> Int {
  match self {
    Near => 1 << 1
    WeakInterpolation => 1 << 2
    TouchedX => 1 << 3
    TouchedY => 1 << 4
  }
}

///|
fn AutoHintPointFlags::with_on_curve(on_curve : Bool) -> AutoHintPointFlags {
  { bits: if on_curve { 1 } else { 0 } }
}

///|
fn AutoHintPointFlags::is_on_curve(self : AutoHintPointFlags) -> Bool {
  (self.bits & 1) != 0
}

///|
fn AutoHintPointFlags::set_marker(
  self : AutoHintPointFlags,
  marker : AutoHintPointMarker,
) -> Unit {
  self.bits = self.bits | marker.bit()
}

///|
fn AutoHintPointFlags::has_marker(
  self : AutoHintPointFlags,
  marker : AutoHintPointMarker,
) -> Bool {
  (self.bits & marker.bit()) != 0
}

///|
fn AutoHintPointFlags::has_marker_mask(
  self : AutoHintPointFlags,
  mask : Int,
) -> Bool {
  (self.bits & mask) != 0
}

///|
priv struct AutoHintPoint {
  mut flags : AutoHintPointFlags
  mut fx : Int
  mut fy : Int
  mut ox : Int
  mut oy : Int
  mut x : Int
  mut y : Int
  mut in_dir : AutoHintDirection
  mut out_dir : AutoHintDirection
  mut u : Int
  mut v : Int
  mut next_ix : Int
  mut prev_ix : Int
}

///|
fn AutoHintPoint::default() -> AutoHintPoint {
  {
    flags: AutoHintPointFlags::default(),
    fx: 0,
    fy: 0,
    ox: 0,
    oy: 0,
    x: 0,
    y: 0,
    in_dir: None_,
    out_dir: None_,
    u: 0,
    v: 0,
    next_ix: 0,
    prev_ix: 0,
  }
}

///|
fn AutoHintPoint::is_on_curve(self : AutoHintPoint) -> Bool {
  self.flags.is_on_curve()
}

///|
fn AutoHintPoint::next(self : AutoHintPoint) -> Int {
  self.next_ix
}

///|
fn AutoHintPoint::prev(self : AutoHintPoint) -> Int {
  self.prev_ix
}

///|
priv struct AutoHintContour {
  first_ix : Int
  last_ix : Int
}

///|
fn AutoHintContour::first(self : AutoHintContour) -> Int {
  self.first_ix
}

///|
fn AutoHintContour::next(self : AutoHintContour, index : Int) -> Int {
  if index >= self.last_ix {
    self.first_ix
  } else {
    index + 1
  }
}

///|
fn AutoHintContour::prev(self : AutoHintContour, index : Int) -> Int {
  if index <= self.first_ix {
    self.last_ix
  } else {
    index - 1
  }
}

///|
fn AutoHintContour::length(self : AutoHintContour) -> Int {
  if self.last_ix < self.first_ix {
    0
  } else {
    self.last_ix - self.first_ix + 1
  }
}

///|
priv struct AutoHintOutline {
  mut units_per_em : Int
  mut orientation : AutoHintOrientation?
  points : Array[AutoHintPoint]
  contours : Array[AutoHintContour]
}

///|
fn autohint_round_font_units(v : Double) -> Int {
  v.round().to_int()
}

///|
fn AutoHintOutline::from_path(path : Array[PathElement]) -> AutoHintOutline {
  let points : Array[AutoHintPoint] = Array::new()
  let contours : Array[AutoHintContour] = Array::new()
  let mut in_contour = false
  let mut contour_start = 0
  for e in path.iter() {
    match e {
      MoveTo(x, y) => {
        if in_contour && points.length() > contour_start {
          contours.push({
            first_ix: contour_start,
            last_ix: points.length() - 1,
          })
        }
        contour_start = points.length()
        let px = autohint_round_font_units(x)
        let py = autohint_round_font_units(y)
        let p = AutoHintPoint::default()
        p.flags = AutoHintPointFlags::with_on_curve(true)
        p.fx = px
        p.fy = py
        p.ox = px
        p.oy = py
        p.x = px
        p.y = py
        points.push(p)
        in_contour = true
      }
      LineTo(x, y) =>
        if in_contour {
          let px = autohint_round_font_units(x)
          let py = autohint_round_font_units(y)
          let p = AutoHintPoint::default()
          p.flags = AutoHintPointFlags::with_on_curve(true)
          p.fx = px
          p.fy = py
          p.ox = px
          p.oy = py
          p.x = px
          p.y = py
          points.push(p)
        }
      QuadTo(cx0, cy0, x, y) =>
        if in_contour {
          let cpx = autohint_round_font_units(cx0)
          let cpy = autohint_round_font_units(cy0)
          let c = AutoHintPoint::default()
          c.flags = AutoHintPointFlags::with_on_curve(false)
          c.fx = cpx
          c.fy = cpy
          c.ox = cpx
          c.oy = cpy
          c.x = cpx
          c.y = cpy
          points.push(c)
          let px = autohint_round_font_units(x)
          let py = autohint_round_font_units(y)
          let p = AutoHintPoint::default()
          p.flags = AutoHintPointFlags::with_on_curve(true)
          p.fx = px
          p.fy = py
          p.ox = px
          p.oy = py
          p.x = px
          p.y = py
          points.push(p)
        }
      CurveTo(cx0, cy0, cx1, cy1, x, y) =>
        if in_contour {
          let c0x = autohint_round_font_units(cx0)
          let c0y = autohint_round_font_units(cy0)
          let c0 = AutoHintPoint::default()
          c0.flags = AutoHintPointFlags::with_on_curve(false)
          c0.fx = c0x
          c0.fy = c0y
          c0.ox = c0x
          c0.oy = c0y
          c0.x = c0x
          c0.y = c0y
          points.push(c0)
          let c1x = autohint_round_font_units(cx1)
          let c1y = autohint_round_font_units(cy1)
          let c1 = AutoHintPoint::default()
          c1.flags = AutoHintPointFlags::with_on_curve(false)
          c1.fx = c1x
          c1.fy = c1y
          c1.ox = c1x
          c1.oy = c1y
          c1.x = c1x
          c1.y = c1y
          points.push(c1)
          let px = autohint_round_font_units(x)
          let py = autohint_round_font_units(y)
          let p = AutoHintPoint::default()
          p.flags = AutoHintPointFlags::with_on_curve(true)
          p.fx = px
          p.fy = py
          p.ox = px
          p.oy = py
          p.x = px
          p.y = py
          points.push(p)
        }
      Close =>
        if in_contour {
          if points.length() > contour_start {
            contours.push({
              first_ix: contour_start,
              last_ix: points.length() - 1,
            })
          }
          in_contour = false
        }
    }
  }
  if in_contour && points.length() > contour_start {
    contours.push({ first_ix: contour_start, last_ix: points.length() - 1 })
  }
  let outline = AutoHintOutline::{
    units_per_em: 0,
    orientation: None,
    points,
    contours,
  }
  outline.link_points()
  outline.compute_directions(0)
  outline.compute_orientation()
  outline
}

///|
fn AutoHintOutline::is_empty(self : AutoHintOutline) -> Bool {
  self.contours.length() == 0
}

///|
fn AutoHintOutline::clear(self : AutoHintOutline) -> Unit {
  self.units_per_em = 0
  self.orientation = None
  self.points.clear()
  self.contours.clear()
}

///|
fn autohint_outline_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 autohint_outline_read_i16_be(view : BytesView, offset : Int) -> Int? {
  match autohint_outline_read_u16_be(view, offset) {
    None => None
    Some(u) => Some(if u >= 0x8000 { u - 0x10000 } else { u })
  }
}

///|
fn autohint_outline_read_i8(view : BytesView, offset : Int) -> Int? {
  if offset < 0 || offset >= view.length() {
    None
  } else {
    let u = view.at(offset).to_int()
    Some(if u >= 0x80 { u - 0x100 } else { u })
  }
}

///|
fn autohint_outline_read_u32_be(view : BytesView, offset : Int) -> UInt? {
  if offset < 0 || offset + 4 > view.length() {
    None
  } else {
    let b0 = view.at(offset).to_uint()
    let b1 = view.at(offset + 1).to_uint()
    let b2 = view.at(offset + 2).to_uint()
    let b3 = view.at(offset + 3).to_uint()
    Some((b0 << 24) | (b1 << 16) | (b2 << 8) | b3)
  }
}

///|
const AUTOHINT_OUTLINE_TAG_HEAD : UInt = 0x68656164 // "head"

///|
const AUTOHINT_OUTLINE_TAG_LOCA : UInt = 0x6C6F6361 // "loca"

///|
const AUTOHINT_OUTLINE_TAG_GLYF : UInt = 0x676C7966 // "glyf"

///|
const AUTOHINT_OUTLINE_TAG_MAXP : UInt = 0x6D617870 // "maxp"

///|
fn autohint_outline_units_per_em(font : @moon_skrifa.FontRef) -> Int? {
  match font.table(AUTOHINT_OUTLINE_TAG_HEAD) {
    None => None
    Some(head) => autohint_outline_read_u16_be(head, 18)
  }
}

///|
fn autohint_outline_num_glyphs(font : @moon_skrifa.FontRef) -> Int? {
  match font.table(AUTOHINT_OUTLINE_TAG_MAXP) {
    None => None
    Some(maxp) => autohint_outline_read_u16_be(maxp, 4)
  }
}

///|
fn autohint_outline_index_to_loc_format(font : @moon_skrifa.FontRef) -> Int? {
  match font.table(AUTOHINT_OUTLINE_TAG_HEAD) {
    None => None
    Some(head) => autohint_outline_read_i16_be(head, 50)
  }
}

///|
fn autohint_outline_glyf_glyph_slice(
  font : @moon_skrifa.FontRef,
  gid : @moon_skrifa.GlyphId,
) -> BytesView? {
  let glyf = match font.table(AUTOHINT_OUTLINE_TAG_GLYF) {
    None => return None
    Some(v) => v
  }
  let loca = match font.table(AUTOHINT_OUTLINE_TAG_LOCA) {
    None => return None
    Some(v) => v
  }
  let num_glyphs = autohint_outline_num_glyphs(font).unwrap_or(-1)
  let idx = gid.to_uint64().to_int()
  if idx < 0 || idx >= num_glyphs {
    return None
  }
  let fmt = autohint_outline_index_to_loc_format(font).unwrap_or(-1)
  let (start, end) = if fmt == 0 {
    let need = (num_glyphs + 1) * 2
    if loca.length() < need {
      return None
    }
    let o0 = autohint_outline_read_u16_be(loca, idx * 2).unwrap_or(-1)
    let o1 = autohint_outline_read_u16_be(loca, (idx + 1) * 2).unwrap_or(-1)
    (o0 * 2, o1 * 2)
  } else if fmt == 1 {
    let need = (num_glyphs + 1) * 4
    if loca.length() < need {
      return None
    }
    let o0 = autohint_outline_read_u32_be(loca, idx * 4)
      .unwrap_or(0)
      .to_uint64()
      .to_int()
    let o1 = autohint_outline_read_u32_be(loca, (idx + 1) * 4)
      .unwrap_or(0)
      .to_uint64()
      .to_int()
    (o0, o1)
  } else {
    return None
  }
  if start < 0 || end < start || end > glyf.length() {
    return None
  }
  Some(glyf[start:end])
}

///|
priv struct AutoHintOutlineData {
  points : Array[AutoHintPoint]
  contours : Array[AutoHintContour]
}

///|
fn AutoHintOutlineData::AutoHintOutlineData() -> AutoHintOutlineData {
  { points: Array::new(), contours: Array::new() }
}

///|
fn autohint_outline_data_append(
  dst : AutoHintOutlineData,
  src : AutoHintOutlineData,
) -> Unit {
  let base = dst.points.length()
  for p in src.points.iter() {
    dst.points.push(p)
  }
  for c in src.contours.iter() {
    dst.contours.push({ first_ix: c.first_ix + base, last_ix: c.last_ix + base })
  }
}

///|
fn autohint_outline_mul_f2dot14(value : Int, coeff : Int) -> Int {
  let prod = value.to_int64() * coeff.to_int64()
  let rounded = if prod >= 0 { prod + 0x2000 } else { prod - 0x2000 }
  (rounded >> 14).to_int()
}

///|
fn autohint_outline_apply_transform(
  points : Array[AutoHintPoint],
  a : Int,
  b : Int,
  c : Int,
  d : Int,
  dx : Int,
  dy : Int,
) -> Unit {
  for i in 0.. Result[AutoHintOutlineData, DrawError] {
  let data = AutoHintOutlineData()
  let mut off = 10
  let end_pts : Array[Int] = Array::new()
  for _ in 0.. return Err(Read)
      Some(u) => u
    }
    end_pts.push(v)
    off = off + 2
  }
  let instruction_len = autohint_outline_read_u16_be(glyph, off).unwrap_or(-1)
  off = off + 2
  if instruction_len < 0 || off + instruction_len > glyph.length() {
    return Err(Read)
  }
  off = off + instruction_len
  let last_end = end_pts.at(end_pts.length() - 1)
  let num_points = last_end + 1
  if num_points <= 0 {
    return Ok(data)
  }
  // Flags stream.
  let flags : Array[Int] = Array::new()
  while flags.length() < num_points {
    if off >= glyph.length() {
      return Err(Read)
    }
    let flag = glyph.at(off).to_int()
    off = off + 1
    flags.push(flag)
    if (flag & 0x08) != 0 {
      if off >= glyph.length() {
        return Err(Read)
      }
      let repeat = glyph.at(off).to_int()
      off = off + 1
      for _ in 0..= glyph.length() {
        return Err(Read)
      }
      let b = glyph.at(off).to_int()
      off = off + 1
      dxs.push(if same { b } else { -b })
    } else if same {
      dxs.push(0)
    } else {
      let d = match autohint_outline_read_i16_be(glyph, off) {
        None => return Err(Read)
        Some(v) => v
      }
      off = off + 2
      dxs.push(d)
    }
  }
  let dys : Array[Int] = Array::new()
  for i in 0..= glyph.length() {
        return Err(Read)
      }
      let b = glyph.at(off).to_int()
      off = off + 1
      dys.push(if same { b } else { -b })
    } else if same {
      dys.push(0)
    } else {
      let d = match autohint_outline_read_i16_be(glyph, off) {
        None => return Err(Read)
        Some(v) => v
      }
      off = off + 2
      dys.push(d)
    }
  }
  let mut x = 0
  let mut y = 0
  let mut contour_start = 0
  for i in 0..= data.points.length() {
      return Err(Read)
    }
    data.contours.push({ first_ix: contour_start, last_ix: end })
    contour_start = end + 1
  }
  Ok(data)
}

///|
const AUTOHINT_COMPOSITE_ARG_1_AND_2_ARE_WORDS : Int = 0x0001

///|
const AUTOHINT_COMPOSITE_ARGS_ARE_XY_VALUES : Int = 0x0002

///|
const AUTOHINT_COMPOSITE_WE_HAVE_A_SCALE : Int = 0x0008

///|
const AUTOHINT_COMPOSITE_MORE_COMPONENTS : Int = 0x0020

///|
const AUTOHINT_COMPOSITE_WE_HAVE_AN_XY_SCALE : Int = 0x0040

///|
const AUTOHINT_COMPOSITE_WE_HAVE_A_2X2 : Int = 0x0080

///|
const AUTOHINT_COMPOSITE_WE_HAVE_INSTRUCTIONS : Int = 0x0100

///|
fn autohint_outline_build_glyf_raw(
  font : @moon_skrifa.FontRef,
  gid : @moon_skrifa.GlyphId,
  depth : Int,
) -> Result[AutoHintOutlineData, DrawError] {
  if depth > 16 {
    return Err(Read)
  }
  let glyph = match autohint_outline_glyf_glyph_slice(font, gid) {
    None => return Err(GlyphNotFound(gid))
    Some(v) => v
  }
  if glyph.length() == 0 {
    return Ok(AutoHintOutlineData())
  }
  if glyph.length() < 10 {
    return Err(Read)
  }
  let number_of_contours = autohint_outline_read_i16_be(glyph, 0).unwrap_or(0)
  if number_of_contours == 0 {
    return Ok(AutoHintOutlineData())
  }
  if number_of_contours > 0 {
    return autohint_outline_decode_simple_glyf(glyph, number_of_contours)
  }
  // Composite glyph.
  let data = AutoHintOutlineData()
  let mut off = 10
  let mut last_flags = 0
  while true {
    let flags = autohint_outline_read_u16_be(glyph, off).unwrap_or(-1)
    let comp_gid_u16 = autohint_outline_read_u16_be(glyph, off + 2).unwrap_or(
      -1,
    )
    if flags < 0 || comp_gid_u16 < 0 {
      return Err(Read)
    }
    last_flags = flags
    off = off + 4
    let (arg1, arg2) = if (flags & AUTOHINT_COMPOSITE_ARG_1_AND_2_ARE_WORDS) !=
      0 {
      let a1 = autohint_outline_read_i16_be(glyph, off).unwrap_or(-1)
      let a2 = autohint_outline_read_i16_be(glyph, off + 2).unwrap_or(-1)
      off = off + 4
      (a1, a2)
    } else {
      let a1 = autohint_outline_read_i8(glyph, off).unwrap_or(0)
      let a2 = autohint_outline_read_i8(glyph, off + 1).unwrap_or(0)
      off = off + 2
      (a1, a2)
    }
    // Default transform is identity (F2Dot14).
    let mut a = 1 << 14
    let mut b = 0
    let mut c = 0
    let mut d = 1 << 14
    if (flags & AUTOHINT_COMPOSITE_WE_HAVE_A_SCALE) != 0 {
      let s = autohint_outline_read_i16_be(glyph, off).unwrap_or(0)
      off = off + 2
      a = s
      d = s
    } else if (flags & AUTOHINT_COMPOSITE_WE_HAVE_AN_XY_SCALE) != 0 {
      a = autohint_outline_read_i16_be(glyph, off).unwrap_or(0)
      d = autohint_outline_read_i16_be(glyph, off + 2).unwrap_or(0)
      off = off + 4
    } else if (flags & AUTOHINT_COMPOSITE_WE_HAVE_A_2X2) != 0 {
      a = autohint_outline_read_i16_be(glyph, off).unwrap_or(0)
      b = autohint_outline_read_i16_be(glyph, off + 2).unwrap_or(0)
      c = autohint_outline_read_i16_be(glyph, off + 4).unwrap_or(0)
      d = autohint_outline_read_i16_be(glyph, off + 6).unwrap_or(0)
      off = off + 8
    }
    let comp_gid = @moon_skrifa.GlyphId::GlyphId(comp_gid_u16.to_uint16())
    let comp = match
      autohint_outline_build_glyf_raw(font, comp_gid, depth + 1) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    autohint_outline_apply_transform(comp.points, a, b, c, d, 0, 0)
    let (dx, dy) = if (flags & AUTOHINT_COMPOSITE_ARGS_ARE_XY_VALUES) != 0 {
      (arg1, arg2)
    } else {
      if arg2 < 0 || arg2 >= data.points.length() {
        return Err(Read)
      }
      if arg1 < 0 || arg1 >= comp.points.length() {
        return Err(Read)
      }
      let p_parent = data.points.at(arg2)
      let p_comp = comp.points.at(arg1)
      (p_parent.x - p_comp.x, p_parent.y - p_comp.y)
    }
    if dx != 0 || dy != 0 {
      autohint_outline_apply_transform(
        comp.points,
        1 << 14,
        0,
        0,
        1 << 14,
        dx,
        dy,
      )
    }
    autohint_outline_data_append(data, comp)
    if (flags & AUTOHINT_COMPOSITE_MORE_COMPONENTS) == 0 {
      break
    }
  }
  if (last_flags & AUTOHINT_COMPOSITE_WE_HAVE_INSTRUCTIONS) != 0 {
    let instr_len = autohint_outline_read_u16_be(glyph, off).unwrap_or(-1)
    off = off + 2
    if instr_len < 0 || off + instr_len > glyph.length() {
      return Err(Read)
    }
  }
  Ok(data)
}

///|
fn AutoHintOutline::fill_glyf(
  self : AutoHintOutline,
  font : @moon_skrifa.FontRef,
  gid : @moon_skrifa.GlyphId,
) -> Result[Unit, DrawError] {
  self.clear()
  self.units_per_em = autohint_outline_units_per_em(font).unwrap_or(0)
  let near_limit = 20 * self.units_per_em / 2048
  let data = match autohint_outline_build_glyf_raw(font, gid, 0) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  for p in data.points.iter() {
    self.points.push(p)
  }
  for c in data.contours.iter() {
    self.contours.push(c)
  }
  self.link_points()
  self.mark_near_points(near_limit)
  self.compute_directions(near_limit)
  self.simplify_topology()
  self.check_remaining_weak_points()
  self.compute_orientation()
  Ok(())
}

///|
fn AutoHintOutline::_fill_unscaled(
  self : AutoHintOutline,
  font : @moon_skrifa.FontRef,
  gid : @moon_skrifa.GlyphId,
) -> Result[Unit, DrawError] {
  self.fill_glyf(font, gid)
}

///|
fn AutoHintOutline::link_points(self : AutoHintOutline) -> Unit {
  for contour in self.contours.iter() {
    let first = contour.first_ix
    let last = contour.last_ix
    if first < 0 || last < first || last >= self.points.length() {
      continue
    }
    for i in first..<(last + 1) {
      let p = self.points.at(i)
      p.prev_ix = if i == first { last } else { i - 1 }
      p.next_ix = if i == last { first } else { i + 1 }
      self.points.set(i, p)
    }
  }
}

///|
fn AutoHintOutline::compute_directions(
  self : AutoHintOutline,
  near_limit : Int,
) -> Unit {
  let near_limit2 = 2 * near_limit - 1
  for contour in self.contours.iter() {
    let mut first_ix = contour.first_ix
    let mut ix = first_ix
    let mut prev_ix = contour.prev(first_ix)
    let mut point = self.points.at(first_ix)
    // Walk backward to find the first non-near point.
    while prev_ix != first_ix {
      let prev = self.points.at(prev_ix)
      let out_x = point.fx - prev.fx
      let out_y = point.fy - prev.fy
      if out_x.abs() + out_y.abs() >= near_limit2 {
        break
      }
      point = prev
      ix = prev_ix
      prev_ix = contour.prev(prev_ix)
    }
    first_ix = ix
    let first = self.points.at(first_ix)
    first.u = first_ix
    first.v = first_ix
    self.points.set(first_ix, first)
    let mut next_ix = first_ix
    ix = first_ix
    let mut out_x = 0
    let mut out_y = 0
    // Loop over all points in the contour to compute in/out directions.
    while true {
      let point_ix = next_ix
      next_ix = contour.next(point_ix)
      let point0 = self.points.at(point_ix)
      let next0 = self.points.at(next_ix)
      out_x = out_x + (next0.fx - point0.fx)
      out_y = out_y + (next0.fy - point0.fy)
      if out_x.abs() + out_y.abs() < near_limit {
        let p = self.points.at(next_ix)
        p.flags.set_marker(WeakInterpolation)
        self.points.set(next_ix, p)
        if next_ix == first_ix {
          break
        }
        continue
      }
      let out_dir = AutoHintDirection::from_delta(out_x, out_y)
      let nextp = self.points.at(next_ix)
      nextp.in_dir = out_dir
      nextp.v = ix
      self.points.set(next_ix, nextp)
      let cur = self.points.at(ix)
      cur.u = next_ix
      cur.out_dir = out_dir
      self.points.set(ix, cur)
      // Adjust directions for all intermediate points.
      let mut inter_ix = contour.next(ix)
      while inter_ix != next_ix {
        let p = self.points.at(inter_ix)
        p.in_dir = out_dir
        p.out_dir = out_dir
        self.points.set(inter_ix, p)
        inter_ix = contour.next(inter_ix)
      }
      ix = next_ix
      let lastp = self.points.at(ix)
      lastp.u = first_ix
      self.points.set(ix, lastp)
      let firstp = self.points.at(first_ix)
      firstp.v = ix
      self.points.set(first_ix, firstp)
      out_x = 0
      out_y = 0
      if next_ix == first_ix {
        break
      }
    }
  }
}

///|
fn AutoHintOutline::mark_near_points(
  self : AutoHintOutline,
  near_limit : Int,
) -> Unit {
  for contour in self.contours.iter() {
    let mut prev_ix = contour.last_ix
    for ix in contour.first_ix..<(contour.last_ix + 1) {
      let point = self.points.at(ix)
      let prev = self.points.at(prev_ix)
      let out_x = point.fx - prev.fx
      let out_y = point.fy - prev.fy
      if out_x.abs() + out_y.abs() < near_limit {
        prev.flags.set_marker(Near)
        self.points.set(prev_ix, prev)
      }
      prev_ix = ix
    }
  }
}

///|
fn AutoHintOutline::simplify_topology(self : AutoHintOutline) -> Unit {
  for i in 0..= 0 && (in_y ^ out_y) >= 0 {
        let p = self.points.at(i)
        p.flags.set_marker(WeakInterpolation)
        self.points.set(i, p)
        let pv = self.points.at(v_index)
        pv.u = u_index
        self.points.set(v_index, pv)
        let pu = self.points.at(u_index)
        pu.v = v_index
        self.points.set(u_index, pu)
      }
    }
  }
}

///|
fn autohint_is_corner_flat(
  in_x : Int,
  in_y : Int,
  out_x : Int,
  out_y : Int,
) -> Bool {
  let ax = in_x + out_x
  let ay = in_y + out_y
  fn autohint_hypot(x0 : Int, y0 : Int) -> Int {
    let x = x0.abs()
    let y = y0.abs()
    if x > y {
      x + ((3 * y) >> 3)
    } else {
      y + ((3 * x) >> 3)
    }
  }

  let d_in = autohint_hypot(in_x, in_y)
  let d_out = autohint_hypot(out_x, out_y)
  let d_hypot = autohint_hypot(ax, ay)
  d_in + d_out - d_hypot < d_hypot >> 4
}

///|
fn AutoHintOutline::check_remaining_weak_points(self : AutoHintOutline) -> Unit {
  for i in 0.. Unit {
  let mut sum : Int64 = 0
  for contour in self.contours.iter() {
    let first = contour.first_ix
    let last = contour.last_ix
    if first < 0 || last < first || last >= self.points.length() {
      continue
    }
    for i in first..<(last + 1) {
      let p0 = self.points.at(i)
      let j = if i == last { first } else { i + 1 }
      let p1 = self.points.at(j)
      sum = sum +
        p0.x.to_int64() * p1.y.to_int64() -
        p1.x.to_int64() * p0.y.to_int64()
    }
  }
  // Match fontations' winding convention: positive area => Clockwise.
  if sum > 0 {
    self.orientation = Some(Clockwise)
  } else if sum < 0 {
    self.orientation = Some(CounterClockwise)
  } else {
    self.orientation = None
  }
}

///|
/// Returns basic stats for the extracted outline.
///
/// This is used by the autohinter pipeline (bounds-based heuristics). Keeping
/// it here also avoids unused-field warnings while the full hinting passes are
/// being ported.

///|
/// Applies 16.16 scale factors and 26.6 deltas to each point.
///
/// This matches upstream `skrifa::outline::autohint::outline::Outline::scale`.
fn AutoHintOutline::scale(
  self : AutoHintOutline,
  x_scale : Int,
  y_scale : Int,
  x_delta : Int,
  y_delta : Int,
) -> Unit {
  for i in 0..