///|
/// Bezier control-line factor used by cpdf for circular arcs.
pub let pdf_shape_kappa : Double = (2.0.sqrt() - 1.0) / 3.0 * 4.0

///|
/// Source-spelled compatibility alias for cpdfshape's `kappa`.
pub let pdf_kappa : Double = pdf_shape_kappa

///|
fn pdf_shape_mod_float(value : Double, modulus : Double) -> Double {
  value - (value / modulus).floor() * modulus
}

///|
/// Restrict an angle to the closest multiple of `step`, modulo `2 * pi`.
pub fn pdf_restrict_angle(step : Double, angle : Double) -> Double {
  let full = 2.0 * @math.PI
  let lower = pdf_shape_mod_float((angle / step).floor() * step, full)
  let upper = pdf_shape_mod_float(lower + step, full)
  if (lower - angle).abs() < (upper - angle).abs() {
    lower
  } else {
    upper
  }
}

///|
fn pdf_shape_transform_point(
  center_x : Double,
  center_y : Double,
  radius : Double,
  angle : Double,
  point : Point2,
) -> Point2 {
  let x = point.x * radius
  let y = point.y * radius
  @geometry.point2(
    center_x + x * @math.cos(angle) - y * @math.sin(angle),
    center_y + x * @math.sin(angle) + y * @math.cos(angle),
  )
}

///|
fn pdf_shape_quarter(
  angle : Double,
  center_x : Double,
  center_y : Double,
  radius : Double,
) -> PdfPathSegment {
  PdfPathBezier(
    pdf_shape_transform_point(
      center_x,
      center_y,
      radius,
      angle,
      @geometry.point2(1.0, 0.0),
    ),
    pdf_shape_transform_point(
      center_x,
      center_y,
      radius,
      angle,
      @geometry.point2(1.0, pdf_shape_kappa),
    ),
    pdf_shape_transform_point(
      center_x,
      center_y,
      radius,
      angle,
      @geometry.point2(pdf_shape_kappa, 1.0),
    ),
    pdf_shape_transform_point(
      center_x,
      center_y,
      radius,
      angle,
      @geometry.point2(0.0, 1.0),
    ),
  )
}

///|
fn pdf_path_segment_end(segment : PdfPathSegment) -> Point2 {
  match segment {
    PdfPathStraight(_, end) => end
    PdfPathBezier(_, _, _, end) => end
  }
}

///|
fn pdf_path_segment_with_start(
  segment : PdfPathSegment,
  start : Point2,
) -> PdfPathSegment {
  match segment {
    PdfPathStraight(_, end) => PdfPathStraight(start, end)
    PdfPathBezier(_, control1, control2, end) =>
      PdfPathBezier(start, control1, control2, end)
  }
}

///|
fn pdf_shape_join_segments(
  segments : ArrayView[PdfPathSegment],
) -> Array[PdfPathSegment] {
  if segments.length() <= 1 {
    segments.to_owned()
  } else {
    let output : Array[PdfPathSegment] = [segments[0]]
    let mut previous_end = pdf_path_segment_end(segments[0])
    for index in 1.. PdfPathSubpath {
  { hole, closure, segments: segments.to_owned(), }
}

///|
fn pdf_path(
  winding_rule : PdfPathWindingRule,
  subpaths : ArrayView[PdfPathSubpath],
) -> PdfPath {
  { winding_rule, subpaths: subpaths.to_owned(), }
}

///|
/// Build a cpdf-style circular path from four Bezier quarter arcs.
pub fn pdf_shape_circle(
  center_x : Double,
  center_y : Double,
  radius : Double,
) -> PdfPath {
  let segments = pdf_shape_join_segments([
    pdf_shape_quarter(0.0, center_x, center_y, radius),
    pdf_shape_quarter(@math.PI / 2.0, center_x, center_y, radius),
    pdf_shape_quarter(@math.PI, center_x, center_y, radius),
    pdf_shape_quarter(3.0 * @math.PI / 2.0, center_x, center_y, radius),
  ])
  pdf_path(PdfPathNonZero, [
    pdf_path_subpath(PdfPathNotHole, PdfPathClosed, segments),
  ])
}

///|
/// Source-spelled compatibility alias for cpdfshape's `circle`.
pub fn pdf_circle(
  center_x : Double,
  center_y : Double,
  radius : Double,
) -> PdfPath {
  pdf_shape_circle(center_x, center_y, radius)
}

///|
/// Build a cpdf-style rectangle path from a top-left point, width, and height.
pub fn pdf_shape_rectangle(
  x : Double,
  y : Double,
  width : Double,
  height : Double,
) -> PdfPath {
  pdf_path(PdfPathEvenOdd, [
    pdf_path_subpath(PdfPathNotHole, PdfPathClosed, [
      PdfPathStraight(@geometry.point2(x, y), @geometry.point2(x + width, y)),
      PdfPathStraight(
        @geometry.point2(x + width, y),
        @geometry.point2(x + width, y + height),
      ),
      PdfPathStraight(
        @geometry.point2(x + width, y + height),
        @geometry.point2(x, y + height),
      ),
      PdfPathStraight(@geometry.point2(x, y + height), @geometry.point2(x, y)),
    ]),
  ])
}

///|
/// Source-spelled compatibility alias for cpdfshape's `rectangle`.
pub fn pdf_rectangle(
  x : Double,
  y : Double,
  width : Double,
  height : Double,
) -> PdfPath {
  pdf_shape_rectangle(x, y, width, height)
}