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

///|
/// Support for applying embedded hinting instructions.
///
/// Ported from `fontations/skrifa/src/outline/hint.rs` (Apache-2.0 OR MIT).

///|
pub(all) suberror HintError {
  Unsupported
  UnexpectedEndOfBytecode
  /// Execution stopped on an opcode that this port does not yet implement.
  UnhandledOpcode(Int, Int)
  DefinitionInGlyphProgram
  NestedDefinition
  DefinitionTooLarge
  TooManyDefinitions
  InvalidDefinition(Int)
  ValueStackOverflow
  ValueStackUnderflow
  CallStackOverflow
  CallStackUnderflow
  InvalidStackValue(Int)
  InvalidPointIndex(Int)
  InvalidPointRange(Int, Int)
  InvalidContourIndex(Int)
  InvalidCvtIndex(Int)
  InvalidStorageIndex(Int)
  DivideByZero
  InvalidZoneIndex(Int)
  NegativeLoopCounter
  InvalidJump
  ExceededExecutionBudget
}

///|
pub struct HintingOptions {
  engine : Engine
  target : Target
}

///|
pub fn HintingOptions::default() -> HintingOptions {
  { engine: AutoFallback, target: Target::default() }
}

///|
/// Creates hinting options with explicit engine + target.
pub fn HintingOptions::HintingOptions(
  engine : Engine,
  target : Target,
) -> HintingOptions {
  { engine, target }
}

///|
pub(all) enum Engine {
  Interpreter
  /// Automatic hinter that adjusts outlines without embedded instructions.
  ///
  /// Glyph styles can be precomputed per font and may be provided here as an
  /// optimization to avoid recomputing them for each instance.
  Auto(GlyphStyles?)
  AutoFallback
}

///|
/// Resolves `AutoFallback` based on outline collection preference.
pub fn Engine::resolve_auto_fallback(
  self : Engine,
  outlines : OutlineGlyphCollection,
) -> Engine {
  match self {
    AutoFallback =>
      if outlines.prefer_interpreter() {
        Interpreter
      } else {
        Auto(None)
      }
    _ => self
  }
}

///|
/// Converts an engine selection into `HintingOptions` with default target.
pub fn Engine::into(self : Engine) -> HintingOptions {
  HintingOptions(self, Target::default())
}

///|
pub(all) enum Target {
  Mono
  Smooth(SmoothMode, Bool, Bool)
}

///|
pub fn Target::default() -> Target {
  SmoothMode::Normal.into()
}

///|
pub(all) enum SmoothMode {
  Normal
  Light
  Lcd
  VerticalLcd
}

///|
pub fn SmoothMode::into(self : SmoothMode) -> Target {
  Smooth(self, true, false)
}

///|
/// Modes that control hinting when using embedded instructions.
///
/// This mirrors the legacy FreeType hinting mode selector and is intentionally
/// undocumented upstream.
pub(all) enum HintingMode {
  Strong
  Smooth(LcdLayout?, Bool)
}

///|
/// Specifies direction of pixel layout for LCD-based subpixel hinting.
pub(all) enum LcdLayout {
  Horizontal
  Vertical
}

///|
pub fn HintingMode::into(self : HintingMode) -> HintingOptions {
  let target = match self {
    Strong => Target::Mono
    Smooth(lcd_subpixel, preserve_linear_metrics) => {
      let mode = match lcd_subpixel {
        None => SmoothMode::Normal
        Some(Horizontal) => Lcd
        Some(Vertical) => VerticalLcd
      }
      Smooth(mode, true, preserve_linear_metrics)
    }
  }
  { engine: AutoFallback, target }
}

///|
pub fn Target::mode(self : Target) -> SmoothMode? {
  match self {
    Smooth(mode, _, _) => Some(mode)
    _ => None
  }
}

///|
pub fn Target::symmetric_rendering(self : Target) -> Bool {
  match self {
    Smooth(_, symmetric, _) => symmetric
    _ => false
  }
}

