///|
struct CanvasEdge {
  x0 : Double
  y0 : Double
  x1 : Double
  y1 : Double
} derive(Debug)

///|
pub extend CanvasEdge with Debug::{to_repr}

///|
struct StrokeEndpoint {
  x : Double
  y : Double
  dir_x : Double
  dir_y : Double
} derive(Debug)

///|
pub extend StrokeEndpoint with Debug::{to_repr}

///|
struct StrokeJoinPoint {
  x : Double
  y : Double
  in_x : Double
  in_y : Double
  out_x : Double
  out_y : Double
} derive(Debug)

///|
pub extend StrokeJoinPoint with Debug::{to_repr}

///|
struct StrokePathData {
  edges : Array[CanvasEdge]
  endpoints : Array[StrokeEndpoint]
  joins : Array[StrokeJoinPoint]
  subpath_starts : Array[Int]
} derive(Debug)

///|
pub extend StrokePathData with Debug::{to_repr}

///|
struct DashCursor {
  pattern : Array[Double]
  mut index : Int
  mut remaining : Double
  mut is_drawing : Bool
} derive(Debug)

///|
pub extend DashCursor with Debug::{to_repr}

///|
fn path_to_edges(path : Path, transform : Transform) -> Array[CanvasEdge] {
  let edges = []
  let mut current_x = 0.0
  let mut current_y = 0.0
  let mut start_x = 0.0
  let mut start_y = 0.0
  let mut has_current = false

  for segment in path.segments {
    match segment {
      MoveTo(x, y) => {
        close_current_fill_subpath(
          edges, transform, current_x, current_y, start_x, start_y, has_current,
        )
        current_x = x
        current_y = y
        start_x = x
        start_y = y
        has_current = true
      }
      LineTo(x, y) => {
        if has_current {
          push_transformed_edge(edges, transform, current_x, current_y, x, y)
        }
        current_x = x
        current_y = y
      }
      QuadTo(cx, cy, x, y) => {
        if has_current {
          flatten_quad(edges, transform, current_x, current_y, cx, cy, x, y)
        }
        current_x = x
        current_y = y
      }
      CubicTo(cx1, cy1, cx2, cy2, x, y) => {
        if has_current {
          flatten_cubic(
            edges, transform, current_x, current_y, cx1, cy1, cx2, cy2, x, y,
          )
        }
        current_x = x
        current_y = y
      }
      Close => {
        if has_current {
          push_transformed_edge(
            edges, transform, current_x, current_y, start_x, start_y,
          )
        }
        current_x = start_x
        current_y = start_y
      }
    }
  }

  close_current_fill_subpath(
    edges, transform, current_x, current_y, start_x, start_y, has_current,
  )
  edges
}

///|
fn close_current_fill_subpath(
  edges : Array[CanvasEdge],
  transform : Transform,
  current_x : Double,
  current_y : Double,
  start_x : Double,
  start_y : Double,
  has_current : Bool,
) -> Unit {
  if has_current && (current_x != start_x || current_y != start_y) {
    push_transformed_edge(
      edges, transform, current_x, current_y, start_x, start_y,
    )
  }
}

///|
fn path_to_stroke_path(path : Path, transform : Transform) -> StrokePathData {
  let edges = []
  let endpoints = []
  let joins = []
  let subpath_starts = []
  let mut current_x = 0.0
  let mut current_y = 0.0
  let mut start_x = 0.0
  let mut start_y = 0.0
  let mut has_current = false
  let mut subpath_start = 0
  let mut subpath_closed = false

  for segment in path.segments {
    match segment {
      MoveTo(x, y) => {
        if has_current && !subpath_closed {
          push_open_subpath_endpoints(edges, endpoints, subpath_start)
          push_open_subpath_joins(edges, joins, subpath_start)
        }
        current_x = x
        current_y = y
        start_x = x
        start_y = y
        has_current = true
        subpath_start = edges.length()
        push_subpath_start(subpath_starts, subpath_start)
        subpath_closed = false
      }
      LineTo(x, y) => {
        if has_current {
          if subpath_closed {
            subpath_start = edges.length()
            push_subpath_start(subpath_starts, subpath_start)
            subpath_closed = false
          }
          push_transformed_edge(edges, transform, current_x, current_y, x, y)
        }
        current_x = x
        current_y = y
      }
      QuadTo(cx, cy, x, y) => {
        if has_current {
          if subpath_closed {
            subpath_start = edges.length()
            push_subpath_start(subpath_starts, subpath_start)
            subpath_closed = false
          }
          flatten_quad(edges, transform, current_x, current_y, cx, cy, x, y)
        }
        current_x = x
        current_y = y
      }
      CubicTo(cx1, cy1, cx2, cy2, x, y) => {
        if has_current {
          if subpath_closed {
            subpath_start = edges.length()
            push_subpath_start(subpath_starts, subpath_start)
            subpath_closed = false
          }
          flatten_cubic(
            edges, transform, current_x, current_y, cx1, cy1, cx2, cy2, x, y,
          )
        }
        current_x = x
        current_y = y
      }
      Close => {
        if has_current {
          push_transformed_edge(
            edges, transform, current_x, current_y, start_x, start_y,
          )
          push_closed_subpath_joins(edges, joins, subpath_start)
        }
        current_x = start_x
        current_y = start_y
        subpath_closed = true
      }
    }
  }

  if has_current && !subpath_closed {
    push_open_subpath_endpoints(edges, endpoints, subpath_start)
    push_open_subpath_joins(edges, joins, subpath_start)
  }

  { edges, endpoints, joins, subpath_starts, }
}

