///|
/// Draw a filled rectangle
pub fn Canvas::fill_rect(
  self : Canvas,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
  paint : Paint,
) -> Unit {
  guard paint.style is Fill else { return }
  guard w > 0.0 && h > 0.0 else { return }

  if self.transform != Transform::identity() ||
    !is_pixel_aligned_rect(x, y, w, h) {
    self.fill_path(Path::new().add_rect(x, y, w, h), paint)
    return
  }

  let ix = x.to_int()
  let iy = y.to_int()
  let iw = w.to_int()
  let ih = h.to_int()
  let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
    self,
    ix,
    iy,
    ix + iw,
    iy + ih,
  )
  guard x_start < x_end && y_start < y_end else { return }

  if paint.has_shader() {
    let mut dirty_x_start = x_end
    let mut dirty_y_start = y_end
    let mut dirty_x_end = x_start
    let mut dirty_y_end = y_start
    for py in y_start.. Bool {
  is_pixel_aligned_value(x) &&
  is_pixel_aligned_value(y) &&
  is_pixel_aligned_value(w) &&
  is_pixel_aligned_value(h)
}

///|
fn is_pixel_aligned_value(value : Double) -> Bool {
  value == value.to_int().to_double()
}

///|
/// Draw a filled rounded rectangle.
pub fn Canvas::fill_rounded_rect(
  self : Canvas,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
  radius : Double,
  paint : Paint,
) -> Unit {
  guard w > 0.0 && h > 0.0 else { return }
  self.fill_path(Path::new().add_rounded_rect(x, y, w, h, radius), paint)
}

///|
/// Draw a filled rounded rectangle with independent corner radii.
pub fn Canvas::fill_rounded_rect_corners(
  self : Canvas,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
  top_left : Double,
  top_right : Double,
  bottom_right : Double,
  bottom_left : Double,
  paint : Paint,
) -> Unit {
  guard w > 0.0 && h > 0.0 else { return }
  self.fill_path(
    Path::new().add_rounded_rect_corners(
      x, y, w, h, top_left, top_right, bottom_right, bottom_left,
    ),
    paint,
  )
}

///|
/// Draw a filled circle.
pub fn Canvas::fill_circle(
  self : Canvas,
  cx : Double,
  cy : Double,
  radius : Double,
  paint : Paint,
) -> Unit {
  guard radius > 0.0 else { return }
  self.fill_path(Path::new().add_circle(cx, cy, radius), paint)
}

///|
/// Draw a filled ellipse.
pub fn Canvas::fill_ellipse(
  self : Canvas,
  cx : Double,
  cy : Double,
  rx : Double,
  ry : Double,
  paint : Paint,
) -> Unit {
  guard rx > 0.0 && ry > 0.0 else { return }
  self.fill_path(Path::new().add_ellipse(cx, cy, rx, ry), paint)
}

///|
/// Draw a filled polygon.
pub fn Canvas::fill_polygon(
  self : Canvas,
  points : Array[Point],
  paint : Paint,
) -> Unit {
  guard points.length() >= 3 else { return }
  self.fill_path(Path::new().add_polygon(points), paint)
}

///|
/// Draw a filled pie slice.
pub fn Canvas::fill_pie(
  self : Canvas,
  cx : Double,
  cy : Double,
  radius : Double,
  start_angle : Double,
  sweep_angle : Double,
  paint : Paint,
) -> Unit {
  guard radius > 0.0 && sweep_angle != 0.0 else { return }
  self.fill_path(
    Path::new().add_pie(cx, cy, radius, start_angle, sweep_angle),
    paint,
  )
}

///|
/// Draw a stroked rectangle.
pub fn Canvas::stroke_rect(
  self : Canvas,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
  paint : Paint,
) -> Unit {
  guard w > 0.0 && h > 0.0 else { return }
  self.stroke_path(Path::new().add_rect(x, y, w, h), paint)
}

///|
/// Draw a stroked line segment.
pub fn Canvas::stroke_line(
  self : Canvas,
  x0 : Double,
  y0 : Double,
  x1 : Double,
  y1 : Double,
  paint : Paint,
) -> Unit {
  self.stroke_path(Path::new().move_to(x0, y0).line_to(x1, y1), paint)
}

///|
/// Draw a stroked open polyline.
pub fn Canvas::stroke_polyline(
  self : Canvas,
  points : Array[Point],
  paint : Paint,
) -> Unit {
  guard points.length() >= 2 else { return }
  self.stroke_path(Path::new().add_polyline(points), paint)
}

///|
/// Draw a stroked closed polygon.
pub fn Canvas::stroke_polygon(
  self : Canvas,
  points : Array[Point],
  paint : Paint,
) -> Unit {
  guard points.length() >= 3 else { return }
  self.stroke_path(Path::new().add_polygon(points), paint)
}

///|
/// Draw a stroked circular arc.
pub fn Canvas::stroke_arc(
  self : Canvas,
  cx : Double,
  cy : Double,
  radius : Double,
  start_angle : Double,
  sweep_angle : Double,
  paint : Paint,
) -> Unit {
  guard radius > 0.0 && sweep_angle != 0.0 else { return }
  self.stroke_path(
    Path::new().add_arc(cx, cy, radius, start_angle, sweep_angle),
    paint,
  )
}

///|
/// Draw a stroked circle.
pub fn Canvas::stroke_circle(
  self : Canvas,
  cx : Double,
  cy : Double,
  radius : Double,
  paint : Paint,
) -> Unit {
  guard radius > 0.0 else { return }
  self.stroke_path(Path::new().add_circle(cx, cy, radius), paint)
}

///|
/// Draw a stroked ellipse.
pub fn Canvas::stroke_ellipse(
  self : Canvas,
  cx : Double,
  cy : Double,
  rx : Double,
  ry : Double,
  paint : Paint,
) -> Unit {
  guard rx > 0.0 && ry > 0.0 else { return }
  self.stroke_path(Path::new().add_ellipse(cx, cy, rx, ry), paint)
}

///|
/// Fill a path with the given paint
pub fn Canvas::fill_path(self : Canvas, path : Path, paint : Paint) -> Unit {
  guard paint.style is Fill && !path.is_empty() else { return }
  let edges = path_to_edges(path, self.transform)
  guard edges.length() > 0 else { return }

  let (min_x_value, min_y_value, max_x_value, max_y_value) = path_bounds(edges)
  let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
    self,
    min_x_value.to_int(),
    min_y_value.to_int(),
    max_x_value.to_int() + 1,
    max_y_value.to_int() + 1,
  )
  guard x_start < x_end && y_start < y_end else { return }
  let sample_count = 4
  let sample_count_sq = sample_count * sample_count
  let sample_count_double = sample_count.to_double()

  for y in y_start.. 0 {
        let did_blend = blend_coverage_pixel(
          self.pixmap,
          x,
          y,
          paint.sample_color(x.to_double() + 0.5, y.to_double() + 0.5),
          covered,
          sample_count_sq,
          paint.blend_mode,
        )
        if did_blend {
          self.mark_dirty_bounds(x, y, x + 1, y + 1)
        }
      }
    }
  }
}