///|
pub fn Target::preserve_linear_metrics(self : Target) -> Bool {
  match self {
    Smooth(_, _, preserve) => preserve
    _ => false
  }
}

///|
pub fn Target::is_smooth(self : Target) -> Bool {
  match self {
    Smooth(_, _, _) => true
    _ => false
  }
}

///|
pub fn Target::is_grayscale_cleartype(self : Target) -> Bool {
  match self {
    Smooth(Normal, _, _) => true
    Smooth(Light, _, _) => true
    _ => false
  }
}

///|
pub fn Target::is_light(self : Target) -> Bool {
  match self {
    Smooth(Light, _, _) => true
    _ => false
  }
}

///|
pub fn Target::is_lcd(self : Target) -> Bool {
  match self {
    Smooth(Lcd, _, _) => true
    _ => false
  }
}

///|
pub fn Target::is_vertical_lcd(self : Target) -> Bool {
  match self {
    Smooth(VerticalLcd, _, _) => true
    _ => false
  }
}

///|
/// Hinting instance that uses information embedded in the font to perform
/// grid-fitting.
///
/// This follows the same high-level behavior as upstream:
/// - The backend engine is selected at configuration time (Interpreter/Auto).
/// - For TrueType outlines, `fpgm` + `prep` are executed during configuration.
pub struct HintingInstance {
  priv mut size : @moon_skrifa.Size
  priv coords : Array[@moon_skrifa.NormalizedCoord]
  priv mut target : Target
  priv mut kind : HinterKind
}

///|
priv enum HinterKind {
  None_
  Glyf(TtHintInstance)
  Cff(Bool)
  Auto(AutoHintInstance)
}

///|
priv struct AutoHintInstance {
  styles : GlyphStyles
  is_fixed_width : Bool
  font_style : @moon_skrifa.Style
}

///|
const AUTOHINT_TAG_POST : UInt = 0x706F7374 // "post"

///|
fn autohint_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)
  }
}

///|
fn autohint_is_fixed_width(font : @moon_skrifa.FontRef) -> Bool {
  match font.table(AUTOHINT_TAG_POST) {
    None => false
    Some(post) =>
      if post.length() < 16 {
        false
      } else {
        match autohint_read_u32_be(post, 12) {
          None => false
          Some(v) => v.reinterpret_as_int() != 0
        }
      }
  }
}

///|
fn AutoHintInstance::AutoHintInstance(
  outlines : OutlineGlyphCollection,
  styles : GlyphStyles?,
) -> AutoHintInstance {
  let resolved = match styles {
    Some(s) => s
    None => GlyphStyles(outlines)
  }
  let attrs = outlines.font.attributes()
  {
    styles: resolved,
    is_fixed_width: autohint_is_fixed_width(outlines.font),
    font_style: attrs.style(),
  }
}

///|
priv struct AutoHintEdgeMetrics {
  left_opos : Int
  left_pos : Int
  right_opos : Int
  right_pos : Int
}

///|
fn hint_round_ppem(scale_bits : Int, upem : Int) -> Int {
  let v = scale_bits.to_int64() * upem.to_int64()
  let ppem_26_6 = (v + (0x8000).to_int64()) >> 16
  ((ppem_26_6 + (32).to_int64()) >> 6).to_int()
}

///|
pub fn HintingInstance::create(
  outlines : OutlineGlyphCollection,
  size : @moon_skrifa.Size,
  location : @moon_skrifa.LocationRef,
  options : HintingOptions,
) -> Result[HintingInstance, DrawError] {
  let h = HintingInstance::{
    size: @moon_skrifa.Size::unscaled(),
    coords: Array::new(),
    target: options.target,
    kind: None_,
  }
  match h.reconfigure(outlines, size, location, options) {
    Err(e) => Err(e)
    Ok(_) => Ok(h)
  }
}

///|
pub fn HintingInstance::size(self : HintingInstance) -> @moon_skrifa.Size {
  self.size
}