///|
fn push_subpath_start(starts : Array[Int], start : Int) -> Unit {
  if starts.length() == 0 || starts[starts.length() - 1] != start {
    starts.push(start)
  }
}

///|
fn StrokePathData::is_subpath_start(
  self : StrokePathData,
  edge_index : Int,
) -> Bool {
  for start in self.subpath_starts {
    if start == edge_index {
      return true
    }
    if start > edge_index {
      return false
    }
  }
  false
}

///|
fn apply_stroke_dash(
  stroke_path : StrokePathData,
  pattern : Array[Double],
  offset : Double,
) -> StrokePathData {
  guard pattern.length() > 0 else { return stroke_path }

  let edges = []
  let endpoints = []
  let joins = []
  let mut cursor = DashCursor::new(pattern, offset)
  let mut dash_run_start = -1
  let mut has_previous_source_edge = false
  let mut previous_source_edge = CanvasEdge::{
    x0: 0.0,
    y0: 0.0,
    x1: 0.0,
    y1: 0.0,
  }

  for i in 0.. 0 && stroke_path.is_subpath_start(i) {
      finish_dash_run(edges, endpoints, joins, dash_run_start)
      dash_run_start = -1
      cursor = DashCursor::new(pattern, offset)
    } else if has_previous_source_edge &&
      !close_point(
        previous_source_edge.x1,
        previous_source_edge.y1,
        edge.x0,
        edge.y0,
      ) {
      finish_dash_run(edges, endpoints, joins, dash_run_start)
      dash_run_start = -1
      cursor = DashCursor::new(pattern, offset)
    }
    dash_run_start = append_dashed_edge(
      edges, endpoints, joins, edge, cursor, dash_run_start,
    )
    previous_source_edge = edge
    has_previous_source_edge = true
  }
  finish_dash_run(edges, endpoints, joins, dash_run_start)
  { edges, endpoints, joins, subpath_starts: [], }
}

///|
fn append_dashed_edge(
  edges : Array[CanvasEdge],
  endpoints : Array[StrokeEndpoint],
  joins : Array[StrokeJoinPoint],
  edge : CanvasEdge,
  cursor : DashCursor,
  dash_run_start : Int,
) -> Int {
  let dx = edge.x1 - edge.x0
  let dy = edge.y1 - edge.y0
  let length = (dx * dx + dy * dy).sqrt()
  guard length > 0.0 else { return dash_run_start }
  let mut current_run_start = dash_run_start
  let mut distance = 0.0
  while distance < length {
    if !cursor.is_drawing && current_run_start >= 0 {
      finish_dash_run(edges, endpoints, joins, current_run_start)
      current_run_start = -1
    }
    let remaining_edge = length - distance
    let step = remaining_edge.min(cursor.remaining)
    if cursor.is_drawing && step > 0.0 {
      let t0 = distance / length
      let t1 = (distance + step) / length
      if current_run_start < 0 {
        current_run_start = edges.length()
      }
      edges.push({
        x0: edge.x0 + dx * t0,
        y0: edge.y0 + dy * t0,
        x1: edge.x0 + dx * t1,
        y1: edge.y0 + dy * t1,
      })
    }
    distance = distance + step
    cursor.advance(step)
  }
  current_run_start
}

