///|
/// SVG Rasterizer
/// Efficient shape rasterization algorithms (Bresenham, Scanline, etc.)

///|
/// Pixel setter callback type
/// (x, y, color) -> Unit
pub(all) struct PixelSetter {
  set : (Int, Int, Color) -> Unit
}

///|
/// Draw a single pixel
pub fn PixelSetter::pixel(
  self : PixelSetter,
  x : Int,
  y : Int,
  color : Color,
) -> Unit {
  (self.set)(x, y, color)
}

///|
/// Create a clipped pixel setter that only draws within the clip rect
pub fn PixelSetter::with_clip(
  self : PixelSetter,
  clip : ClipRect,
) -> PixelSetter {
  let inner_set = self.set
  { set: (x, y, color) => if clip.contains(x, y) { inner_set(x, y, color) } }
}

///|
/// Create a clipped pixel setter with offset (for camera translation)
pub fn PixelSetter::with_clip_and_offset(
  self : PixelSetter,
  clip : ClipRect,
  offset_x : Int,
  offset_y : Int,
) -> PixelSetter {
  let inner_set = self.set
  {
    set: (x, y, color) => {
      let tx = x + offset_x
      let ty = y + offset_y
      if clip.contains(tx, ty) {
        inner_set(tx, ty, color)
      }
    },
  }
}

///|
/// Bresenham's line algorithm - integer-only, efficient line drawing
pub fn raster_line(
  x0 : Int,
  y0 : Int,
  x1 : Int,
  y1 : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  let mut x0 = x0
  let mut y0 = y0
  let dx = if x1 > x0 { x1 - x0 } else { x0 - x1 }
  let dy = if y1 > y0 { y1 - y0 } else { y0 - y1 }
  let sx = if x0 < x1 { 1 } else { -1 }
  let sy = if y0 < y1 { 1 } else { -1 }
  let mut err = dx - dy
  while true {
    setter.pixel(x0, y0, color)
    if x0 == x1 && y0 == y1 {
      break
    }
    let e2 = 2 * err
    if e2 > -dy {
      err = err - dy
      x0 = x0 + sx
    }
    if e2 < dx {
      err = err + dx
      y0 = y0 + sy
    }
  }
}

///|
/// Bresenham's line with dash pattern support
pub fn raster_line_dashed(
  x0 : Int,
  y0 : Int,
  x1 : Int,
  y1 : Int,
  color : Color,
  dasharray : Array[Double],
  dashoffset : Double,
  setter : PixelSetter,
) -> Unit {
  if dasharray.length() == 0 {
    // No dash pattern, draw solid line
    raster_line(x0, y0, x1, y1, color, setter)
    return
  }
  // Calculate total line length
  let dx_f = (x1 - x0).to_double()
  let dy_f = (y1 - y0).to_double()
  let total_len = (dx_f * dx_f + dy_f * dy_f).sqrt()
  if total_len < 0.5 {
    return
  }
  // Dash pattern state
  let mut dash_pos = -dashoffset
  // Normalize dashoffset to be within pattern
  let total_dash = dasharray.fold(init=0.0, (acc, v) => acc + v)
  while dash_pos < 0.0 {
    dash_pos = dash_pos + total_dash
  }
  while dash_pos >= total_dash {
    dash_pos = dash_pos - total_dash
  }
  // Bresenham setup
  let mut x = x0
  let mut y = y0
  let dx = if x1 > x0 { x1 - x0 } else { x0 - x1 }
  let dy = if y1 > y0 { y1 - y0 } else { y0 - y1 }
  let sx = if x0 < x1 { 1 } else { -1 }
  let sy = if y0 < y1 { 1 } else { -1 }
  let mut err = dx - dy
  while true {
    // Calculate distance from start
    let cur_dx = (x - x0).to_double()
    let cur_dy = (y - y0).to_double()
    let cur_dist = (cur_dx * cur_dx + cur_dy * cur_dy).sqrt()
    // Update dash state based on distance traveled
    let travel = cur_dist
    let mut pos_in_pattern = dash_pos + travel
    while pos_in_pattern >= total_dash {
      pos_in_pattern = pos_in_pattern - total_dash
    }
    // Find current dash index
    let mut cum = 0.0
    let mut cur_idx = 0
    for i in 0.. pos_in_pattern {
        cur_idx = i
        break
      }
      cum = cum + dasharray[i]
    }
    // Even indices draw, odd indices gap
    let is_drawing = cur_idx % 2 == 0
    if is_drawing {
      setter.pixel(x, y, color)
    }
    if x == x1 && y == y1 {
      break
    }
    let e2 = 2 * err
    if e2 > -dy {
      err = err - dy
      x = x + sx
    }
    if e2 < dx {
      err = err + dx
      y = y + sy
    }
  }
}