///|
pub fn HintingInstance::location(
  self : HintingInstance,
) -> @moon_skrifa.LocationRef {
  LocationRef(self.coords.op_as_view())
}

///|
pub fn HintingInstance::target(self : HintingInstance) -> Target {
  self.target
}

///|
pub fn HintingInstance::is_enabled(self : HintingInstance) -> Bool {
  match self.kind {
    None_ => false
    Glyf(tt) => tt.is_enabled()
    Cff(_) => true
    Auto(_) => true
  }
}

///|
pub fn HintingInstance::reconfigure(
  self : HintingInstance,
  outlines : OutlineGlyphCollection,
  size : @moon_skrifa.Size,
  location : @moon_skrifa.LocationRef,
  options : HintingOptions,
) -> Result[Unit, DrawError] {
  self.size = size
  self.coords.clear()
  for c in location.effective_coords().iter() {
    self.coords.push(c)
  }
  self.target = options.target
  let engine = match options.engine {
    AutoFallback =>
      if outlines.prefer_interpreter() {
        Engine::Interpreter
      } else {
        Auto(None)
      }
    _ => options.engine
  }
  let prev = self.kind
  self.kind = None_
  match engine {
    Interpreter =>
      match outlines.format() {
        Some(Glyf) => {
          let tt = match prev {
            Glyf(inst) => inst
            _ => TtHintInstance::default()
          }
          let upem = outline_units_per_em(outlines.font).to_int()
          let size_for_scale = match size.ppem() {
            None => size
            Some(ppem) =>
              if outlines.fractional_size_hinting() {
                size
              } else {
                Size(ppem.round())
              }
          }
          let (_, scale_bits) = glyf_compute_scale_bits(
            size_for_scale,
            upem.to_uint16(),
          )
          let rounded_ppem = hint_round_ppem(scale_bits, upem)
          match
            tt.reconfigure(
              outlines.font,
              scale_bits,
              rounded_ppem,
              self.target,
              self.coords.op_as_view(),
            ) {
            Err(e) => return Err(HintingFailed(e))
            Ok(_) => ()
          }
          self.kind = Glyf(tt)
        }
        Some(Cff) => self.kind = Cff(false)
        Some(Cff2) => self.kind = Cff(true)
        _ => ()
      }
    Auto(_styles) =>
      match outlines.format() {
        None => ()
        Some(_) => self.kind = Auto(AutoHintInstance(outlines, _styles))
      }
    _ => ()
  }
  Ok(())
}

///|
fn tt_bits_to_double(bits : Int) -> Double {
  bits.to_double() / 64.0
}