///|
fn finish_dash_run(
  edges : Array[CanvasEdge],
  endpoints : Array[StrokeEndpoint],
  joins : Array[StrokeJoinPoint],
  start_index : Int,
) -> Unit {
  guard start_index >= 0 && start_index < edges.length() else { return }
  push_open_subpath_endpoints(edges, endpoints, start_index)
  push_open_subpath_joins(edges, joins, start_index)
}

///|
fn DashCursor::new(pattern : Array[Double], offset : Double) -> DashCursor {
  let total = dash_pattern_total(pattern)
  let mut phase = normalize_dash_offset(offset, total)
  let mut index = 0
  let mut remaining = pattern[0]
  while phase >= remaining && pattern.length() > 0 {
    phase = phase - remaining
    index = (index + 1) % pattern.length()
    remaining = pattern[index]
  }
  { pattern, index, remaining: remaining - phase, is_drawing: index % 2 == 0, }
}

///|
fn DashCursor::advance(self : DashCursor, distance : Double) -> Unit {
  guard self.pattern.length() > 0 else { return }
  self.remaining = self.remaining - distance
  while self.remaining <= 0.000001 {
    self.index = (self.index + 1) % self.pattern.length()
    self.remaining = self.remaining + self.pattern[self.index]
    self.is_drawing = self.index % 2 == 0
  }
}

///|
fn dash_pattern_total(pattern : Array[Double]) -> Double {
  let mut total = 0.0
  for value in pattern {
    total = total + value
  }
  total
}

///|
fn normalize_dash_offset(offset : Double, total : Double) -> Double {
  guard total > 0.0 else { return 0.0 }
  let cycles = (offset / total).to_int()
  let mut phase = offset - cycles.to_double() * total
  while phase < 0.0 {
    phase = phase + total
  }
  while phase >= total {
    phase = phase - total
  }
  phase
}

///|
fn clipped_int_bounds(
  canvas : Canvas,
  x_start : Int,
  y_start : Int,
  x_end : Int,
  y_end : Int,
) -> (Int, Int, Int, Int) {
  let mut left = x_start.max(0)
  let mut top = y_start.max(0)
  let mut right = x_end.min(canvas.pixmap.get_width())
  let mut bottom = y_end.min(canvas.pixmap.get_height())
  match canvas.clip_rect {
    Some(rect) => {
      left = left.max(rect.x.to_int())
      top = top.max(rect.y.to_int())
      right = right.min(rect.right().to_int())
      bottom = bottom.min(rect.bottom().to_int())
    }
    None => ()
  }
  (left, top, right, bottom)
}

///|
fn is_pixel_in_clip(canvas : Canvas, x : Int, y : Int) -> Bool {
  guard x >= 0 &&
    x < canvas.pixmap.get_width() &&
    y >= 0 &&
    y < canvas.pixmap.get_height() else {
    return false
  }
  match canvas.clip_rect {
    Some(rect) => rect.contains(x.to_double() + 0.5, y.to_double() + 0.5)
    None => true
  }
}

///|
fn Canvas::mark_dirty_bounds(
  self : Canvas,
  x_start : Int,
  y_start : Int,
  x_end : Int,
  y_end : Int,
) -> Unit {
  self.dirty_region.add_rect(
    PixelRect::new(
      x=x_start,
      y=y_start,
      width=x_end - x_start,
      height=y_end - y_start,
    ),
  )
}

///|
fn blend_clipped_rect(
  canvas : Canvas,
  x : Int,
  y : Int,
  w : Int,
  h : Int,
  color : Color,
) -> Unit {
  let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
    canvas,
    x,
    y,
    x + w,
    y + h,
  )
  guard x_start < x_end && y_start < y_end else { return }
  canvas.pixmap.blend_rect_with_mode(
    x_start,
    y_start,
    x_end - x_start,
    y_end - y_start,
    color,
    SourceOver,
  )
  canvas.mark_dirty_bounds(x_start, y_start, x_end, y_end)
}

///|
fn push_open_subpath_endpoints(
  edges : Array[CanvasEdge],
  endpoints : Array[StrokeEndpoint],
  start_index : Int,
) -> Unit {
  guard start_index < edges.length() else { return }
  let first = edges[start_index]
  let last = edges[edges.length() - 1]
  push_endpoint(
    endpoints,
    first.x0,
    first.y0,
    first.x0 - first.x1,
    first.y0 - first.y1,
  )
  push_endpoint(
    endpoints,
    last.x1,
    last.y1,
    last.x1 - last.x0,
    last.y1 - last.y0,
  )
}

