///|
fn rect_path_commands(
  x : Double,
  y : Double,
  width : Double,
  height : Double,
  rx : Double,
  ry : Double,
) -> Array[PathCommand] {
  let clamped_rx = min(max(rx, 0.0), width / 2.0)
  let clamped_ry = min(max(ry, 0.0), height / 2.0)
  if (clamped_rx - width / 2.0).abs() <= 0.000000000001 &&
    (clamped_ry - height / 2.0).abs() <= 0.000000000001 {
    return ellipse_path_commands(
      x + width / 2.0,
      y + height / 2.0,
      clamped_rx,
      clamped_ry,
    )
  }
  if clamped_rx <= 0.0 || clamped_ry <= 0.0 {
    return [
      MoveTo(x, y),
      LineTo(x + width, y),
      LineTo(x + width, y + height),
      LineTo(x, y + height),
      ClosePath,
    ]
  }
  [
    MoveTo(x + clamped_rx, y),
    LineTo(x + width - clamped_rx, y),
    ArcTo(clamped_rx, clamped_ry, 0.0, false, true, x + width, y + clamped_ry),
    LineTo(x + width, y + height - clamped_ry),
    ArcTo(
      clamped_rx,
      clamped_ry,
      0.0,
      false,
      true,
      x + width - clamped_rx,
      y + height,
    ),
    LineTo(x + clamped_rx, y + height),
    ArcTo(clamped_rx, clamped_ry, 0.0, false, true, x, y + height - clamped_ry),
    LineTo(x, y + clamped_ry),
    ArcTo(clamped_rx, clamped_ry, 0.0, false, true, x + clamped_rx, y),
    ClosePath,
  ]
}