///|
fn tt_zone_emit_contour_freetype(
  out : Array[PathElement],
  zone : TtZone,
  start : Int,
  end : Int,
  x_shift : Int,
) -> Unit {
  let n = end - start + 1
  if n <= 0 {
    return
  }
  let first = zone.points.at(start)
  let last = zone.points.at(end)
  let first_on = (zone.flags.at(start) & TT_POINT_ON_CURVE) != 0
  let last_on = (zone.flags.at(end) & TT_POINT_ON_CURVE) != 0
  let (sx, sy, start_idx) = if first_on {
    (first.x - x_shift, first.y, start)
  } else if last_on {
    (last.x - x_shift, last.y, end)
  } else {
    (
      glyf_midpoint_i32(last.x - x_shift, first.x - x_shift),
      glyf_midpoint_i32(last.y, first.y),
      -1,
    )
  }
  out.push(MoveTo(tt_bits_to_double(sx), tt_bits_to_double(sy)))
  let mut control : (Int, Int)? = None
  if start_idx == -1 {
    for i in start..<(end + 1) {
      let p = zone.points.at(i)
      let on = (zone.flags.at(i) & TT_POINT_ON_CURVE) != 0
      let px = p.x - x_shift
      let py = p.y
      if on {
        match control {
          None => out.push(LineTo(tt_bits_to_double(px), tt_bits_to_double(py)))
          Some((cx, cy)) => {
            out.push(
              QuadTo(
                tt_bits_to_double(cx),
                tt_bits_to_double(cy),
                tt_bits_to_double(px),
                tt_bits_to_double(py),
              ),
            )
            control = None
          }
        }
      } else {
        match control {
          None => control = Some((px, py))
          Some((cx, cy)) => {
            let mx = glyf_midpoint_i32(cx, px)
            let my = glyf_midpoint_i32(cy, py)
            out.push(
              QuadTo(
                tt_bits_to_double(cx),
                tt_bits_to_double(cy),
                tt_bits_to_double(mx),
                tt_bits_to_double(my),
              ),
            )
            control = Some((px, py))
          }
        }
      }
    }
  } else {
    let base = start_idx - start
    for step in 1.. out.push(LineTo(tt_bits_to_double(px), tt_bits_to_double(py)))
          Some((cx, cy)) => {
            out.push(
              QuadTo(
                tt_bits_to_double(cx),
                tt_bits_to_double(cy),
                tt_bits_to_double(px),
                tt_bits_to_double(py),
              ),
            )
            control = None
          }
        }
      } else {
        match control {
          None => control = Some((px, py))
          Some((cx, cy)) => {
            let mx = glyf_midpoint_i32(cx, px)
            let my = glyf_midpoint_i32(cy, py)
            out.push(
              QuadTo(
                tt_bits_to_double(cx),
                tt_bits_to_double(cy),
                tt_bits_to_double(mx),
                tt_bits_to_double(my),
              ),
            )
            control = Some((px, py))
          }
        }
      }
    }
  }
  match control {
    Some((cx, cy)) =>
      out.push(
        QuadTo(
          tt_bits_to_double(cx),
          tt_bits_to_double(cy),
          tt_bits_to_double(sx),
          tt_bits_to_double(sy),
        ),
      )
    None => ()
  }
  out.push(Close)
}

///|
fn tt_zone_to_path_freetype(zone : TtZone) -> Array[PathElement] {
  if zone.contours.length() == 0 {
    return Array::new()
  }
  let last_end = zone.contours.at(zone.contours.length() - 1)
  let num_points = last_end + 1
  if num_points <= 0 || zone.points.length() < num_points + 4 {
    return Array::new()
  }
  let x_shift = zone.points.at(num_points).x
  let out : Array[PathElement] = Array::new()
  let mut start = 0
  for end_ix in zone.contours.iter() {
    if end_ix >= start {
      tt_zone_emit_contour_freetype(out, zone, start, end_ix, x_shift)
    }
    start = end_ix + 1
  }
  out
}

///|
const TAG_HDMX_TT_HINT : UInt = 0x68646D78 // "hdmx"

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

///|
fn hint_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 hint_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)
  }
}

///|
fn hint_hdmx_width(
  font : @moon_skrifa.FontRef,
  ppem : Int,
  gid : @moon_skrifa.GlyphId,
) -> Int? {
  let hdmx = match font.table(TAG_HDMX_TT_HINT) {
    None => return None
    Some(v) => v
  }
  if hdmx.length() < 8 {
    return None
  }
  let maxp = match font.table(TAG_MAXP_TT_HINT) {
    None => return None
    Some(v) => v
  }
  let num_glyphs = hint_read_u16_be(maxp, 4).unwrap_or(0)
  if num_glyphs <= 0 {
    return None
  }
  let idx = gid.to_uint64().to_int()
  if idx < 0 || idx >= num_glyphs {
    return None
  }
  let num_records = hint_read_u16_be(hdmx, 2).unwrap_or(0)
  let rec_size = hint_read_u32_be(hdmx, 4).unwrap_or(0).to_uint64().to_int()
  if rec_size <= 0 {
    return None
  }
  let base = 8
  for i in 0.. hdmx.length() {
      continue
    }
    // record: ppem (u8), max_width (u8), widths[num_glyphs]
    if off + 2 + num_glyphs > hdmx.length() {
      continue
    }
    let pix = hdmx.at(off).to_int()
    if pix == ppem {
      let widths_off = off + 2
      return Some(hdmx.at(widths_off + idx).to_int())
    }
  }
  None
}