///|
/// Draw polyline with dash pattern
pub fn raster_polyline_dashed(
  points : Array[(Int, Int)],
  color : Color,
  dasharray : Array[Double],
  dashoffset : Double,
  setter : PixelSetter,
) -> Unit {
  if points.length() < 2 {
    return
  }
  if dasharray.length() == 0 {
    raster_polyline(points, color, setter)
    return
  }
  let mut offset = dashoffset
  for i in 0..<(points.length() - 1) {
    let (x0, y0) = points[i]
    let (x1, y1) = points[i + 1]
    raster_line_dashed(x0, y0, x1, y1, color, dasharray, offset, setter)
    // Calculate line length and update offset
    let dx = (x1 - x0).to_double()
    let dy = (y1 - y0).to_double()
    let len = (dx * dx + dy * dy).sqrt()
    offset = offset + len
    // Normalize offset within dash pattern total length
    let total_dash = dasharray.fold(init=0.0, (acc, v) => acc + v)
    while offset >= total_dash {
      offset = offset - total_dash
    }
  }
}

///|
/// Draw rectangle outline (stroke only)
pub fn raster_rect_stroke(
  x : Int,
  y : Int,
  w : Int,
  h : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  if w <= 0 || h <= 0 {
    return
  }
  // Top edge
  for px in x..<(x + w) {
    setter.pixel(px, y, color)
  }
  // Bottom edge
  for px in x..<(x + w) {
    setter.pixel(px, y + h - 1, color)
  }
  // Left edge
  for py in (y + 1)..<(y + h - 1) {
    setter.pixel(x, py, color)
  }
  // Right edge
  for py in (y + 1)..<(y + h - 1) {
    setter.pixel(x + w - 1, py, color)
  }
}

///|
/// Fill rectangle
pub fn raster_rect_fill(
  x : Int,
  y : Int,
  w : Int,
  h : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  if w <= 0 || h <= 0 {
    return
  }
  for py in y..<(y + h) {
    for px in x..<(x + w) {
      setter.pixel(px, py, color)
    }
  }
}

///|
fn point_in_rounded_rect(
  px : Double,
  py : Double,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
  rx : Double,
  ry : Double,
) -> Bool {
  if w <= 0.0 || h <= 0.0 {
    return false
  }
  let rx = min(rx, w / 2.0)
  let ry = min(ry, h / 2.0)
  if rx <= 0.0 || ry <= 0.0 {
    return px >= x && px <= x + w && py >= y && py <= y + h
  }
  let left = x + rx
  let right = x + w - rx
  let top = y + ry
  let bottom = y + h - ry
  let dx = if px < left {
    left - px
  } else if px > right {
    px - right
  } else {
    0.0
  }
  let dy = if py < top {
    top - py
  } else if py > bottom {
    py - bottom
  } else {
    0.0
  }
  dx * dx / (rx * rx) + dy * dy / (ry * ry) <= 1.0
}

///|
fn ceil_to_int_raster(value : Double) -> Int {
  let i = value.to_int()
  if value > i.to_double() {
    i + 1
  } else {
    i
  }
}

///|
pub fn raster_rounded_rect_fill(
  x : Int,
  y : Int,
  w : Int,
  h : Int,
  rx : Int,
  ry : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  if w <= 0 || h <= 0 {
    return
  }
  let rx = min_int(rx, w / 2)
  let ry = min_int(ry, h / 2)
  if rx <= 0 || ry <= 0 {
    raster_rect_fill(x, y, w, h, color, setter)
    return
  }
  let fx = x.to_double()
  let fy = y.to_double()
  let fw = w.to_double()
  let fh = h.to_double()
  let frx = rx.to_double()
  let fry = ry.to_double()
  for py in y..<(y + h) {
    let cy = py.to_double() + 0.5
    for px in x..<(x + w) {
      let cx = px.to_double() + 0.5
      if point_in_rounded_rect(cx, cy, fx, fy, fw, fh, frx, fry) {
        setter.pixel(px, py, color)
      }
    }
  }
}

///|
pub fn raster_rect_stroke_thick(
  x : Int,
  y : Int,
  w : Int,
  h : Int,
  stroke_w : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  raster_rounded_rect_stroke_thick(x, y, w, h, 0, 0, stroke_w, color, setter)
}