///|
fn points_path_commands(
  points : Array[(Double, Double)],
  close : Bool,
) -> Array[PathCommand] {
  if points.is_empty() {
    return []
  }
  let commands : Array[PathCommand] = [MoveTo(points[0].0, points[0].1)]
  for i in 1.. Array[PathCommand] {
  [
    MoveTo(cx + rx, cy),
    ArcTo(rx, ry, 0.0, true, true, cx - rx, cy),
    ArcTo(rx, ry, 0.0, true, true, cx + rx, cy),
    ClosePath,
  ]
}

///|
fn render_rect(
  x : Double,
  y : Double,
  width : Double,
  height : Double,
  rx : Double,
  ry : Double,
  node : SVGNode,
  transform : Transform,
  ctx : RenderState,
  resources : RenderResources,
  node_color : Color,
) -> Unit {
  if width <= 0.0 || height <= 0.0 {
    return
  }
  render_path(
    rect_path_commands(x, y, width, height, rx, ry),
    node,
    transform,
    ctx,
    node_color,
    resources,
  )
}

///|
fn render_circle(
  cx : Double,
  cy : Double,
  r : Double,
  node : SVGNode,
  transform : Transform,
  ctx : RenderState,
  resources : RenderResources,
  node_color : Color,
) -> Unit {
  if r <= 0.0 {
    return
  }
  render_path(
    ellipse_path_commands(cx, cy, r, r),
    node,
    transform,
    ctx,
    node_color,
    resources,
  )
}

///|
fn render_ellipse(
  cx : Double,
  cy : Double,
  rx : Double,
  ry : Double,
  node : SVGNode,
  transform : Transform,
  ctx : RenderState,
  resources : RenderResources,
  node_color : Color,
) -> Unit {
  if rx <= 0.0 || ry <= 0.0 {
    return
  }
  render_path(
    ellipse_path_commands(cx, cy, rx, ry),
    node,
    transform,
    ctx,
    node_color,
    resources,
  )
}

///|
fn render_line(
  x1 : Double,
  y1 : Double,
  x2 : Double,
  y2 : Double,
  node : SVGNode,
  transform : Transform,
  ctx : RenderState,
  resources : RenderResources,
  node_color : Color,
) -> Unit {
  fn draw_stroke() -> Unit {
    match resolve_paint_for_render(node.stroke.paint, node_color, resources) {
      SolidColor(color) =>
        if node.stroke_opacity > 0.0 && node.stroke.width > 0.0 {
          let stroke_color = apply_opacity(
            color,
            node.stroke_opacity * node.opacity,
          )
          raster_affine_stroke(
            [(x1, y1), (x2, y2)],
            node.stroke,
            transform,
            stroke_color,
            ctx.setter,
            ctx.width,
            ctx.height,
            false,
          )
        }
      _ => ()
    }
  }

  fn draw_markers() -> Unit {
    render_markers_for_linear_shape(
      [(x1, y1), (x2, y2)],
      false,
      node,
      transform,
      ctx,
      resources,
      node_color,
    )
  }

  render_paint_order(node.paint_order, () => (), draw_stroke, draw_markers)
}

///|
fn marker_transform_with_ref_ratio(
  marker : Marker,
  x : Double,
  y : Double,
  angle : Double,
  scale : Double,
  ref_ratio : Double,
) -> Transform {
  let orient_angle = match marker.orient {
    Auto => angle
    AutoStartReverse => angle + 3.14159265358979323846
    Angle(a) => degrees_to_radians(a)
  }
  let content_transform = match marker.view_box {
    Some(vb) => {
      let view_t = vb.get_transform(
        marker.marker_width,
        marker.marker_height,
        marker.preserve_aspect_ratio,
      )
      let (ref_px, ref_py) = view_t.apply(marker.ref_x, marker.ref_y)
      let ref_t = Transform::translate(-ref_px * ref_ratio, -ref_py * ref_ratio)
      ref_t.multiply(view_t)
    }
    None =>
      Transform::translate(-marker.ref_x * ref_ratio, -marker.ref_y * ref_ratio)
  }
  let t1 = Transform::translate(x, y)
  let r = Transform::rotate(orient_angle)
  let s = Transform::scale(scale, scale)
  t1.multiply(r).multiply(s).multiply(content_transform)
}

///|
fn render_marker_instance(
  marker : Marker,
  x : Double,
  y : Double,
  angle : Double,
  node : SVGNode,
  transform : Transform,
  ctx : RenderState,
  resources : RenderResources,
  node_color : Color,
) -> Unit {
  let (marker_scale, ref_ratio) = match marker.marker_units {
    StrokeWidth => {
      let scale = node.stroke.width
      if scale > 0.0 {
        (scale, 1.0)
      } else {
        (0.0, 1.0)
      }
    }
    UserSpaceOnUse_ => (1.0, 1.0)
  }
  let marker_transform = marker_transform_with_ref_ratio(
    marker, x, y, angle, marker_scale, ref_ratio,
  )
  let viewport_transform = marker_viewport_transform(
    marker, x, y, angle, marker_scale, ref_ratio,
  )
  let parent_transform = transform.multiply(marker_transform)
  let mut ctx_for_marker = ctx
  if marker.clip_overflow &&
    marker.marker_width > 0.0 &&
    marker.marker_height > 0.0 {
    let viewport_bbox = BoundingBox::from_rect(
      0.0,
      0.0,
      marker.marker_width,
      marker.marker_height,
    )
    let clip_transform = transform.multiply(viewport_transform)
    let clip_shape = Rect(
      x=viewport_bbox.min_x,
      y=viewport_bbox.min_y,
      width=viewport_bbox.width(),
      height=viewport_bbox.height(),
      rx=0.0,
      ry=0.0,
    )
    let clip = ClipPath::with_transform(
      "marker-viewport", clip_shape, clip_transform,
    )
    ctx_for_marker = apply_clip_path(
      ctx_for_marker,
      clip,
      Transform::identity(),
      BoundingBox::empty(),
      resources,
    )
  }
  render_node(
    marker.content,
    parent_transform,
    ctx_for_marker,
    resources,
    true,
    node_color,
  )
}

///|
fn marker_viewport_transform(
  marker : Marker,
  x : Double,
  y : Double,
  angle : Double,
  scale : Double,
  ref_ratio : Double,
) -> Transform {
  let orient_angle = match marker.orient {
    Auto => angle
    AutoStartReverse => angle + 3.14159265358979323846
    Angle(a) => degrees_to_radians(a)
  }
  let (ref_px, ref_py) = match marker.view_box {
    Some(vb) => {
      let view_t = vb.get_transform(
        marker.marker_width,
        marker.marker_height,
        marker.preserve_aspect_ratio,
      )
      view_t.apply(marker.ref_x, marker.ref_y)
    }
    None => (marker.ref_x, marker.ref_y)
  }
  let ref_t = Transform::translate(-ref_px * ref_ratio, -ref_py * ref_ratio)
  let t1 = Transform::translate(x, y)
  let r = Transform::rotate(orient_angle)
  let s = Transform::scale(scale, scale)
  t1.multiply(r).multiply(s).multiply(ref_t)
}

///|
fn render_markers_for_linear_shape(
  points : Array[(Double, Double)],
  closed : Bool,
  node : SVGNode,
  transform : Transform,
  ctx : RenderState,
  resources : RenderResources,
  node_color : Color,
) -> Unit {
  if points.length() == 0 {
    return
  }
  let commands : Array[PathCommand] = [MoveTo(points[0].0, points[0].1)]
  for index in 1.. Array[Double] {
  let normalized : Array[Double] = []
  for value in dasharray {
    if value < 0.0 {
      return []
    }
    normalized.push(value)
  }
  if normalized.length() % 2 == 1 {
    for value in dasharray {
      normalized.push(value)
    }
  }
  normalized
}

///|
fn push_distinct_local_point(
  points : Array[(Double, Double)],
  point : (Double, Double),
) -> Unit {
  if points.length() == 0 {
    points.push(point)
    return
  }
  let previous = points[points.length() - 1]
  if (previous.0 - point.0).abs() > 0.000001 ||
    (previous.1 - point.1).abs() > 0.000001 {
    points.push(point)
  }
}

///|
fn points_are_close(left : (Double, Double), right : (Double, Double)) -> Bool {
  (left.0 - right.0).abs() <= 0.000001 && (left.1 - right.1).abs() <= 0.000001
}

///|
fn dash_local_polyline_pieces(
  points : Array[(Double, Double)],
  dasharray : Array[Double],
  dashoffset : Double,
) -> Array[Array[(Double, Double)]] {
  let pieces : Array[Array[(Double, Double)]] = []
  if points.length() < 2 {
    return pieces
  }
  let pattern = normalize_dash_pattern(dasharray)
  if pattern.length() == 0 {
    pieces.push(points)
    return pieces
  }
  let total = pattern.fold(init=0.0, (sum, value) => sum + value)
  if total <= 0.0 {
    return pieces
  }
  let epsilon = 0.000001
  let mut position = -dashoffset
  while position < 0.0 {
    position = position + total
  }
  while position >= total {
    position = position - total
  }
  let mut pattern_index = 0
  while position >= pattern[pattern_index] && pattern[pattern_index] > epsilon {
    position = position - pattern[pattern_index]
    pattern_index = (pattern_index + 1) % pattern.length()
  }
  let mut remaining = pattern[pattern_index] - position
  let mut drawing = pattern_index % 2 == 0
  let mut zero_guard = 0
  while remaining <= epsilon && zero_guard < pattern.length() {
    pattern_index = (pattern_index + 1) % pattern.length()
    remaining = pattern[pattern_index]
    drawing = pattern_index % 2 == 0
    zero_guard = zero_guard + 1
  }
  let mut current : Array[(Double, Double)] = []
  for segment_index in 0..<(points.length() - 1) {
    let start = points[segment_index]
    let finish = points[segment_index + 1]
    let dx = finish.0 - start.0
    let dy = finish.1 - start.1
    let length = (dx * dx + dy * dy).sqrt()
    if length <= epsilon {
      continue
    }
    let mut traveled = 0.0
    while traveled < length - epsilon {
      let available = length - traveled
      let step = if remaining < available { remaining } else { available }
      let start_ratio = traveled / length
      let end_ratio = (traveled + step) / length
      let piece_start = (start.0 + dx * start_ratio, start.1 + dy * start_ratio)
      let piece_end = (start.0 + dx * end_ratio, start.1 + dy * end_ratio)
      if drawing {
        push_distinct_local_point(current, piece_start)
        push_distinct_local_point(current, piece_end)
      }
      traveled = traveled + step
      remaining = remaining - step
      if remaining <= epsilon {
        if drawing && current.length() >= 2 {
          pieces.push(current)
          current = []
        }
        pattern_index = (pattern_index + 1) % pattern.length()
        remaining = pattern[pattern_index]
        drawing = pattern_index % 2 == 0
        zero_guard = 0
        while remaining <= epsilon && zero_guard < pattern.length() {
          pattern_index = (pattern_index + 1) % pattern.length()
          remaining = pattern[pattern_index]
          drawing = pattern_index % 2 == 0
          zero_guard = zero_guard + 1
        }
      }
    }
  }
  if drawing && current.length() >= 2 {
    pieces.push(current)
  }
  pieces
}

///|
fn merge_closed_dash_seam(
  pieces : Array[Array[(Double, Double)]],
  seam : (Double, Double),
) -> Unit {
  if pieces.length() <= 1 {
    return
  }
  let first_piece = pieces[0]
  let last_piece = pieces[pieces.length() - 1]
  if points_are_close(first_piece[0], seam) &&
    points_are_close(last_piece[last_piece.length() - 1], seam) {
    let merged = last_piece.copy()
    for index in 1.. Unit {
  let transformed : Array[(Double, Double)] = []
  for point in local_points {
    let device = transform.apply(point.0, point.1)
    push_distinct_local_point(transformed, device)
  }
  let area = polygon_signed_area(transformed)
  if transformed.length() >= 3 && area.abs() > 0.000000000001 {
    if area > 0.0 {
      outlines.push(transformed)
    } else {
      let reversed : Array[(Double, Double)] = []
      for index in 0.. Unit {
  let polygon : Array[(Double, Double)] = []
  let error = min(radius, device_path_flatness(transform))
  let max_angle = 2.0 * @math.acos(1.0 - error / radius)
  let segments = min_int(
    65536,
    max_int((6.283185307179586 / max_angle).ceil().to_int(), 8),
  )
  for index in 0.. Unit {
  let previous_dx = vertex.0 - previous.0
  let previous_dy = vertex.1 - previous.1
  let next_dx = next.0 - vertex.0
  let next_dy = next.1 - vertex.1
  let previous_length = (previous_dx * previous_dx + previous_dy * previous_dy).sqrt()
  let next_length = (next_dx * next_dx + next_dy * next_dy).sqrt()
  if previous_length <= 0.000001 || next_length <= 0.000001 {
    return
  }
  let ux0 = previous_dx / previous_length
  let uy0 = previous_dy / previous_length
  let ux1 = next_dx / next_length
  let uy1 = next_dy / next_length
  let cross = ux0 * uy1 - uy0 * ux1
  if cross.abs() <= 0.000001 {
    return
  }
  match linejoin {
    Round => push_affine_stroke_disk(outlines, vertex, half, transform)
    Bevel | Miter => {
      let side = if cross > 0.0 { -1.0 } else { 1.0 }
      let outer0 = (vertex.0 + side * -uy0 * half, vertex.1 + side * ux0 * half)
      let outer1 = (vertex.0 + side * -uy1 * half, vertex.1 + side * ux1 * half)
      if linejoin == Bevel {
        push_transformed_stroke_polygon(
          outlines,
          [vertex, outer0, outer1],
          transform,
        )
        return
      }
      let denominator = ux0 * uy1 - uy0 * ux1
      let t = ((outer1.0 - outer0.0) * uy1 - (outer1.1 - outer0.1) * ux1) /
        denominator
      let miter = (outer0.0 + t * ux0, outer0.1 + t * uy0)
      let miter_dx = miter.0 - vertex.0
      let miter_dy = miter.1 - vertex.1
      let miter_ratio = (miter_dx * miter_dx + miter_dy * miter_dy).sqrt() /
        half
      let polygon = if miter_ratio <= miterlimit {
        [vertex, outer0, miter, outer1]
      } else {
        [vertex, outer0, outer1]
      }
      push_transformed_stroke_polygon(outlines, polygon, transform)
    }
  }
}

///|
fn append_affine_stroke_piece(
  outlines : Array[Array[(Double, Double)]],
  points : Array[(Double, Double)],
  stroke : StrokeStyle,
  transform : Transform,
  closed : Bool,
) -> Unit {
  if points.length() == 0 || stroke.width <= 0.0 {
    return
  }
  let half = stroke.width / 2.0
  let segment_count = if closed { points.length() } else { points.length() - 1 }
  let mut has_segment = false
  for index in 0.. ()
        Round => push_affine_stroke_disk(outlines, points[0], half, transform)
        Square =>
          push_transformed_stroke_polygon(
            outlines,
            [
              (points[0].0 - half, points[0].1 - half),
              (points[0].0 + half, points[0].1 - half),
              (points[0].0 + half, points[0].1 + half),
              (points[0].0 - half, points[0].1 + half),
            ],
            transform,
          )
      }
    }
    return
  }
  if closed {
    for index in 0.. Unit {
  if source_points.length() == 0 || stroke.width <= 0.0 {
    return
  }
  if stroke.non_scaling {
    let device_points = source_points.map(fn(point) {
      transform.apply(point.0, point.1)
    })
    raster_affine_stroke(
      device_points,
      { ..stroke, non_scaling: false },
      Transform::identity(),
      color,
      setter,
      canvas_width,
      canvas_height,
      closed,
    )
    return
  }
  let points = source_points.copy()
  if closed && points.length() > 1 {
    let first = points[0]
    let last = points[points.length() - 1]
    if (first.0 - last.0).abs() <= 0.000001 &&
      (first.1 - last.1).abs() <= 0.000001 {
      let _ = points.pop()
    }
  }
  let outlines : Array[Array[(Double, Double)]] = []
  match stroke.dasharray {
    Some(dasharray) => {
      let dash_points = if closed {
        let result = points.copy()
        result.push(points[0])
        result
      } else {
        points
      }
      let pieces = dash_local_polyline_pieces(
        dash_points,
        dasharray,
        stroke.dashoffset,
      )
      if closed {
        merge_closed_dash_seam(pieces, points[0])
      }
      for piece in pieces {
        let piece_closed = piece.length() > 2 &&
          points_are_close(piece[0], piece[piece.length() - 1])
        let piece_points = piece.copy()
        if piece_closed {
          let _ = piece_points.pop()
        }
        append_affine_stroke_piece(
          outlines, piece_points, stroke, transform, piece_closed,
        )
      }
    }
    None =>
      append_affine_stroke_piece(outlines, points, stroke, transform, closed)
  }
  if outlines.length() > 0 {
    raster_contours_coverage(
      outlines,
      color,
      NonZero,
      setter,
      canvas_width,
      canvas_height,
    )
  }
}

///|
priv struct MarkerPoint {
  x : Double
  y : Double
  mut has_in : Bool
  mut in_dx : Double
  mut in_dy : Double
  mut has_out : Bool
  mut out_dx : Double
  mut out_dy : Double
}

///|
fn MarkerPoint::new(x : Double, y : Double) -> MarkerPoint {
  {
    x,
    y,
    has_in: false,
    in_dx: 0.0,
    in_dy: 0.0,
    has_out: false,
    out_dx: 0.0,
    out_dy: 0.0,
  }
}

///|
priv struct MarkerSubpath {
  points : Array[MarkerPoint]
}

///|
fn arc_tangent_at(
  theta : Double,
  cos_phi : Double,
  sin_phi : Double,
  rx : Double,
  ry : Double,
) -> (Double, Double) {
  let sin_t = @math.sin(theta)
  let cos_t = @math.cos(theta)
  let dx = -cos_phi * rx * sin_t - sin_phi * ry * cos_t
  let dy = -sin_phi * rx * sin_t + cos_phi * ry * cos_t
  (dx, dy)
}

///|
fn angle_between_vec(
  ux : Double,
  uy : Double,
  vx : Double,
  vy : Double,
) -> Double {
  let dot = ux * vx + uy * vy
  let len_u = (ux * ux + uy * uy).sqrt()
  let len_v = (vx * vx + vy * vy).sqrt()
  let len_prod = len_u * len_v
  if len_prod == 0.0 {
    return 0.0
  }
  let mut cos_angle = dot / len_prod
  if cos_angle > 1.0 {
    cos_angle = 1.0
  }
  if cos_angle < -1.0 {
    cos_angle = -1.0
  }
  let angle = @math.acos(cos_angle)
  let cross = ux * vy - uy * vx
  if cross < 0.0 {
    -angle
  } else {
    angle
  }
}

///|
fn compute_arc_tangents(
  x1 : Double,
  y1 : Double,
  rx_in : Double,
  ry_in : Double,
  rotation_degrees : Double,
  large_arc : Bool,
  sweep : Bool,
  x2 : Double,
  y2 : Double,
) -> (Double, Double, Double, Double) {
  let dx_line = x2 - x1
  let dy_line = y2 - y1
  if (x1 == x2 && y1 == y2) || rx_in == 0.0 || ry_in == 0.0 {
    return (dx_line, dy_line, dx_line, dy_line)
  }
  let mut rx = if rx_in < 0.0 { -rx_in } else { rx_in }
  let mut ry = if ry_in < 0.0 { -ry_in } else { ry_in }
  let phi = degrees_to_radians(rotation_degrees)
  let cos_phi = @math.cos(phi)
  let sin_phi = @math.sin(phi)
  let dx = (x1 - x2) / 2.0
  let dy = (y1 - y2) / 2.0
  let x1p = cos_phi * dx + sin_phi * dy
  let y1p = -sin_phi * dx + cos_phi * dy
  let lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry)
  if lambda > 1.0 {
    let sqrt_lambda = lambda.sqrt()
    rx = rx * sqrt_lambda
    ry = ry * sqrt_lambda
  }
  let rx2 = rx * rx
  let ry2 = ry * ry
  let x1p2 = x1p * x1p
  let y1p2 = y1p * y1p
  let sq = (rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2) / (rx2 * y1p2 + ry2 * x1p2)
  let sq_abs = if sq < 0.0 { 0.0 } else { sq }
  let coef = sq_abs.sqrt() * (if large_arc == sweep { -1.0 } else { 1.0 })
  let cxp = coef * rx * y1p / ry
  let cyp = -coef * ry * x1p / rx
  let theta1 = angle_between_vec(1.0, 0.0, (x1p - cxp) / rx, (y1p - cyp) / ry)
  let mut dtheta = angle_between_vec(
    (x1p - cxp) / rx,
    (y1p - cyp) / ry,
    (-x1p - cxp) / rx,
    (-y1p - cyp) / ry,
  )
  let pi = 3.14159265358979323846
  if !sweep && dtheta > 0.0 {
    dtheta = dtheta - 2.0 * pi
  } else if sweep && dtheta < 0.0 {
    dtheta = dtheta + 2.0 * pi
  }
  let theta2 = theta1 + dtheta
  let sign = if dtheta < 0.0 { -1.0 } else { 1.0 }
  let (sx, sy) = arc_tangent_at(theta1, cos_phi, sin_phi, rx, ry)
  let (ex, ey) = arc_tangent_at(theta2, cos_phi, sin_phi, rx, ry)
  (sx * sign, sy * sign, ex * sign, ey * sign)
}

///|
fn build_marker_subpaths(commands : Array[PathCommand]) -> Array[MarkerSubpath] {
  let subpaths : Array[MarkerSubpath] = []
  let mut points : Array[MarkerPoint] = []
  let mut closed = false
  let mut cur_x = 0.0
  let mut cur_y = 0.0
  let mut start_x = 0.0
  let mut start_y = 0.0
  let mut last_ctrl_x = 0.0
  let mut last_ctrl_y = 0.0
  let mut last_cmd_was_curve = false
  let mut last_cmd_was_quad = false
  let eps = 0.0001
  fn finish_subpath(
    subpaths : Array[MarkerSubpath],
    points : Array[MarkerPoint],
    closed : Bool,
  ) -> Unit {
    if points.length() == 0 {
      return
    }
    let out_points = points
    if closed {
      let first = out_points[0]
      let dup = MarkerPoint::new(first.x, first.y)
      if first.has_in {
        dup.has_in = true
        dup.in_dx = first.in_dx
        dup.in_dy = first.in_dy
      }
      out_points.push(dup)
    }
    subpaths.push({ points: out_points })
  }

  fn record_out(
    points : Array[MarkerPoint],
    idx : Int,
    dx : Double,
    dy : Double,
    eps : Double,
  ) -> Unit {
    let len = (dx * dx + dy * dy).sqrt()
    if len >= eps {
      points[idx].has_out = true
      points[idx].out_dx = dx
      points[idx].out_dy = dy
    }
  }

  fn record_in(
    points : Array[MarkerPoint],
    idx : Int,
    dx : Double,
    dy : Double,
    eps : Double,
  ) -> Unit {
    let len = (dx * dx + dy * dy).sqrt()
    if len >= eps {
      points[idx].has_in = true
      points[idx].in_dx = dx
      points[idx].in_dy = dy
    }
  }

  fn add_segment(
    points : Array[MarkerPoint],
    cur_x : Double,
    cur_y : Double,
    end_x : Double,
    end_y : Double,
    out_dx : Double,
    out_dy : Double,
    in_dx : Double,
    in_dy : Double,
    eps : Double,
  ) -> Unit {
    if points.length() == 0 {
      points.push(MarkerPoint::new(cur_x, cur_y))
    }
    let last_idx = points.length() - 1
    record_out(points, last_idx, out_dx, out_dy, eps)
    let end_point = MarkerPoint::new(end_x, end_y)
    let in_len = (in_dx * in_dx + in_dy * in_dy).sqrt()
    if in_len >= eps {
      end_point.has_in = true
      end_point.in_dx = in_dx
      end_point.in_dy = in_dy
    }
    points.push(end_point)
  }

  for cmd in commands {
    match cmd {
      MoveTo(x, y) => {
        finish_subpath(subpaths, points, closed)
        points = [MarkerPoint::new(x, y)]
        closed = false
        cur_x = x
        cur_y = y
        start_x = x
        start_y = y
        last_cmd_was_curve = false
        last_cmd_was_quad = false
      }
      MoveToRel(dx, dy) => {
        finish_subpath(subpaths, points, closed)
        let x = cur_x + dx
        let y = cur_y + dy
        points = [MarkerPoint::new(x, y)]
        closed = false
        cur_x = x
        cur_y = y
        start_x = x
        start_y = y
        last_cmd_was_curve = false
        last_cmd_was_quad = false
      }
      LineTo(x, y) => {
        let dx = x - cur_x
        let dy = y - cur_y
        add_segment(points, cur_x, cur_y, x, y, dx, dy, dx, dy, eps)
        cur_x = x
        cur_y = y
        last_cmd_was_curve = false
        last_cmd_was_quad = false
      }
      LineToRel(dx, dy) => {
        let x = cur_x + dx
        let y = cur_y + dy
        add_segment(points, cur_x, cur_y, x, y, dx, dy, dx, dy, eps)
        cur_x = x
        cur_y = y
        last_cmd_was_curve = false
        last_cmd_was_quad = false
      }
      HorizontalLineTo(x) => {
        let dx = x - cur_x
        add_segment(points, cur_x, cur_y, x, cur_y, dx, 0.0, dx, 0.0, eps)
        cur_x = x
        last_cmd_was_curve = false
        last_cmd_was_quad = false
      }
      HorizontalLineToRel(dx) => {
        let x = cur_x + dx
        add_segment(points, cur_x, cur_y, x, cur_y, dx, 0.0, dx, 0.0, eps)
        cur_x = x
        last_cmd_was_curve = false
        last_cmd_was_quad = false
      }
      VerticalLineTo(y) => {
        let dy = y - cur_y
        add_segment(points, cur_x, cur_y, cur_x, y, 0.0, dy, 0.0, dy, eps)
        cur_y = y
        last_cmd_was_curve = false
        last_cmd_was_quad = false
      }
      VerticalLineToRel(dy) => {
        let y = cur_y + dy
        add_segment(points, cur_x, cur_y, cur_x, y, 0.0, dy, 0.0, dy, eps)
        cur_y = y
        last_cmd_was_curve = false
        last_cmd_was_quad = false
      }
      CurveTo(x1, y1, x2, y2, x, y) => {
        let mut sx = x1 - cur_x
        let mut sy = y1 - cur_y
        if (sx * sx + sy * sy).sqrt() < eps {
          sx = x2 - cur_x
          sy = y2 - cur_y
        }
        if (sx * sx + sy * sy).sqrt() < eps {
          sx = x - cur_x
          sy = y - cur_y
        }
        let mut ex = x - x2
        let mut ey = y - y2
        if (ex * ex + ey * ey).sqrt() < eps {
          ex = x - x1
          ey = y - y1
        }
        if (ex * ex + ey * ey).sqrt() < eps {
          ex = x - cur_x
          ey = y - cur_y
        }
        add_segment(points, cur_x, cur_y, x, y, sx, sy, ex, ey, eps)
        last_ctrl_x = x2
        last_ctrl_y = y2
        cur_x = x
        cur_y = y
        last_cmd_was_curve = true
        last_cmd_was_quad = false
      }
      CurveToRel(dx1, dy1, dx2, dy2, dx, dy) => {
        let x1 = cur_x + dx1
        let y1 = cur_y + dy1
        let x2 = cur_x + dx2
        let y2 = cur_y + dy2
        let x = cur_x + dx
        let y = cur_y + dy
        let mut sx = x1 - cur_x
        let mut sy = y1 - cur_y
        if (sx * sx + sy * sy).sqrt() < eps {
          sx = x2 - cur_x
          sy = y2 - cur_y
        }
        if (sx * sx + sy * sy).sqrt() < eps {
          sx = x - cur_x
          sy = y - cur_y
        }
        let mut ex = x - x2
        let mut ey = y - y2
        if (ex * ex + ey * ey).sqrt() < eps {
          ex = x - x1
          ey = y - y1
        }
        if (ex * ex + ey * ey).sqrt() < eps {
          ex = x - cur_x
          ey = y - cur_y
        }
        add_segment(points, cur_x, cur_y, x, y, sx, sy, ex, ey, eps)
        last_ctrl_x = x2
        last_ctrl_y = y2
        cur_x = x
        cur_y = y
        last_cmd_was_curve = true
        last_cmd_was_quad = false
      }
      SmoothCurveTo(x2, y2, x, y) => {
        let x1 = if last_cmd_was_curve {
          2.0 * cur_x - last_ctrl_x
        } else {
          cur_x
        }
        let y1 = if last_cmd_was_curve {
          2.0 * cur_y - last_ctrl_y
        } else {
          cur_y
        }
        let mut sx = x1 - cur_x
        let mut sy = y1 - cur_y
        if (sx * sx + sy * sy).sqrt() < eps {
          sx = x2 - cur_x
          sy = y2 - cur_y
        }
        if (sx * sx + sy * sy).sqrt() < eps {
          sx = x - cur_x
          sy = y - cur_y
        }
        let mut ex = x - x2
        let mut ey = y - y2
        if (ex * ex + ey * ey).sqrt() < eps {
          ex = x - x1
          ey = y - y1
        }
        if (ex * ex + ey * ey).sqrt() < eps {
          ex = x - cur_x
          ey = y - cur_y
        }
        add_segment(points, cur_x, cur_y, x, y, sx, sy, ex, ey, eps)
        last_ctrl_x = x2
        last_ctrl_y = y2
        cur_x = x
        cur_y = y
        last_cmd_was_curve = true
        last_cmd_was_quad = false
      }
      SmoothCurveToRel(dx2, dy2, dx, dy) => {
        let x1 = if last_cmd_was_curve {
          2.0 * cur_x - last_ctrl_x
        } else {
          cur_x
        }
        let y1 = if last_cmd_was_curve {
          2.0 * cur_y - last_ctrl_y
        } else {
          cur_y
        }
        let x2 = cur_x + dx2
        let y2 = cur_y + dy2
        let x = cur_x + dx
        let y = cur_y + dy
        let mut sx = x1 - cur_x
        let mut sy = y1 - cur_y
        if (sx * sx + sy * sy).sqrt() < eps {
          sx = x2 - cur_x
          sy = y2 - cur_y
        }
        if (sx * sx + sy * sy).sqrt() < eps {
          sx = x - cur_x
          sy = y - cur_y
        }
        let mut ex = x - x2
        let mut ey = y - y2
        if (ex * ex + ey * ey).sqrt() < eps {
          ex = x - x1
          ey = y - y1
        }
        if (ex * ex + ey * ey).sqrt() < eps {
          ex = x - cur_x
          ey = y - cur_y
        }
        add_segment(points, cur_x, cur_y, x, y, sx, sy, ex, ey, eps)
        last_ctrl_x = x2
        last_ctrl_y = y2
        cur_x = x
        cur_y = y
        last_cmd_was_curve = true
        last_cmd_was_quad = false
      }
      QuadraticCurveTo(x1, y1, x, y) => {
        let mut sx = x1 - cur_x
        let mut sy = y1 - cur_y
        if (sx * sx + sy * sy).sqrt() < eps {
          sx = x - cur_x
          sy = y - cur_y
        }
        let mut ex = x - x1
        let mut ey = y - y1
        if (ex * ex + ey * ey).sqrt() < eps {
          ex = x - cur_x
          ey = y - cur_y
        }
        add_segment(points, cur_x, cur_y, x, y, sx, sy, ex, ey, eps)
        last_ctrl_x = x1
        last_ctrl_y = y1
        cur_x = x
        cur_y = y
        last_cmd_was_curve = false
        last_cmd_was_quad = true
      }
      QuadraticCurveToRel(dx1, dy1, dx, dy) => {
        let x1 = cur_x + dx1
        let y1 = cur_y + dy1
        let x = cur_x + dx
        let y = cur_y + dy
        let mut sx = x1 - cur_x
        let mut sy = y1 - cur_y
        if (sx * sx + sy * sy).sqrt() < eps {
          sx = x - cur_x
          sy = y - cur_y
        }
        let mut ex = x - x1
        let mut ey = y - y1
        if (ex * ex + ey * ey).sqrt() < eps {
          ex = x - cur_x
          ey = y - cur_y
        }
        add_segment(points, cur_x, cur_y, x, y, sx, sy, ex, ey, eps)
        last_ctrl_x = x1
        last_ctrl_y = y1
        cur_x = x
        cur_y = y
        last_cmd_was_curve = false
        last_cmd_was_quad = true
      }
      SmoothQuadraticCurveTo(x, y) => {
        let x1 = if last_cmd_was_quad {
          2.0 * cur_x - last_ctrl_x
        } else {
          cur_x
        }
        let y1 = if last_cmd_was_quad {
          2.0 * cur_y - last_ctrl_y
        } else {
          cur_y
        }
        let mut sx = x1 - cur_x
        let mut sy = y1 - cur_y
        if (sx * sx + sy * sy).sqrt() < eps {
          sx = x - cur_x
          sy = y - cur_y
        }
        let mut ex = x - x1
        let mut ey = y - y1
        if (ex * ex + ey * ey).sqrt() < eps {
          ex = x - cur_x
          ey = y - cur_y
        }
        add_segment(points, cur_x, cur_y, x, y, sx, sy, ex, ey, eps)
        last_ctrl_x = x1
        last_ctrl_y = y1
        cur_x = x
        cur_y = y
        last_cmd_was_curve = false
        last_cmd_was_quad = true
      }
      SmoothQuadraticCurveToRel(dx, dy) => {
        let x1 = if last_cmd_was_quad {
          2.0 * cur_x - last_ctrl_x
        } else {
          cur_x
        }
        let y1 = if last_cmd_was_quad {
          2.0 * cur_y - last_ctrl_y
        } else {
          cur_y
        }
        let x = cur_x + dx
        let y = cur_y + dy
        let mut sx = x1 - cur_x
        let mut sy = y1 - cur_y
        if (sx * sx + sy * sy).sqrt() < eps {
          sx = x - cur_x
          sy = y - cur_y
        }
        let mut ex = x - x1
        let mut ey = y - y1
        if (ex * ex + ey * ey).sqrt() < eps {
          ex = x - cur_x
          ey = y - cur_y
        }
        add_segment(points, cur_x, cur_y, x, y, sx, sy, ex, ey, eps)
        last_ctrl_x = x1
        last_ctrl_y = y1
        cur_x = x
        cur_y = y
        last_cmd_was_curve = false
        last_cmd_was_quad = true
      }
      ArcTo(rx, ry, rotation, large_arc, sweep, x, y) => {
        let (sx, sy, ex, ey) = compute_arc_tangents(
          cur_x, cur_y, rx, ry, rotation, large_arc, sweep, x, y,
        )
        add_segment(points, cur_x, cur_y, x, y, sx, sy, ex, ey, eps)
        cur_x = x
        cur_y = y
        last_cmd_was_curve = false
        last_cmd_was_quad = false
      }
      ArcToRel(rx, ry, rotation, large_arc, sweep, dx, dy) => {
        let x = cur_x + dx
        let y = cur_y + dy
        let (sx, sy, ex, ey) = compute_arc_tangents(
          cur_x, cur_y, rx, ry, rotation, large_arc, sweep, x, y,
        )
        add_segment(points, cur_x, cur_y, x, y, sx, sy, ex, ey, eps)
        cur_x = x
        cur_y = y
        last_cmd_was_curve = false
        last_cmd_was_quad = false
      }
      ClosePath => {
        if points.length() > 0 {
          let dx = start_x - cur_x
          let dy = start_y - cur_y
          let last_idx = points.length() - 1
          record_out(points, last_idx, dx, dy, eps)
          record_in(points, 0, dx, dy, eps)
          cur_x = start_x
          cur_y = start_y
          closed = true
        }
        last_cmd_was_curve = false
        last_cmd_was_quad = false
      }
    }
  }
  finish_subpath(subpaths, points, closed)
  subpaths
}

///|
fn render_markers_for_path_commands(
  commands : Array[PathCommand],
  node : SVGNode,
  transform : Transform,
  ctx : RenderState,
  resources : RenderResources,
  node_color : Color,
) -> Unit {
  if node.marker_start is None &&
    node.marker_mid is None &&
    node.marker_end is None {
    return
  }
  let subpaths = build_marker_subpaths(commands)
  if subpaths.length() == 0 {
    return
  }
  let eps = 0.0001
  let (sx, sy) = transform.get_scale()
  let skew = transform.a * transform.c + transform.b * transform.d
  let use_transformed = (sx - sy).abs() > eps || skew.abs() > eps
  let inv = transform.inverse()
  fn to_local_angle(angle : Double) -> Double {
    let dx = @math.cos(angle)
    let dy = @math.sin(angle)
    let lx = inv.a * dx + inv.c * dy
    let ly = inv.b * dx + inv.d * dy
    @math.atan2(ly, lx)
  }

  fn apply_vec(
    dx : Double,
    dy : Double,
    t : Transform,
    use_t : Bool,
  ) -> (Double, Double) {
    if use_t {
      (t.a * dx + t.c * dy, t.b * dx + t.d * dy)
    } else {
      (dx, dy)
    }
  }

  fn normalize(
    dx : Double,
    dy : Double,
    eps : Double,
  ) -> (Double, Double, Double) {
    let len = (dx * dx + dy * dy).sqrt()
    if len < eps {
      (0.0, 0.0, len)
    } else {
      (dx / len, dy / len, len)
    }
  }

  fn angle_from_vec(
    dx : Double,
    dy : Double,
    use_t : Bool,
    t : Transform,
  ) -> Double {
    let (vx, vy) = apply_vec(dx, dy, t, use_t)
    let ang = @math.atan2(vy, vx)
    if use_t {
      to_local_angle(ang)
    } else {
      ang
    }
  }

  fn angle_mid_vec(
    in_dx : Double,
    in_dy : Double,
    out_dx : Double,
    out_dy : Double,
    eps : Double,
  ) -> Double {
    let (ux1, uy1, l1) = normalize(in_dx, in_dy, eps)
    let (ux2, uy2, l2) = normalize(out_dx, out_dy, eps)
    if l1 < eps && l2 < eps {
      0.0
    } else if l1 < eps {
      @math.atan2(out_dy, out_dx)
    } else if l2 < eps {
      @math.atan2(in_dy, in_dx)
    } else {
      let sx = ux1 + ux2
      let sy = uy1 + uy2
      if sx * sx + sy * sy < eps * eps {
        @math.atan2(out_dx, -out_dy)
      } else {
        @math.atan2(sy, sx)
      }
    }
  }

  let sub_last = subpaths.length() - 1
  for sub_idx in 0..
          match resources.markers.get(id) {
            Some(marker) => {
              let p = sub.points[i]
              let angle = if i == 0 {
                if p.has_in && p.has_out {
                  let (in_dx, in_dy) = apply_vec(
                    p.in_dx,
                    p.in_dy,
                    transform,
                    use_transformed,
                  )
                  let (out_dx, out_dy) = apply_vec(
                    p.out_dx,
                    p.out_dy,
                    transform,
                    use_transformed,
                  )
                  let ang = angle_mid_vec(in_dx, in_dy, out_dx, out_dy, eps)
                  if use_transformed {
                    to_local_angle(ang)
                  } else {
                    ang
                  }
                } else if p.has_out {
                  angle_from_vec(p.out_dx, p.out_dy, use_transformed, transform)
                } else if p.has_in {
                  angle_from_vec(p.in_dx, p.in_dy, use_transformed, transform)
                } else {
                  0.0
                }
              } else if i == last {
                if p.has_in {
                  angle_from_vec(p.in_dx, p.in_dy, use_transformed, transform)
                } else if p.has_out {
                  angle_from_vec(p.out_dx, p.out_dy, use_transformed, transform)
                } else {
                  0.0
                }
              } else if p.has_in && p.has_out {
                let (in_dx, in_dy) = apply_vec(
                  p.in_dx,
                  p.in_dy,
                  transform,
                  use_transformed,
                )
                let (out_dx, out_dy) = apply_vec(
                  p.out_dx,
                  p.out_dy,
                  transform,
                  use_transformed,
                )
                let ang = angle_mid_vec(in_dx, in_dy, out_dx, out_dy, eps)
                if use_transformed {
                  to_local_angle(ang)
                } else {
                  ang
                }
              } else if p.has_in {
                angle_from_vec(p.in_dx, p.in_dy, use_transformed, transform)
              } else if p.has_out {
                angle_from_vec(p.out_dx, p.out_dy, use_transformed, transform)
              } else {
                0.0
              }
              render_marker_instance(
                marker,
                p.x,
                p.y,
                angle,
                node,
                transform,
                ctx,
                resources,
                node_color,
              )
            }
            None => ()
          }
        None => ()
      }
    }
  }
}

///|

///|
fn render_polyline(
  points : Array[(Double, Double)],
  node : SVGNode,
  transform : Transform,
  ctx : RenderState,
  resources : RenderResources,
  node_color : Color,
) -> Unit {
  if points.length() < 2 {
    return
  }
  render_path(
    points_path_commands(points, false),
    node,
    transform,
    ctx,
    node_color,
    resources,
  )
}

///|
fn render_polygon(
  points : Array[(Double, Double)],
  node : SVGNode,
  transform : Transform,
  ctx : RenderState,
  node_color : Color,
  resources : RenderResources,
) -> Unit {
  if points.length() < 3 {
    return
  }
  render_path(
    points_path_commands(points, true),
    node,
    transform,
    ctx,
    node_color,
    resources,
  )
}

///|