///|
/// 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)
}