///|
pub fn raster_rounded_rect_stroke_thick(
  x : Int,
  y : Int,
  w : Int,
  h : Int,
  rx : Int,
  ry : Int,
  stroke_w : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  if w <= 0 || h <= 0 || stroke_w <= 0 {
    return
  }
  if stroke_w <= 1 {
    raster_rounded_rect_stroke(x, y, w, h, rx, ry, color, setter)
    return
  }
  let half = stroke_w.to_double() / 2.0
  let outer_x = x.to_double() - half
  let outer_y = y.to_double() - half
  let outer_w = w.to_double() + stroke_w.to_double()
  let outer_h = h.to_double() + stroke_w.to_double()
  let base_rx = rx.to_double()
  let base_ry = ry.to_double()
  let outer_rx = if base_rx > 0.0 { base_rx + half } else { 0.0 }
  let outer_ry = if base_ry > 0.0 { base_ry + half } else { 0.0 }
  let inner_w = w.to_double() - stroke_w.to_double()
  let inner_h = h.to_double() - stroke_w.to_double()
  let inner_x = x.to_double() + half
  let inner_y = y.to_double() + half
  let inner_rx = if base_rx > 0.0 { max(0.0, base_rx - half) } else { 0.0 }
  let inner_ry = if base_ry > 0.0 { max(0.0, base_ry - half) } else { 0.0 }
  let min_x = outer_x.floor().to_int()
  let min_y = outer_y.floor().to_int()
  let max_x = ceil_to_int_raster(outer_x + outer_w)
  let max_y = ceil_to_int_raster(outer_y + outer_h)
  for py in min_y.. Unit {
  if w <= 0 || h <= 0 {
    return
  }
  // Build LUT for fast color lookup (256 entries)
  let lut = grad.build_lut(256)
  // Gradient direction vector (in normalized 0-1 space)
  let gx1 = grad.x1
  let gy1 = grad.y1
  let gx2 = grad.x2
  let gy2 = grad.y2
  let gdx = gx2 - gx1
  let gdy = gy2 - gy1
  let glen_sq = gdx * gdx + gdy * gdy
  let inv_w = if w > 1 { 1.0 / (w - 1).to_double() } else { 0.0 }
  let inv_h = if h > 1 { 1.0 / (h - 1).to_double() } else { 0.0 }
  let inv_glen_sq = if glen_sq > 0.0001 { 1.0 / glen_sq } else { 0.0 }
  for py in y..<(y + h) {
    let ny = if h > 1 { (py - y).to_double() * inv_h } else { 0.5 }
    let base_y = (ny - gy1) * gdy
    for px in x..<(x + w) {
      // Normalize pixel position to 0-1 within rect
      let nx = if w > 1 { (px - x).to_double() * inv_w } else { 0.5 }
      // Project onto gradient line
      let t = ((nx - gx1) * gdx + base_y) * inv_glen_sq
      // Get color from LUT
      let color = lut_color_at(lut, t)
      let final_color = if opacity < 1.0 {
        Color::rgba(
          color.r,
          color.g,
          color.b,
          (color.a.to_double() * opacity).to_int(),
        )
      } else {
        color
      }
      setter.pixel(px, py, final_color)
    }
  }
}

///|
/// Draw filled rectangle with radial gradient
pub fn raster_rect_radial_gradient(
  x : Int,
  y : Int,
  w : Int,
  h : Int,
  grad : RadialGradient,
  opacity : Double,
  setter : PixelSetter,
) -> Unit {
  if w <= 0 || h <= 0 {
    return
  }
  // Build LUT for fast color lookup
  let lut = grad.build_lut(256)
  // Precompute gradient parameters
  let gx = grad.cx
  let gy = grad.cy
  let gr = grad.r
  let inv_gr = if gr > 0.0 { 1.0 / gr } else { 0.0 }
  let inv_w = if w > 1 { 1.0 / (w - 1).to_double() } else { 0.0 }
  let inv_h = if h > 1 { 1.0 / (h - 1).to_double() } else { 0.0 }
  for py in y..<(y + h) {
    let ny = if h > 1 { (py - y).to_double() * inv_h } else { 0.5 }
    let dy = ny - gy
    let dy_sq = dy * dy
    for px in x..<(x + w) {
      // Normalize pixel position to 0-1 within rect
      let nx = if w > 1 { (px - x).to_double() * inv_w } else { 0.5 }
      // Distance from center
      let dx = nx - gx
      let dist = (dx * dx + dy_sq).sqrt()
      // Normalized position
      let t = dist * inv_gr
      let t = apply_spread_inline(t, grad.spread_method)
      // Get color from LUT
      let color = lut_color_at(lut, t)
      let final_color = if opacity < 1.0 {
        Color::rgba(
          color.r,
          color.g,
          color.b,
          (color.a.to_double() * opacity).to_int(),
        )
      } else {
        color
      }
      setter.pixel(px, py, final_color)
    }
  }
}

///|
fn apply_spread_inline(t : Double, spread : SpreadMethod) -> Double {
  match spread {
    Pad => if t < 0.0 { 0.0 } else if t > 1.0 { 1.0 } else { t }
    Repeat => t - t.floor()
    Reflect => {
      let t2 = t - t.floor()
      if t.to_int() % 2 == 0 {
        t2
      } else {
        1.0 - t2
      }
    }
  }
}

///|
/// Draw filled circle with radial gradient
pub fn raster_circle_radial_gradient(
  cx : Int,
  cy : Int,
  r : Int,
  grad : RadialGradient,
  opacity : Double,
  setter : PixelSetter,
) -> Unit {
  if r <= 0 {
    return
  }
  // Build LUT for fast color lookup
  let lut = grad.build_lut(256)
  let r_sq = r * r
  let r_f = r.to_double()
  let inv_2r = 0.5 / r_f
  // Precompute gradient center offset from circle center
  let gx = grad.cx - 0.5
  let gy = grad.cy - 0.5
  let gr = grad.r
  let inv_gr = if gr > 0.0 { 1.0 / gr } else { 0.0 }
  for py in (cy - r)..<=(cy + r) {
    let dy = py - cy
    let dy_sq = dy * dy
    let ny_offset = dy.to_double() * inv_2r - gy
    for px in (cx - r)..<=(cx + r) {
      let dx = px - cx
      let dist_sq = dx * dx + dy_sq
      if dist_sq <= r_sq {
        // Calculate t for radial gradient
        let nx_offset = dx.to_double() * inv_2r - gx
        let grad_dist = (nx_offset * nx_offset + ny_offset * ny_offset).sqrt()
        let t = grad_dist * inv_gr
        let t = apply_spread_inline(t, grad.spread_method)
        // Get color from LUT
        let color = lut_color_at(lut, t)
        let final_color = if opacity < 1.0 {
          Color::rgba(
            color.r,
            color.g,
            color.b,
            (color.a.to_double() * opacity).to_int(),
          )
        } else {
          color
        }
        setter.pixel(px, py, final_color)
      }
    }
  }
}

///|
/// Draw filled ellipse with radial gradient
pub fn raster_ellipse_radial_gradient(
  cx : Int,
  cy : Int,
  rx : Int,
  ry : Int,
  grad : RadialGradient,
  opacity : Double,
  setter : PixelSetter,
) -> Unit {
  if rx <= 0 || ry <= 0 {
    return
  }
  // Build LUT for fast color lookup
  let lut = grad.build_lut(256)
  let inv_rx = 1.0 / rx.to_double()
  let inv_ry = 1.0 / ry.to_double()
  let inv_2rx = 0.5 * inv_rx
  let inv_2ry = 0.5 * inv_ry
  // Precompute gradient center offset
  let gx = grad.cx - 0.5
  let gy = grad.cy - 0.5
  let gr = grad.r
  let inv_gr = if gr > 0.0 { 1.0 / gr } else { 0.0 }
  for py in (cy - ry)..<=(cy + ry) {
    let dy_norm = (py - cy).to_double() * inv_ry
    let dy_sq = dy_norm * dy_norm
    let ny_offset = (py - cy).to_double() * inv_2ry - gy
    for px in (cx - rx)..<=(cx + rx) {
      let dx_norm = (px - cx).to_double() * inv_rx
      let dist_sq = dx_norm * dx_norm + dy_sq
      if dist_sq <= 1.0 {
        // Calculate t for radial gradient
        let nx_offset = (px - cx).to_double() * inv_2rx - gx
        let grad_dist = (nx_offset * nx_offset + ny_offset * ny_offset).sqrt()
        let t = grad_dist * inv_gr
        let t = apply_spread_inline(t, grad.spread_method)
        // Get color from LUT
        let color = lut_color_at(lut, t)
        let final_color = if opacity < 1.0 {
          Color::rgba(
            color.r,
            color.g,
            color.b,
            (color.a.to_double() * opacity).to_int(),
          )
        } else {
          color
        }
        setter.pixel(px, py, final_color)
      }
    }
  }
}

///|
/// Draw rounded rectangle outline
pub fn raster_rounded_rect_stroke(
  x : Int,
  y : Int,
  w : Int,
  h : Int,
  rx : Int,
  ry : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  if w <= 0 || h <= 0 {
    return
  }
  // Clamp radii to half dimensions
  let rx = min_int(rx, w / 2)
  let ry = min_int(ry, h / 2)
  if rx <= 0 || ry <= 0 {
    // No rounding, draw regular rect
    raster_rect_stroke(x, y, w, h, color, setter)
    return
  }
  // Top edge (middle)
  for px in (x + rx)..<(x + w - rx) {
    setter.pixel(px, y, color)
  }
  // Bottom edge (middle)
  for px in (x + rx)..<(x + w - rx) {
    setter.pixel(px, y + h - 1, color)
  }
  // Left edge (middle)
  for py in (y + ry)..<(y + h - ry) {
    setter.pixel(x, py, color)
  }
  // Right edge (middle)
  for py in (y + ry)..<(y + h - ry) {
    setter.pixel(x + w - 1, py, color)
  }
  // Draw corners using ellipse arc algorithm
  draw_corner_arc(x + rx, y + ry, rx, ry, 2, color, setter) // top-left
  draw_corner_arc(x + w - rx - 1, y + ry, rx, ry, 1, color, setter) // top-right
  draw_corner_arc(x + rx, y + h - ry - 1, rx, ry, 3, color, setter) // bottom-left
  draw_corner_arc(x + w - rx - 1, y + h - ry - 1, rx, ry, 4, color, setter) // bottom-right
}

///|
/// Draw corner arc (quadrant of ellipse)
/// quadrant: 1=top-right, 2=top-left, 3=bottom-left, 4=bottom-right
fn draw_corner_arc(
  cx : Int,
  cy : Int,
  rx : Int,
  ry : Int,
  quadrant : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  // Simplified ellipse arc using midpoint algorithm
  let rx2 = rx * rx
  let ry2 = ry * ry
  let two_rx2 = 2 * rx2
  let two_ry2 = 2 * ry2
  let mut x = 0
  let mut y = ry
  let mut px = 0
  let mut py = two_rx2 * y
  // Plot initial point
  plot_corner_point(cx, cy, x, y, quadrant, color, setter)
  // Region 1
  let mut p = ry2 - rx2 * ry + rx2 / 4
  while px < py {
    x = x + 1
    px = px + two_ry2
    if p < 0 {
      p = p + ry2 + px
    } else {
      y = y - 1
      py = py - two_rx2
      p = p + ry2 + px - py
    }
    plot_corner_point(cx, cy, x, y, quadrant, color, setter)
  }
  // Region 2
  p = ry2 * (x * 2 + 1) * (x * 2 + 1) / 4 + rx2 * (y - 1) * (y - 1) - rx2 * ry2
  while y > 0 {
    y = y - 1
    py = py - two_rx2
    if p > 0 {
      p = p + rx2 - py
    } else {
      x = x + 1
      px = px + two_ry2
      p = p + rx2 - py + px
    }
    plot_corner_point(cx, cy, x, y, quadrant, color, setter)
  }
}

///|
/// Plot point in specific quadrant relative to center
fn plot_corner_point(
  cx : Int,
  cy : Int,
  x : Int,
  y : Int,
  quadrant : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  match quadrant {
    1 => setter.pixel(cx + x, cy - y, color) // top-right
    2 => setter.pixel(cx - x, cy - y, color) // top-left
    3 => setter.pixel(cx - x, cy + y, color) // bottom-left
    4 => setter.pixel(cx + x, cy + y, color) // bottom-right
    _ => ()
  }
}

///|
/// Midpoint circle algorithm - draw circle outline
pub fn raster_circle_stroke(
  cx : Int,
  cy : Int,
  r : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  if r <= 0 {
    setter.pixel(cx, cy, color)
    return
  }
  let mut x = r
  let mut y = 0
  let mut err = 1 - r
  while x >= y {
    // Draw all 8 octants
    setter.pixel(cx + x, cy + y, color)
    setter.pixel(cx - x, cy + y, color)
    setter.pixel(cx + x, cy - y, color)
    setter.pixel(cx - x, cy - y, color)
    setter.pixel(cx + y, cy + x, color)
    setter.pixel(cx - y, cy + x, color)
    setter.pixel(cx + y, cy - x, color)
    setter.pixel(cx - y, cy - x, color)
    y = y + 1
    if err < 0 {
      err = err + 2 * y + 1
    } else {
      x = x - 1
      err = err + 2 * (y - x) + 1
    }
  }
}

///|
/// Fill circle using scanlines
pub fn raster_circle_fill(
  cx : Int,
  cy : Int,
  r : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  if r <= 0 {
    setter.pixel(cx, cy, color)
    return
  }
  let mut x = r
  let mut y = 0
  let mut err = 1 - r
  while x >= y {
    // Draw horizontal lines for each y level
    for px in (cx - x)..<=(cx + x) {
      setter..pixel(px, cy + y, color)..pixel(px, cy - y, color) |> ignore
    }
    for px in (cx - y)..<=(cx + y) {
      setter..pixel(px, cy + x, color)..pixel(px, cy - x, color) |> ignore
    }
    y = y + 1
    if err < 0 {
      err = err + 2 * y + 1
    } else {
      x = x - 1
      err = err + 2 * (y - x) + 1
    }
  }
}

///|
/// Midpoint ellipse algorithm - draw ellipse outline
pub fn raster_ellipse_stroke(
  cx : Int,
  cy : Int,
  rx : Int,
  ry : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  if rx <= 0 && ry <= 0 {
    setter.pixel(cx, cy, color)
    return
  }
  if rx <= 0 {
    // Vertical line
    for y in (cy - ry)..<=(cy + ry) {
      setter.pixel(cx, y, color)
    }
    return
  }
  if ry <= 0 {
    // Horizontal line
    for x in (cx - rx)..<=(cx + rx) {
      setter.pixel(x, cy, color)
    }
    return
  }
  let rx2 = rx * rx
  let ry2 = ry * ry
  let two_rx2 = 2 * rx2
  let two_ry2 = 2 * ry2
  let mut x = 0
  let mut y = ry
  let mut px = 0
  let mut py = two_rx2 * y
  // Plot initial points
  setter.pixel(cx + x, cy + y, color)
  setter.pixel(cx - x, cy + y, color)
  setter.pixel(cx + x, cy - y, color)
  setter.pixel(cx - x, cy - y, color)
  // Region 1
  let mut p = ry2 - rx2 * ry + rx2 / 4
  while px < py {
    x = x + 1
    px = px + two_ry2
    if p < 0 {
      p = p + ry2 + px
    } else {
      y = y - 1
      py = py - two_rx2
      p = p + ry2 + px - py
    }
    setter.pixel(cx + x, cy + y, color)
    setter.pixel(cx - x, cy + y, color)
    setter.pixel(cx + x, cy - y, color)
    setter.pixel(cx - x, cy - y, color)
  }
  // Region 2
  p = ry2 * (x * 2 + 1) * (x * 2 + 1) / 4 + rx2 * (y - 1) * (y - 1) - rx2 * ry2
  while y > 0 {
    y = y - 1
    py = py - two_rx2
    if p > 0 {
      p = p + rx2 - py
    } else {
      x = x + 1
      px = px + two_ry2
      p = p + rx2 - py + px
    }
    setter.pixel(cx + x, cy + y, color)
    setter.pixel(cx - x, cy + y, color)
    setter.pixel(cx + x, cy - y, color)
    setter.pixel(cx - x, cy - y, color)
  }
}

///|
/// Fill ellipse using scanlines
pub fn raster_ellipse_fill(
  cx : Int,
  cy : Int,
  rx : Int,
  ry : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  if rx <= 0 && ry <= 0 {
    setter.pixel(cx, cy, color)
    return
  }
  if rx <= 0 {
    for y = cy - ry; y <= cy + ry; y = y + 1 {
      setter.pixel(cx, y, color)
    }
    return
  }
  if ry <= 0 {
    for x = cx - rx; x <= cx + rx; x = x + 1 {
      setter.pixel(x, cy, color)
    }
    return
  }
  // Fill using scanline for each y from -ry to +ry
  for dy = -ry; dy <= ry; dy = dy + 1 {
    let y_frac = dy.to_double() / ry.to_double()
    let x_extent_sq = 1.0 - y_frac * y_frac
    if x_extent_sq >= 0.0 {
      let x_extent = (x_extent_sq.sqrt() * rx.to_double()).to_int()
      for dx = -x_extent; dx <= x_extent; dx = dx + 1 {
        setter.pixel(cx + dx, cy + dy, color)
      }
    }
  }
}

///|
/// Polygon fill using scanline algorithm with edge table
pub fn raster_polygon_fill(
  points : Array[(Int, Int)],
  color : Color,
  setter : PixelSetter,
) -> Unit {
  raster_polygon_fill_rule(points, color, NonZero, setter)
}

///|
/// Polygon fill with specified fill rule
pub fn raster_polygon_fill_rule(
  points : Array[(Int, Int)],
  color : Color,
  rule : FillRule,
  setter : PixelSetter,
) -> Unit {
  raster_polygons_fill_rule([points], color, rule, setter)
}

///|
/// Fill multiple polygons (subpaths) with specified fill rule
pub fn raster_polygons_fill_rule(
  polygons : Array[Array[(Int, Int)]],
  color : Color,
  rule : FillRule,
  setter : PixelSetter,
) -> Unit {
  if polygons.length() == 0 {
    return
  }
  // Find bounding box across all polygons
  let mut has_point = false
  let mut min_y = 0
  let mut max_y = 0
  for poly in polygons {
    if poly.length() < 3 {
      continue
    }
    for i in 0.. max_y {
          max_y = y
        }
      }
    }
  }
  if not(has_point) {
    return
  }
  // Scanline fill
  for y = min_y; y <= max_y; y = y + 1 {
    // Find all intersections with polygon edges
    let intersections : Array[(Int, Int)] = []
    for poly in polygons {
      let n = poly.length()
      if n < 3 {
        continue
      }
      for i in 0.. {
        // Fill between pairs of intersections
        let mut i = 0
        while i + 1 < intersections.length() {
          let x_start = intersections[i].0
          let x_end = intersections[i + 1].0
          for x in x_start..<=x_end {
            setter.pixel(x, y, color)
          }
          i = i + 2
        }
      }
      NonZero => {
        let mut winding = 0
        let mut i = 0
        while i + 1 < intersections.length() {
          winding = winding + intersections[i].1
          if winding != 0 {
            let x_start = intersections[i].0
            let x_end = intersections[i + 1].0
            for x in x_start..<=x_end {
              setter.pixel(x, y, color)
            }
          }
          i = i + 1
        }
      }
    }
  }
}