///|
/// Draw a stroked rounded rectangle.
pub fn Canvas::stroke_rounded_rect(
  self : Canvas,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
  radius : Double,
  paint : Paint,
) -> Unit {
  guard w > 0.0 && h > 0.0 else { return }
  self.stroke_path(Path::new().add_rounded_rect(x, y, w, h, radius), paint)
}

///|
/// Draw a stroked rounded rectangle with independent corner radii.
pub fn Canvas::stroke_rounded_rect_corners(
  self : Canvas,
  x : Double,
  y : Double,
  w : Double,
  h : Double,
  top_left : Double,
  top_right : Double,
  bottom_right : Double,
  bottom_left : Double,
  paint : Paint,
) -> Unit {
  guard w > 0.0 && h > 0.0 else { return }
  self.stroke_path(
    Path::new().add_rounded_rect_corners(
      x, y, w, h, top_left, top_right, bottom_right, bottom_left,
    ),
    paint,
  )
}

///|
/// Stroke a path with the given paint
pub fn Canvas::stroke_path(self : Canvas, path : Path, paint : Paint) -> Unit {
  guard paint.style is Stroke && !path.is_empty() else { return }
  guard paint.stroke_width > 0.0 else { return }
  match self.transform.inverse() {
    Some(device_to_local) =>
      self.stroke_path_with_local_metrics(path, paint, device_to_local)
    None => self.stroke_path_with_device_metrics(path, paint)
  }
}