///|
fn HintingInstance::hinted_path(
  self : HintingInstance,
  glyph : OutlineGlyph,
  is_pedantic : Bool,
  path_style : PathStyle,
) -> Result[(AdjustedMetrics, Array[PathElement]), DrawError] {
  match self.kind {
    None_ => Err(NoSources)
    Auto(a) =>
      a.hinted_path(
        glyph,
        self.size,
        LocationRef(self.coords.op_as_view()),
        path_style,
        self.target,
      )
    Glyf(tt) => {
      if path_style is HarfBuzz {
        return Err(HarfBuzzHintingUnsupported)
      }
      if !tt.is_enabled() {
        let settings = DrawSettings::unhinted(
          self.size,
          LocationRef(self.coords.op_as_view()),
        ).with_path_style(path_style)
        let metrics0 = match glyph.draw(settings, NullPen()) {
          Err(e) => return Err(e)
          Ok(m) => m
        }
        let metrics = AdjustedMetrics::{
          has_overlaps: metrics0.has_overlaps,
          lsb: metrics0.lsb,
          advance_width: match metrics0.advance_width {
            None => None
            Some(v) => Some(v.round())
          },
        }
        let path = match glyph.path(settings) {
          Err(e) => return Err(e)
          Ok(p) => p
        }
        return Ok((metrics, path))
      }
      self.hinted_glyf_path(glyph, is_pedantic)
    }
    Cff(is_cff2) => {
      if path_style is HarfBuzz {
        return Err(HarfBuzzHintingUnsupported)
      }
      let location = @moon_skrifa.LocationRef::LocationRef(
        self.coords.op_as_view(),
      )
      let path = if is_cff2 {
        cff2_outline_path_hinted(
          glyph.font,
          glyph.glyph_id,
          location.effective_coords(),
          self.size,
        )
      } else {
        cff_outline_path_hinted(glyph.font, glyph.glyph_id, self.size)
      }
      match path {
        None => Err(GlyphNotFound(glyph.glyph_id))
        Some(p) => Ok((AdjustedMetrics::default(), p))
      }
    }
  }
}