///|
/// Draw polygon outline
pub fn raster_polygon_stroke(
  points : Array[(Int, Int)],
  color : Color,
  setter : PixelSetter,
) -> Unit {
  let n = points.length()
  if n < 2 {
    return
  }
  for i in 0.. Unit {
  let n = points.length()
  if n < 2 {
    return
  }
  for i = 0; i < n - 1; i = i + 1 {
    let (x1, y1) = points[i]
    let (x2, y2) = points[i + 1]
    raster_line(x1, y1, x2, y2, color, setter)
  }
}

///|
/// Helper: min of two ints
fn min_int(a : Int, b : Int) -> Int {
  if a < b {
    a
  } else {
    b
  }
}

///|
/// Helper: simple insertion sort for intersections by x
fn sort_intersections(arr : Array[(Int, Int)]) -> Unit {
  let n = arr.length()
  for i in 1..= 0 && arr[j].0 > key.0 {
      arr[j + 1] = arr[j]
      j = j - 1
    }
    arr[j + 1] = key
  }
}

///|
/// Draw a thick line (stroke width > 1)
pub fn raster_thick_line(
  x0 : Int,
  y0 : Int,
  x1 : Int,
  y1 : Int,
  width : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  if width <= 1 {
    raster_line(x0, y0, x1, y1, color, setter)
    return
  }
  // For thick lines, draw multiple parallel lines
  let half_w = width / 2
  let dx = x1 - x0
  let dy = y1 - y0
  let len = (dx * dx + dy * dy).to_double().sqrt()
  if len < 0.001 {
    // Point - draw filled circle
    raster_circle_fill(x0, y0, half_w, color, setter)
    return
  }
  // Perpendicular direction
  let px = (-dy.to_double() / len * half_w.to_double()).to_int()
  let py = (dx.to_double() / len * half_w.to_double()).to_int()
  // Draw as polygon
  let points : Array[(Int, Int)] = [
    (x0 + px, y0 + py),
    (x1 + px, y1 + py),
    (x1 - px, y1 - py),
    (x0 - px, y0 - py),
  ]
  raster_polygon_fill(points, color, setter)
}