///|
fn Canvas::stroke_path_with_local_metrics(
  self : Canvas,
  path : Path,
  paint : Paint,
  device_to_local : Transform,
) -> Unit {
  let solid_stroke_path = path_to_stroke_path(path, Transform::identity())
  guard solid_stroke_path.edges.length() > 0 else { return }
  let stroke_path = apply_stroke_dash(
    solid_stroke_path,
    paint.stroke_dash_pattern,
    paint.stroke_dash_offset,
  )
  guard stroke_path.edges.length() > 0 else { return }

  let half_width = paint.stroke_width / 2.0
  let bounds_outset = stroke_bounds_outset(
    half_width,
    paint.stroke_cap,
    paint.stroke_join,
    paint.miter_limit,
  )
  let (min_x_value, min_y_value, max_x_value, max_y_value) = path_bounds(
    stroke_path.edges,
  )
  let (left, top, right, bottom) = transformed_rect_bounds(
    self.transform,
    min_x_value - bounds_outset,
    min_y_value - bounds_outset,
    max_x_value - min_x_value + bounds_outset * 2.0,
    max_y_value - min_y_value + bounds_outset * 2.0,
  )
  let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
    self, left, top, right, bottom,
  )
  guard x_start < x_end && y_start < y_end else { return }
  let sample_count = 4
  let sample_count_sq = sample_count * sample_count
  let sample_count_double = sample_count.to_double()

  for y in y_start.. 0 {
        let did_blend = blend_coverage_pixel(
          self.pixmap,
          x,
          y,
          paint.sample_color(x.to_double() + 0.5, y.to_double() + 0.5),
          covered,
          sample_count_sq,
          paint.blend_mode,
        )
        if did_blend {
          self.mark_dirty_bounds(x, y, x + 1, y + 1)
        }
      }
    }
  }
}

///|
fn Canvas::stroke_path_with_device_metrics(
  self : Canvas,
  path : Path,
  paint : Paint,
) -> Unit {
  let solid_stroke_path = path_to_stroke_path(path, self.transform)
  guard solid_stroke_path.edges.length() > 0 else { return }
  let stroke_scale = stroke_metric_scale(self.transform)
  let stroke_path = apply_stroke_dash(
    solid_stroke_path,
    scaled_stroke_dash_pattern(paint.stroke_dash_pattern, stroke_scale),
    paint.stroke_dash_offset * stroke_scale,
  )
  guard stroke_path.edges.length() > 0 else { return }

  let half_width = paint.stroke_width * stroke_scale / 2.0
  let bounds_outset = stroke_bounds_outset(
    half_width,
    paint.stroke_cap,
    paint.stroke_join,
    paint.miter_limit,
  )
  let (min_x_value, min_y_value, max_x_value, max_y_value) = path_bounds(
    stroke_path.edges,
  )
  let (x_start, y_start, x_end, y_end) = clipped_int_bounds(
    self,
    (min_x_value - bounds_outset).to_int(),
    (min_y_value - bounds_outset).to_int(),
    (max_x_value + bounds_outset).to_int() + 1,
    (max_y_value + bounds_outset).to_int() + 1,
  )
  guard x_start < x_end && y_start < y_end else { return }
  let sample_count = 4
  let sample_count_sq = sample_count * sample_count
  let sample_count_double = sample_count.to_double()

  for y in y_start.. 0 {
        let did_blend = blend_coverage_pixel(
          self.pixmap,
          x,
          y,
          paint.sample_color(x.to_double() + 0.5, y.to_double() + 0.5),
          covered,
          sample_count_sq,
          paint.blend_mode,
        )
        if did_blend {
          self.mark_dirty_bounds(x, y, x + 1, y + 1)
        }
      }
    }
  }
}

///|
fn stroke_bounds_outset(
  half_width : Double,
  stroke_cap : StrokeCap,
  stroke_join : StrokeJoin,
  miter_limit : Double,
) -> Double {
  let cap_extension = match stroke_cap {
    Butt => 0.0
    Round | Square => half_width
  }
  let join_extension = match stroke_join {
    Miter => half_width * (miter_limit - 1.0).max(0.0)
    Round | Bevel => 0.0
  }
  half_width + cap_extension.max(join_extension)
}

///|
fn stroke_metric_scale(transform : Transform) -> Double {
  let x_scale = (transform.a * transform.a + transform.c * transform.c).sqrt()
  let y_scale = (transform.b * transform.b + transform.d * transform.d).sqrt()
  let scale = x_scale.max(y_scale)
  guard scale == scale && scale > 0.0 else { return 1.0 }
  scale
}

///|
fn scaled_stroke_dash_pattern(
  pattern : Array[Double],
  scale : Double,
) -> Array[Double] {
  guard pattern.length() > 0 && scale != 1.0 else { return pattern }
  let scaled = []
  for value in pattern {
    scaled.push(value * scale)
  }
  scaled
}