///|
fn AutoHintInstance::hinted_path(
  self : AutoHintInstance,
  glyph : OutlineGlyph,
  size : @moon_skrifa.Size,
  location : @moon_skrifa.LocationRef,
  path_style : PathStyle,
  target : Target,
) -> Result[(AdjustedMetrics, Array[PathElement]), DrawError] {
  let ppem = match size.ppem() {
    None => 0.0
    Some(v) => v
  }
  let unscaled_settings = DrawSettings::unhinted(
    @moon_skrifa.Size::unscaled(),
    location,
  ).with_path_style(FreeType)
  let unscaled_path = match glyph.path(unscaled_settings) {
    Err(e) => return Err(e)
    Ok(p) => p
  }
  let outline = match glyph.kind {
    Glyf => {
      let o = AutoHintOutline::from_path(Array::new())
      match o.fill_glyf(glyph.font, glyph.glyph_id) {
        Ok(_) => o
        Err(_) => AutoHintOutline::from_path(unscaled_path)
      }
    }
    _ => AutoHintOutline::from_path(unscaled_path)
  }
  let upem = outline_units_per_em(glyph.font).to_int()
  let ppem = if ppem == 0.0 { upem.to_double() } else { ppem }
  let style_ix = self.styles
    .style_index(glyph.glyph_id)
    .unwrap_or(AUTOHINT_STYLE_LATN)
  let metrics_outlines = OutlineGlyphCollection::from_font(glyph.font)
  let unscaled_metrics = autohint_compute_unscaled_style_metrics(
    metrics_outlines, style_ix, upem,
  )
  let scale0 = AutoHintScale(
    ppem,
    upem,
    self.font_style,
    target,
    unscaled_metrics.group,
  )
  let scaled_metrics = autohint_scale_style_metrics(unscaled_metrics, scale0)
  let scale = scaled_metrics.scale
  outline.scale(scale.x_scale, scale.y_scale, scale.x_delta, scale.y_delta)
  let axis = AutoHintAxis::default()
  let y_axis_scale = scaled_metrics.axes.at(AUTOHINT_DIM_VERTICAL).scale
  let mut hinted_x_scale = scale.x_scale
  let mut edge_metrics : AutoHintEdgeMetrics? = None
  let missing_blues = unscaled_metrics.group is Default &&
    scaled_metrics.axes.at(AUTOHINT_DIM_VERTICAL).blues.is_empty()
  if !missing_blues {
    for dim in 0..<2 {
      if dim == AUTOHINT_DIM_HORIZONTAL &&
        (scale.flags & AUTOHINT_SCALE_NO_HORIZONTAL) != 0 {
        continue
      }
      if dim == AUTOHINT_DIM_VERTICAL &&
        (scale.flags & AUTOHINT_SCALE_NO_VERTICAL) != 0 {
        continue
      }
      let axis_metrics = scaled_metrics.axes.at(dim)
      axis.reset(dim, outline.orientation)
      let ok = autohint_compute_segments(outline, axis, unscaled_metrics.group)
      if !ok {
        continue
      }
      let axis_scale = axis_metrics.scale
      autohint_link_segments(
        outline,
        axis,
        axis_scale,
        unscaled_metrics.group,
        unscaled_metrics.axes.at(dim).max_width(),
      )
      autohint_compute_edges(
        axis,
        axis_metrics.width_metrics.edge_distance_threshold,
        unscaled_metrics.hint_top_to_bottom,
        y_axis_scale,
        axis_scale,
        unscaled_metrics.group,
      )
      if dim == AUTOHINT_DIM_VERTICAL {
        if !(unscaled_metrics.group is Default) ||
          !self.styles._is_non_base(glyph.glyph_id) {
          autohint_compute_blue_edges(
            axis,
            scale,
            unscaled_metrics.axes.at(dim).blues,
            axis_metrics.blues,
            unscaled_metrics.group,
          )
        }
      }
      autohint_hint_edges(
        axis,
        axis_metrics,
        unscaled_metrics.group,
        scale,
        unscaled_metrics.hint_top_to_bottom,
      )
      if dim == AUTOHINT_DIM_HORIZONTAL && axis.edges.length() > 1 {
        hinted_x_scale = axis_metrics.scale
        let left = axis.edges.at(0)
        let right = axis.edges.at(axis.edges.length() - 1)
        edge_metrics = Some({
          left_opos: left.opos,
          left_pos: left.pos,
          right_opos: right.opos,
          right_pos: right.pos,
        })
      }
      autohint_align_edge_points(
        outline,
        axis,
        unscaled_metrics.group,
        scale.flags,
      )
      autohint_align_strong_points(outline, axis)
      autohint_align_weak_points(outline, dim)
    }
  }
  // Adjust advances and origin for better pixel alignment.
  let gm = glyph.font.glyph_metrics(@moon_skrifa.Size::unscaled(), location)
  let h_advance0 = gm.advance_width(glyph.glyph_id).unwrap_or(0.0)
  let h_advance = h_advance0.round().to_int()
  let mut digit_advance : Int? = None
  let mut digits_have_same_width = true
  for d in 0x30..<0x3A {
    match glyph.font.charmap().map(d |> Int::reinterpret_as_uint) {
      None => ()
      Some(gid) =>
        match gm.advance_width(gid) {
          None => ()
          Some(adv) => {
            let aw = adv.round().to_int()
            match digit_advance {
              None => digit_advance = Some(aw)
              Some(prev) => if prev != aw { digits_have_same_width = false }
            }
          }
        }
    }
    if !digits_have_same_width {
      break
    }
  }
  let mut pp1x = 0
  let mut pp2x = autohint_fixed_mul(h_advance, hinted_x_scale)
  let is_light = target.is_light() || target.preserve_linear_metrics()
  if !is_light {
    match (edge_metrics, (scale.flags & AUTOHINT_SCALE_NO_ADVANCE) == 0) {
      (Some(em), true) => {
        let old_rsb = pp2x - em.right_opos
        let old_lsb = em.left_opos
        let new_lsb = em.left_pos
        let mut pp1x_uh = new_lsb - old_lsb
        let mut pp2x_uh = em.right_pos + old_rsb
        if old_lsb < 24 {
          pp1x_uh = pp1x_uh - 8
        }
        if old_rsb < 24 {
          pp2x_uh = pp2x_uh + 8
        }
        pp1x = autohint_pix_round(pp1x_uh)
        pp2x = autohint_pix_round(pp2x_uh)
        if pp1x >= new_lsb && old_lsb > 0 {
          pp1x = pp1x - 64
        }
        if pp2x <= em.right_pos && old_rsb > 0 {
          pp2x = pp2x + 64
        }
      }
      _ => {
        pp1x = autohint_pix_round(pp1x)
        pp2x = autohint_pix_round(pp2x)
      }
    }
  } else {
    pp1x = autohint_pix_round(pp1x)
    pp2x = autohint_pix_round(pp2x)
  }
  if pp1x != 0 {
    for i in 0.. Result[(AdjustedMetrics, Array[PathElement]), DrawError] {
  let tt = match self.kind {
    Glyf(tt) => tt
    _ => return Err(NoSources)
  }
  let font = glyph.font
  let gid = glyph.glyph_id
  let scale_bits = tt.retained.scale
  let fpgm = font.table(TAG_FPGM_TT).unwrap_or(tt_empty_bytes_view())
  let prep = font.table(TAG_PREP_TT).unwrap_or(tt_empty_bytes_view())
  let zone = match
    tt_load_glyph_zone_for_hinting(
      font,
      gid,
      self.coords.op_as_view(),
      scale_bits,
      tt,
      fpgm,
      prep,
      is_pedantic,
    ) {
    Err(e) => return Err(e)
    Ok(z) => z
  }
  if zone.contours.length() == 0 {
    if zone.points.length() >= 4 {
      // Empty glyph: use phantom points to compute metrics, matching upstream.
      let phantom0 = zone.points.at(0)
      let phantom1 = zone.points.at(1)
      let advance = tt_bits_to_double(phantom1.x - phantom0.x).round()
      let lsb = tt_bits_to_double(phantom0.x)
      let metrics = AdjustedMetrics::{
        has_overlaps: glyph.has_overlaps().unwrap_or(false),
        lsb: Some(lsb),
        advance_width: Some(advance),
      }
      return Ok((metrics, Array::new()))
    } else {
      return Ok((AdjustedMetrics::default(), Array::new()))
    }
  }
  let last_end = zone.contours.at(zone.contours.length() - 1)
  let num_points = last_end + 1
  if num_points < 0 || zone.points.length() < num_points + 4 {
    return Err(GlyphNotFound(gid))
  }
  let phantom0 = zone.points.at(num_points)
  let phantom1 = zone.points.at(num_points + 1)
  let mut hdmx : Int? = None
  if !tt.backward_compatibility() {
    match self.size.ppem() {
      None => ()
      Some(ppem) => {
        let ip = ppem.to_int()
        if ip.to_double() == ppem && ip >= 0 && ip <= 255 {
          hdmx = hint_hdmx_width(font, ip, gid)
        }
      }
    }
  }
  let advance0 = match hdmx {
    Some(w) => w.to_double()
    None => tt_bits_to_double(phantom1.x - phantom0.x)
  }
  let advance = advance0.round()
  let lsb = tt_bits_to_double(phantom0.x)
  let metrics = AdjustedMetrics::{
    has_overlaps: glyph.has_overlaps().unwrap_or(false),
    lsb: Some(lsb),
    advance_width: Some(advance),
  }
  Ok((metrics, tt_zone_to_path_freetype(zone)))
}