///|
/// Simple 5x7 bitmap font for ASCII characters
/// Each character is represented as 7 bytes (rows), each byte has 5 bits (columns)
/// Bit 0x10 = leftmost pixel, 0x01 = rightmost pixel
fn get_char_bitmap(c : Char) -> Array[Int] {
  match c {
    ' ' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
    '!' => [0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04]
    '"' => [0x0A, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00]
    '#' => [0x0A, 0x1F, 0x0A, 0x0A, 0x1F, 0x0A, 0x00]
    '$' => [0x04, 0x0F, 0x14, 0x0E, 0x05, 0x1E, 0x04]
    '%' => [0x18, 0x19, 0x02, 0x04, 0x08, 0x13, 0x03]
    '&' => [0x08, 0x14, 0x14, 0x08, 0x15, 0x12, 0x0D]
    '\'' => [0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00]
    '(' => [0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02]
    ')' => [0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08]
    '*' => [0x04, 0x15, 0x0E, 0x1F, 0x0E, 0x15, 0x04]
    '+' => [0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00]
    ',' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08]
    '-' => [0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00]
    '.' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04]
    '/' => [0x01, 0x02, 0x02, 0x04, 0x08, 0x08, 0x10]
    '0' => [0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E]
    '1' => [0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E]
    '2' => [0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F]
    '3' => [0x0E, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0E]
    '4' => [0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02]
    '5' => [0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E]
    '6' => [0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E]
    '7' => [0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08]
    '8' => [0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E]
    '9' => [0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C]
    ':' => [0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00]
    ';' => [0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x08]
    '<' => [0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02]
    '=' => [0x00, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x00]
    '>' => [0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08]
    '?' => [0x0E, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04]
    '@' => [0x0E, 0x11, 0x17, 0x15, 0x17, 0x10, 0x0E]
    'A' => [0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11]
    'B' => [0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E]
    'C' => [0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E]
    'D' => [0x1E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1E]
    'E' => [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F]
    'F' => [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10]
    'G' => [0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0E]
    'H' => [0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11]
    'I' => [0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E]
    'J' => [0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C]
    'K' => [0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11]
    'L' => [0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F]
    'M' => [0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11]
    'N' => [0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11]
    'O' => [0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E]
    'P' => [0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10]
    'Q' => [0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D]
    'R' => [0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11]
    'S' => [0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E]
    'T' => [0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04]
    'U' => [0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E]
    'V' => [0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04]
    'W' => [0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11]
    'X' => [0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11]
    'Y' => [0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04]
    'Z' => [0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F]
    '[' => [0x0E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0E]
    '\\' => [0x10, 0x08, 0x08, 0x04, 0x02, 0x02, 0x01]
    ']' => [0x0E, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0E]
    '^' => [0x04, 0x0A, 0x11, 0x00, 0x00, 0x00, 0x00]
    '_' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F]
    '`' => [0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00]
    'a' => [0x00, 0x00, 0x0E, 0x01, 0x0F, 0x11, 0x0F]
    'b' => [0x10, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x1E]
    'c' => [0x00, 0x00, 0x0E, 0x10, 0x10, 0x10, 0x0E]
    'd' => [0x01, 0x01, 0x0F, 0x11, 0x11, 0x11, 0x0F]
    'e' => [0x00, 0x00, 0x0E, 0x11, 0x1F, 0x10, 0x0E]
    'f' => [0x06, 0x08, 0x08, 0x1E, 0x08, 0x08, 0x08]
    'g' => [0x00, 0x00, 0x0F, 0x11, 0x0F, 0x01, 0x0E]
    'h' => [0x10, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x11]
    'i' => [0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x0E]
    'j' => [0x02, 0x00, 0x06, 0x02, 0x02, 0x12, 0x0C]
    'k' => [0x10, 0x10, 0x12, 0x14, 0x18, 0x14, 0x12]
    'l' => [0x0C, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E]
    'm' => [0x00, 0x00, 0x1A, 0x15, 0x15, 0x11, 0x11]
    'n' => [0x00, 0x00, 0x1E, 0x11, 0x11, 0x11, 0x11]
    'o' => [0x00, 0x00, 0x0E, 0x11, 0x11, 0x11, 0x0E]
    'p' => [0x00, 0x00, 0x1E, 0x11, 0x1E, 0x10, 0x10]
    'q' => [0x00, 0x00, 0x0F, 0x11, 0x0F, 0x01, 0x01]
    'r' => [0x00, 0x00, 0x16, 0x19, 0x10, 0x10, 0x10]
    's' => [0x00, 0x00, 0x0E, 0x10, 0x0E, 0x01, 0x1E]
    't' => [0x08, 0x08, 0x1E, 0x08, 0x08, 0x09, 0x06]
    'u' => [0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0E]
    'v' => [0x00, 0x00, 0x11, 0x11, 0x11, 0x0A, 0x04]
    'w' => [0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0A]
    'x' => [0x00, 0x00, 0x11, 0x0A, 0x04, 0x0A, 0x11]
    'y' => [0x00, 0x00, 0x11, 0x11, 0x0F, 0x01, 0x0E]
    'z' => [0x00, 0x00, 0x1F, 0x02, 0x04, 0x08, 0x1F]
    '{' => [0x02, 0x04, 0x04, 0x08, 0x04, 0x04, 0x02]
    '|' => [0x04, 0x04, 0x04, 0x00, 0x04, 0x04, 0x04]
    '}' => [0x08, 0x04, 0x04, 0x02, 0x04, 0x04, 0x08]
    '~' => [0x00, 0x08, 0x15, 0x02, 0x00, 0x00, 0x00]
    _ => [0x1F, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1F] // Default: box
  }
}

///|
/// Render text using bitmap font
pub fn raster_text(
  x : Int,
  y : Int,
  text : String,
  font_size : Int,
  color : Color,
  setter : PixelSetter,
) -> Unit {
  // Base font is 5x7 pixels
  // Scale factor based on font_size (base is 7 pixels high)
  let scale = if font_size <= 7 { 1 } else { font_size / 7 }
  let char_width = 5 * scale
  let char_height = 7 * scale
  let spacing = scale // Space between characters
  let mut cursor_x = x
  // y is the baseline, so move up by font height
  let start_y = y - char_height
  for i in 0..> col
        if (row_bits & bit_mask) != 0 {
          // Draw scaled pixel
          for sy in 0..