///|
fn push_open_subpath_joins(
  edges : Array[CanvasEdge],
  joins : Array[StrokeJoinPoint],
  start_index : Int,
) -> Unit {
  guard start_index + 1 < edges.length() else { return }
  for i in (start_index + 1).. Unit {
  guard start_index < edges.length() else { return }
  push_open_subpath_joins(edges, joins, start_index)
  push_join_if_connected(joins, edges[edges.length() - 1], edges[start_index])
}

///|
fn push_join_if_connected(
  joins : Array[StrokeJoinPoint],
  prev : CanvasEdge,
  next : CanvasEdge,
) -> Unit {
  guard close_point(prev.x1, prev.y1, next.x0, next.y0) else { return }
  let in_x = prev.x1 - prev.x0
  let in_y = prev.y1 - prev.y0
  let out_x = next.x1 - next.x0
  let out_y = next.y1 - next.y0
  let in_len = (in_x * in_x + in_y * in_y).sqrt()
  let out_len = (out_x * out_x + out_y * out_y).sqrt()
  guard in_len > 0.0 && out_len > 0.0 else { return }
  joins.push({
    x: prev.x1,
    y: prev.y1,
    in_x: in_x / in_len,
    in_y: in_y / in_len,
    out_x: out_x / out_len,
    out_y: out_y / out_len,
  })
}

///|
fn close_point(x0 : Double, y0 : Double, x1 : Double, y1 : Double) -> Bool {
  (x0 - x1).abs() < 0.000001 && (y0 - y1).abs() < 0.000001
}

///|
fn push_endpoint(
  endpoints : Array[StrokeEndpoint],
  x : Double,
  y : Double,
  dir_x : Double,
  dir_y : Double,
) -> Unit {
  let len = (dir_x * dir_x + dir_y * dir_y).sqrt()
  guard len > 0.0 else { return }
  endpoints.push({ x, y, dir_x: dir_x / len, dir_y: dir_y / len, })
}

///|
fn push_transformed_edge(
  edges : Array[CanvasEdge],
  transform : Transform,
  x0 : Double,
  y0 : Double,
  x1 : Double,
  y1 : Double,
) -> Unit {
  let (tx0, ty0) = transform.apply(x0, y0)
  let (tx1, ty1) = transform.apply(x1, y1)
  if tx0 != tx1 || ty0 != ty1 {
    edges.push({ x0: tx0, y0: ty0, x1: tx1, y1: ty1, })
  }
}

///|
fn flatten_quad(
  edges : Array[CanvasEdge],
  transform : Transform,
  x0 : Double,
  y0 : Double,
  cx : Double,
  cy : Double,
  x1 : Double,
  y1 : Double,
) -> Unit {
  let steps = 16
  let mut prev_x = x0
  let mut prev_y = y0
  for i in 1..<=steps {
    let t = i.to_double() / steps.to_double()
    let mt = 1.0 - t
    let x = mt * mt * x0 + 2.0 * mt * t * cx + t * t * x1
    let y = mt * mt * y0 + 2.0 * mt * t * cy + t * t * y1
    push_transformed_edge(edges, transform, prev_x, prev_y, x, y)
    prev_x = x
    prev_y = y
  }
}

///|
fn flatten_cubic(
  edges : Array[CanvasEdge],
  transform : Transform,
  x0 : Double,
  y0 : Double,
  cx1 : Double,
  cy1 : Double,
  cx2 : Double,
  cy2 : Double,
  x1 : Double,
  y1 : Double,
) -> Unit {
  let steps = 24
  let mut prev_x = x0
  let mut prev_y = y0
  for i in 1..<=steps {
    let t = i.to_double() / steps.to_double()
    let mt = 1.0 - t
    let x = mt * mt * mt * x0 +
      3.0 * mt * mt * t * cx1 +
      3.0 * mt * t * t * cx2 +
      t * t * t * x1
    let y = mt * mt * mt * y0 +
      3.0 * mt * mt * t * cy1 +
      3.0 * mt * t * t * cy2 +
      t * t * t * y1
    push_transformed_edge(edges, transform, prev_x, prev_y, x, y)
    prev_x = x
    prev_y = y
  }
}

///|
fn path_bounds(edges : Array[CanvasEdge]) -> (Double, Double, Double, Double) {
  let first = edges[0]
  let mut min_x_value = first.x0.min(first.x1)
  let mut min_y_value = first.y0.min(first.y1)
  let mut max_x_value = first.x0.max(first.x1)
  let mut max_y_value = first.y0.max(first.y1)
  for edge in edges {
    min_x_value = min_x_value.min(edge.x0).min(edge.x1)
    min_y_value = min_y_value.min(edge.y0).min(edge.y1)
    max_x_value = max_x_value.max(edge.x0).max(edge.x1)
    max_y_value = max_y_value.max(edge.y0).max(edge.y1)
  }
  (min_x_value, min_y_value, max_x_value, max_y_value)
}

///|
fn is_point_in_edges(
  px : Double,
  py : Double,
  edges : Array[CanvasEdge],
  fill_rule : FillRule,
) -> Bool {
  match fill_rule {
    EvenOdd => is_point_in_edges_even_odd(px, py, edges)
    NonZero => is_point_in_edges_non_zero(px, py, edges)
  }
}

///|
fn is_point_in_edges_even_odd(
  px : Double,
  py : Double,
  edges : Array[CanvasEdge],
) -> Bool {
  let mut inside = false
  for edge in edges {
    if edge_crosses_ray(edge, py) {
      let x = edge_intersection_x(edge, py)
      if x > px {
        inside = !inside
      }
    }
  }
  inside
}

///|
fn is_point_in_edges_non_zero(
  px : Double,
  py : Double,
  edges : Array[CanvasEdge],
) -> Bool {
  let mut winding = 0
  for edge in edges {
    if edge_crosses_ray(edge, py) {
      let x = edge_intersection_x(edge, py)
      if x > px {
        if edge.y1 > edge.y0 {
          winding = winding + 1
        } else {
          winding = winding - 1
        }
      }
    }
  }
  winding != 0
}

///|
fn edge_crosses_ray(edge : CanvasEdge, py : Double) -> Bool {
  (edge.y0 <= py && edge.y1 > py) || (edge.y1 <= py && edge.y0 > py)
}

///|
fn edge_intersection_x(edge : CanvasEdge, py : Double) -> Double {
  edge.x0 + (py - edge.y0) * (edge.x1 - edge.x0) / (edge.y1 - edge.y0)
}

///|
fn is_point_on_stroke(
  px : Double,
  py : Double,
  stroke_path : StrokePathData,
  half_width : Double,
  stroke_cap : StrokeCap,
  stroke_join : StrokeJoin,
  miter_limit : Double,
) -> Bool {
  let max_distance_sq = half_width * half_width
  for edge in stroke_path.edges {
    if distance_to_edge_sq(px, py, edge) <= max_distance_sq {
      return true
    }
  }
  if is_point_on_stroke_join(
      px,
      py,
      stroke_path.joins,
      half_width,
      stroke_join,
      miter_limit,
    ) {
    return true
  }
  is_point_on_stroke_cap(px, py, stroke_path.endpoints, half_width, stroke_cap)
}

///|
fn is_point_on_stroke_join(
  px : Double,
  py : Double,
  joins : Array[StrokeJoinPoint],
  half_width : Double,
  stroke_join : StrokeJoin,
  miter_limit : Double,
) -> Bool {
  match stroke_join {
    Round => {
      let max_distance_sq = half_width * half_width
      for join in joins {
        let dx = px - join.x
        let dy = py - join.y
        if is_point_in_join_sector(dx, dy, join) &&
          dx * dx + dy * dy <= max_distance_sq {
          return true
        }
      }
      false
    }
    Bevel => {
      for join in joins {
        if is_point_in_join_wedge(px, py, join, half_width) {
          return true
        }
      }
      false
    }
    Miter => {
      for join in joins {
        if is_point_in_miter_join(px, py, join, half_width, miter_limit) {
          return true
        }
      }
      false
    }
  }
}

///|
fn is_point_in_miter_join(
  px : Double,
  py : Double,
  join : StrokeJoinPoint,
  half_width : Double,
  miter_limit : Double,
) -> Bool {
  guard is_miter_join_within_limit(join, miter_limit) else {
    return is_point_in_join_wedge(px, py, join, half_width)
  }
  let dx = px - join.x
  let dy = py - join.y
  is_point_in_join_sector(dx, dy, join) &&
  cross2(join.in_x, join.in_y, dx, dy).abs() <= half_width &&
  cross2(join.out_x, join.out_y, dx, dy).abs() <= half_width
}

///|
fn is_miter_join_within_limit(
  join : StrokeJoinPoint,
  miter_limit : Double,
) -> Bool {
  let bx = -join.out_x
  let by = -join.out_y
  guard cross2(join.in_x, join.in_y, bx, by).abs() > 0.000001 else {
    return false
  }
  let dot = (join.in_x * bx + join.in_y * by).max(-1.0).min(1.0)
  let sin_half_sq = (1.0 - dot) / 2.0
  guard sin_half_sq > 0.0 else { return false }
  let ratio = 1.0 / sin_half_sq.sqrt()
  ratio <= miter_limit.max(1.0)
}

///|
fn is_point_in_join_wedge(
  px : Double,
  py : Double,
  join : StrokeJoinPoint,
  half_width : Double,
) -> Bool {
  let dx = px - join.x
  let dy = py - join.y
  let bx = -join.out_x
  let by = -join.out_y
  let denom = cross2(join.in_x, join.in_y, bx, by)
  guard denom.abs() > 0.000001 else { return false }
  let s = cross2(dx, dy, bx, by) / denom
  let t = cross2(join.in_x, join.in_y, dx, dy) / denom
  s >= 0.0 && t >= 0.0 && s + t <= half_width
}

///|
fn is_point_in_join_sector(
  dx : Double,
  dy : Double,
  join : StrokeJoinPoint,
) -> Bool {
  let bx = -join.out_x
  let by = -join.out_y
  let cross = cross2(join.in_x, join.in_y, bx, by)
  guard cross.abs() > 0.000001 else { return false }
  let side = if cross > 0.0 { 1.0 } else { -1.0 }
  side * cross2(join.in_x, join.in_y, dx, dy) >= 0.0 &&
  side * cross2(dx, dy, bx, by) >= 0.0
}

///|
fn cross2(ax : Double, ay : Double, bx : Double, by : Double) -> Double {
  ax * by - ay * bx
}

///|
fn is_point_on_stroke_cap(
  px : Double,
  py : Double,
  endpoints : Array[StrokeEndpoint],
  half_width : Double,
  stroke_cap : StrokeCap,
) -> Bool {
  match stroke_cap {
    Butt => false
    Round => {
      let max_distance_sq = half_width * half_width
      for endpoint in endpoints {
        let dx = px - endpoint.x
        let dy = py - endpoint.y
        let outward = dx * endpoint.dir_x + dy * endpoint.dir_y
        if outward >= 0.0 && dx * dx + dy * dy <= max_distance_sq {
          return true
        }
      }
      false
    }
    Square => {
      for endpoint in endpoints {
        let dx = px - endpoint.x
        let dy = py - endpoint.y
        let outward = dx * endpoint.dir_x + dy * endpoint.dir_y
        let sideways = dx * -endpoint.dir_y + dy * endpoint.dir_x
        if outward >= 0.0 &&
          outward <= half_width &&
          sideways.abs() <= half_width {
          return true
        }
      }
      false
    }
  }
}

///|
fn distance_to_edge_sq(px : Double, py : Double, edge : CanvasEdge) -> Double {
  let dx = edge.x1 - edge.x0
  let dy = edge.y1 - edge.y0
  let length_sq = dx * dx + dy * dy
  guard length_sq > 0.0 else {
    let point_dx = px - edge.x0
    let point_dy = py - edge.y0
    return point_dx * point_dx + point_dy * point_dy
  }
  let projected = ((px - edge.x0) * dx + (py - edge.y0) * dy) / length_sq
  guard projected >= 0.0 && projected <= 1.0 else { return half_infinite }
  let t = projected
  let closest_x = edge.x0 + t * dx
  let closest_y = edge.y0 + t * dy
  point_distance_sq(px, py, closest_x, closest_y)
}

///|
fn point_distance_sq(
  px : Double,
  py : Double,
  x : Double,
  y : Double,
) -> Double {
  let point_dx = px - x
  let point_dy = py - y
  point_dx * point_dx + point_dy * point_dy
}

///|
let half_infinite = 1.0e100

///|
fn blend_coverage_pixel(
  pixmap : Pixmap,
  x : Int,
  y : Int,
  color : Color,
  covered : Int,
  sample_count : Int,
  blend_mode : BlendMode,
) -> Bool {
  let coverage = coverage_to_byte(covered, sample_count).to_int()
  let alpha = (color.a.to_int() * coverage + 127) / 255
  blend_sampled_source_pixel(
    pixmap,
    x,
    y,
    color.with_alpha(clamp_byte(alpha)),
    blend_mode,
  )
}

///|
fn coverage_to_byte(covered : Int, sample_count : Int) -> Byte {
  guard sample_count > 0 else { return 0 }
  clamp_byte((covered * 255 + sample_count / 2) / sample_count)
}