Cut duplicated and speculative code across the package
An over-engineering audit of the whole tree, applied in five reviewed batches. Behaviour stays the same except where UPGRADE.md says otherwise. PHP: the showcase and error-page stylesheets are prebuilt into resources/dist by bin/stylesheets.mjs, through Vite's own postcss-import (first occurrence kept, the order an application's build gives), instead of Stylesheets::bundle() inlining imports on every request; only the import walk DesignGuard needs stays. SchemeStylesheet::withProfiles() replaces three copies of the scheme-plus-profiles loop, material:scheme leaves spec and contrast checks to the node script that already made them, and the error page's scheme cache, the hashed view namespace, the translations path with no lang/ folder and DesignGuard's 1.x-name hints are gone. JS: the androidx shape port progress.js and both bin scripts each carried lives once in resources/js/shapes.js (the generated SVGs are unchanged); util.js holds ringIndex(), ms(), reopenGuard() and remember(), which were written out several times; listeners are released through AbortController; tooltip.js's hoverPopover() serves the rich tooltip too. CSS: every rule for an element inside the navigation rail queries `--md-navigation-rail-value` instead of repeating the seven collapsed conditions under five media branches; badge, alert, progress, slider and button read one non-inheriting colour-role table (components/color.css); the dialog chrome, the submenu's popover chrome, the chip's state layer and touch target, and the visually-hidden inputs use the shared rules they copied; foundation/tokens.css is folded into foundation.css. Views: Support\Field and Support\Link replace the error-key, bound-value and link-attribute blocks copied into the fields and link components; the timepicker period group, the menu filter and the showcase head are partials; the datepicker's steppers and entry fields are loops; component docblocks no longer restate SKILL.md. Tests and tooling: one dataset-driven ComponentStylesheetsTest replaces four per-group files, DesignGuardTest and the layout-component tests use datasets, browser tests share one ready() helper, CSS parsing lives in ComponentStylesheet alone. docs/audits and the finding IDs citing it are removed, as are pestphp/pest-plugin-laravel, the unused composer scripts and check:font; the lint job runs in the feature job, which now installs node packages so the prebuilt-stylesheet staleness test runs in CI. Feature suite 1177 passed, Chrome browser suite 299 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
471d927e64
commit
247c596c3a
+6
-486
@@ -20,12 +20,9 @@
|
||||
* compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialShapes.kt
|
||||
* compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/ShapeUtil.kt
|
||||
* compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Matrix.kt (rotateZ, scale)
|
||||
* graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/CornerRounding.kt
|
||||
* graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Cubic.kt
|
||||
* graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Point.kt
|
||||
* graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt
|
||||
* graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Shapes.kt
|
||||
* graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Utils.kt
|
||||
*
|
||||
* The RoundedPolygon construction itself (CornerRounding, Cubic, Point, RoundedPolygon, Shapes,
|
||||
* Utils) is shared with bin/loading-indicator.mjs and progress.js: see resources/js/shapes.js.
|
||||
*
|
||||
* Copyright 2022-2024 The Android Open Source Project
|
||||
*
|
||||
@@ -45,480 +42,14 @@
|
||||
import { mkdirSync, readdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { circlePolygon, length, minus, plus, point, polygonFromVertices, regularPolygon, rounding, split, star, times, turningPoints, UNROUNDED } from '../resources/js/shapes.js'
|
||||
|
||||
const OUTPUT = 'resources/svg/shapes'
|
||||
const VIEWBOX = 100
|
||||
const FILL = 96
|
||||
|
||||
const DISTANCE_EPSILON = 1e-4
|
||||
|
||||
// Point.kt / Utils.kt ------------------------------------------------------------------
|
||||
|
||||
const point = (x, y) => ({ x, y })
|
||||
const plus = (a, b) => point(a.x + b.x, a.y + b.y)
|
||||
const minus = (a, b) => point(a.x - b.x, a.y - b.y)
|
||||
const times = (a, k) => point(a.x * k, a.y * k)
|
||||
const div = (a, k) => point(a.x / k, a.y / k)
|
||||
const dot = (a, b) => a.x * b.x + a.y * b.y
|
||||
const length = (a) => Math.sqrt(a.x * a.x + a.y * a.y)
|
||||
const rotate90 = (a) => point(-a.y, a.x)
|
||||
const clockwise = (a, b) => a.x * b.y - a.y * b.x > 0
|
||||
const lerp = (a, b, f) => (1 - f) * a + f * b
|
||||
const lerpPoint = (a, b, f) => point(lerp(a.x, b.x, f), lerp(a.y, b.y, f))
|
||||
|
||||
function direction(a) {
|
||||
const d = length(a)
|
||||
|
||||
if (!(d > 0)) {
|
||||
throw new Error("Can't get the direction of a 0-length vector")
|
||||
}
|
||||
|
||||
return div(a, d)
|
||||
}
|
||||
|
||||
const radialToCartesian = (radius, angle) => point(Math.cos(angle) * radius, Math.sin(angle) * radius)
|
||||
const convex = (previous, current, next) => clockwise(minus(current, previous), minus(next, current))
|
||||
|
||||
// Cubic.kt ------------------------------------------------------------------------------
|
||||
|
||||
/** A cubic is [anchor0X, anchor0Y, control0X, control0Y, control1X, control1Y, anchor1X, anchor1Y]. */
|
||||
const cubic = (a0, c0, c1, a1) => [a0.x, a0.y, c0.x, c0.y, c1.x, c1.y, a1.x, a1.y]
|
||||
|
||||
function straightLine(x0, y0, x1, y1) {
|
||||
return [x0, y0, lerp(x0, x1, 1 / 3), lerp(y0, y1, 1 / 3), lerp(x0, x1, 2 / 3), lerp(y0, y1, 2 / 3), x1, y1]
|
||||
}
|
||||
|
||||
function circularArc(centerX, centerY, x0, y0, x1, y1) {
|
||||
const p0d = direction(point(x0 - centerX, y0 - centerY))
|
||||
const p1d = direction(point(x1 - centerX, y1 - centerY))
|
||||
const rotatedP0 = rotate90(p0d)
|
||||
const rotatedP1 = rotate90(p1d)
|
||||
const isClockwise = dot(rotatedP0, point(x1 - centerX, y1 - centerY)) >= 0
|
||||
const cosa = dot(p0d, p1d)
|
||||
|
||||
if (cosa > 0.999) {
|
||||
return straightLine(x0, y0, x1, y1)
|
||||
}
|
||||
|
||||
const k =
|
||||
(((length(point(x0 - centerX, y0 - centerY)) * 4) / 3) *
|
||||
(Math.sqrt(2 * (1 - cosa)) - Math.sqrt(1 - cosa * cosa))) /
|
||||
(1 - cosa) *
|
||||
(isClockwise ? 1 : -1)
|
||||
|
||||
return [x0, y0, x0 + rotatedP0.x * k, y0 + rotatedP0.y * k, x1 - rotatedP1.x * k, y1 - rotatedP1.y * k, x1, y1]
|
||||
}
|
||||
|
||||
function pointOnCurve(c, t) {
|
||||
const u = 1 - t
|
||||
|
||||
return point(
|
||||
c[0] * (u * u * u) + c[2] * (3 * t * u * u) + c[4] * (3 * t * t * u) + c[6] * (t * t * t),
|
||||
c[1] * (u * u * u) + c[3] * (3 * t * u * u) + c[5] * (3 * t * t * u) + c[7] * (t * t * t),
|
||||
)
|
||||
}
|
||||
|
||||
function split(c, t) {
|
||||
const u = 1 - t
|
||||
const p = pointOnCurve(c, t)
|
||||
|
||||
return [
|
||||
[
|
||||
c[0],
|
||||
c[1],
|
||||
c[0] * u + c[2] * t,
|
||||
c[1] * u + c[3] * t,
|
||||
c[0] * (u * u) + c[2] * (2 * u * t) + c[4] * (t * t),
|
||||
c[1] * (u * u) + c[3] * (2 * u * t) + c[5] * (t * t),
|
||||
p.x,
|
||||
p.y,
|
||||
],
|
||||
[
|
||||
p.x,
|
||||
p.y,
|
||||
c[2] * (u * u) + c[4] * (2 * u * t) + c[6] * (t * t),
|
||||
c[3] * (u * u) + c[5] * (2 * u * t) + c[7] * (t * t),
|
||||
c[4] * u + c[6] * t,
|
||||
c[5] * u + c[7] * t,
|
||||
c[6],
|
||||
c[7],
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
const reverse = (c) => [c[6], c[7], c[4], c[5], c[2], c[3], c[0], c[1]]
|
||||
const zeroLength = (c) => Math.abs(c[0] - c[6]) < DISTANCE_EPSILON && Math.abs(c[1] - c[7]) < DISTANCE_EPSILON
|
||||
|
||||
/** The parameters in (0, 1) where one axis of a cubic turns: the roots of its derivative. */
|
||||
function turningPoints(c, axis) {
|
||||
const [p0, p1, p2, p3] = [c[axis], c[axis + 2], c[axis + 4], c[axis + 6]]
|
||||
const a = -p0 + 3 * p1 - 3 * p2 + p3
|
||||
const b = 2 * (p0 - 2 * p1 + p2)
|
||||
const k = p1 - p0
|
||||
const roots = []
|
||||
|
||||
if (Math.abs(a) < 1e-9) {
|
||||
if (Math.abs(b) > 1e-9) {
|
||||
roots.push(-k / b)
|
||||
}
|
||||
} else if (b * b - 4 * a * k >= 0) {
|
||||
const root = Math.sqrt(b * b - 4 * a * k)
|
||||
roots.push((-b + root) / (2 * a), (-b - root) / (2 * a))
|
||||
}
|
||||
|
||||
return roots.filter((t) => t > 1e-6 && t < 1 - 1e-6)
|
||||
}
|
||||
|
||||
/** Axis-aligned bounds of one cubic: of all four points when approximate, else of the curve itself. */
|
||||
function cubicBounds(c, approximate) {
|
||||
const xs = [c[0], c[6]]
|
||||
const ys = [c[1], c[7]]
|
||||
|
||||
if (approximate) {
|
||||
xs.push(c[2], c[4])
|
||||
ys.push(c[3], c[5])
|
||||
} else {
|
||||
turningPoints(c, 0).forEach((t) => xs.push(pointOnCurve(c, t).x))
|
||||
turningPoints(c, 1).forEach((t) => ys.push(pointOnCurve(c, t).y))
|
||||
}
|
||||
|
||||
return [Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)]
|
||||
}
|
||||
|
||||
// CornerRounding.kt ---------------------------------------------------------------------
|
||||
|
||||
const rounding = (radius = 0, smoothing = 0) => ({ radius, smoothing })
|
||||
const UNROUNDED = rounding()
|
||||
|
||||
// RoundedPolygon.kt ---------------------------------------------------------------------
|
||||
|
||||
class RoundedCorner {
|
||||
constructor(p0, p1, p2, cornerRounding) {
|
||||
this.p0 = p0
|
||||
this.p1 = p1
|
||||
this.p2 = p2
|
||||
|
||||
const v01 = minus(p0, p1)
|
||||
const v21 = minus(p2, p1)
|
||||
const d01 = length(v01)
|
||||
const d21 = length(v21)
|
||||
|
||||
if (d01 > 0 && d21 > 0) {
|
||||
this.d1 = div(v01, d01)
|
||||
this.d2 = div(v21, d21)
|
||||
this.cornerRadius = cornerRounding?.radius ?? 0
|
||||
this.smoothing = cornerRounding?.smoothing ?? 0
|
||||
this.cosAngle = dot(this.d1, this.d2)
|
||||
this.sinAngle = Math.sqrt(1 - this.cosAngle * this.cosAngle)
|
||||
this.expectedRoundCut = this.sinAngle > 1e-3 ? (this.cornerRadius * (this.cosAngle + 1)) / this.sinAngle : 0
|
||||
} else {
|
||||
this.d1 = point(0, 0)
|
||||
this.d2 = point(0, 0)
|
||||
this.cornerRadius = 0
|
||||
this.smoothing = 0
|
||||
this.cosAngle = 0
|
||||
this.sinAngle = 0
|
||||
this.expectedRoundCut = 0
|
||||
}
|
||||
}
|
||||
|
||||
get expectedCut() {
|
||||
return (1 + this.smoothing) * this.expectedRoundCut
|
||||
}
|
||||
|
||||
getCubics(allowedCut0, allowedCut1 = allowedCut0) {
|
||||
const allowedCut = Math.min(allowedCut0, allowedCut1)
|
||||
|
||||
if (
|
||||
this.expectedRoundCut < DISTANCE_EPSILON ||
|
||||
allowedCut < DISTANCE_EPSILON ||
|
||||
this.cornerRadius < DISTANCE_EPSILON
|
||||
) {
|
||||
return [straightLine(this.p1.x, this.p1.y, this.p1.x, this.p1.y)]
|
||||
}
|
||||
|
||||
const actualRoundCut = Math.min(allowedCut, this.expectedRoundCut)
|
||||
const actualSmoothing0 = this.actualSmoothing(allowedCut0)
|
||||
const actualSmoothing1 = this.actualSmoothing(allowedCut1)
|
||||
const actualR = (this.cornerRadius * actualRoundCut) / this.expectedRoundCut
|
||||
const centerDistance = Math.sqrt(actualR * actualR + actualRoundCut * actualRoundCut)
|
||||
const center = plus(this.p1, times(direction(div(plus(this.d1, this.d2), 2)), centerDistance))
|
||||
const circleIntersection0 = plus(this.p1, times(this.d1, actualRoundCut))
|
||||
const circleIntersection2 = plus(this.p1, times(this.d2, actualRoundCut))
|
||||
const flanking0 = this.flankingCurve(
|
||||
actualRoundCut,
|
||||
actualSmoothing0,
|
||||
this.p1,
|
||||
this.p0,
|
||||
circleIntersection0,
|
||||
circleIntersection2,
|
||||
center,
|
||||
actualR,
|
||||
)
|
||||
const flanking2 = reverse(
|
||||
this.flankingCurve(
|
||||
actualRoundCut,
|
||||
actualSmoothing1,
|
||||
this.p1,
|
||||
this.p2,
|
||||
circleIntersection2,
|
||||
circleIntersection0,
|
||||
center,
|
||||
actualR,
|
||||
),
|
||||
)
|
||||
|
||||
return [
|
||||
flanking0,
|
||||
circularArc(center.x, center.y, flanking0[6], flanking0[7], flanking2[0], flanking2[1]),
|
||||
flanking2,
|
||||
]
|
||||
}
|
||||
|
||||
actualSmoothing(allowedCut) {
|
||||
if (allowedCut > this.expectedCut) {
|
||||
return this.smoothing
|
||||
}
|
||||
|
||||
if (allowedCut > this.expectedRoundCut) {
|
||||
return (this.smoothing * (allowedCut - this.expectedRoundCut)) / (this.expectedCut - this.expectedRoundCut)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
flankingCurve(actualRoundCut, smoothing, corner, sideStart, intersection, otherIntersection, circleCenter, actualR) {
|
||||
const sideDirection = direction(minus(sideStart, corner))
|
||||
const curveStart = plus(corner, times(sideDirection, actualRoundCut * (1 + smoothing)))
|
||||
const p = lerpPoint(intersection, div(plus(intersection, otherIntersection), 2), smoothing)
|
||||
const curveEnd = plus(circleCenter, times(direction(minus(p, circleCenter)), actualR))
|
||||
const circleTangent = rotate90(minus(curveEnd, circleCenter))
|
||||
const anchorEnd = lineIntersection(sideStart, sideDirection, curveEnd, circleTangent) ?? intersection
|
||||
const anchorStart = div(plus(curveStart, times(anchorEnd, 2)), 3)
|
||||
|
||||
return cubic(curveStart, anchorStart, anchorEnd, curveEnd)
|
||||
}
|
||||
}
|
||||
|
||||
function lineIntersection(p0, d0, p1, d1) {
|
||||
const rotatedD1 = rotate90(d1)
|
||||
const den = dot(d0, rotatedD1)
|
||||
|
||||
if (Math.abs(den) < DISTANCE_EPSILON) {
|
||||
return null
|
||||
}
|
||||
|
||||
const num = dot(minus(p1, p0), rotatedD1)
|
||||
|
||||
if (Math.abs(den) < DISTANCE_EPSILON * Math.abs(num)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return plus(p0, times(d0, num / den))
|
||||
}
|
||||
|
||||
/**
|
||||
* A polygon as androidx keeps it: its features (corners and the edges between them, each a
|
||||
* list of cubics) and a centre. `cubics` flattens the features exactly as RoundedPolygon does.
|
||||
*/
|
||||
class RoundedPolygon {
|
||||
constructor(features, center) {
|
||||
this.features = features
|
||||
this.center = center
|
||||
this.cubics = flatten(features, center)
|
||||
}
|
||||
|
||||
transformed(f) {
|
||||
const move = (c) => {
|
||||
const out = []
|
||||
|
||||
for (let i = 0; i < 8; i += 2) {
|
||||
const p = f(c[i], c[i + 1])
|
||||
out.push(p.x, p.y)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
return new RoundedPolygon(
|
||||
this.features.map((feature) => ({ ...feature, cubics: feature.cubics.map(move) })),
|
||||
f(this.center.x, this.center.y),
|
||||
)
|
||||
}
|
||||
|
||||
bounds(approximate) {
|
||||
const all = this.cubics.map((c) => (zeroLength(c) ? [c[0], c[1], c[0], c[1]] : cubicBounds(c, approximate)))
|
||||
|
||||
return [
|
||||
Math.min(...all.map((b) => b[0])),
|
||||
Math.min(...all.map((b) => b[1])),
|
||||
Math.max(...all.map((b) => b[2])),
|
||||
Math.max(...all.map((b) => b[3])),
|
||||
]
|
||||
}
|
||||
|
||||
normalized() {
|
||||
const [left, top, right, bottom] = this.bounds(true)
|
||||
const width = right - left
|
||||
const height = bottom - top
|
||||
const side = Math.max(width, height)
|
||||
const offsetX = (side - width) / 2 - left
|
||||
const offsetY = (side - height) / 2 - top
|
||||
|
||||
return this.transformed((x, y) => point((x + offsetX) / side, (y + offsetY) / side))
|
||||
}
|
||||
}
|
||||
|
||||
function flatten(features, center) {
|
||||
const out = []
|
||||
let firstCubic = null
|
||||
let lastCubic = null
|
||||
let firstFeatureSplitStart = null
|
||||
let firstFeatureSplitEnd = null
|
||||
|
||||
if (features.length > 0 && features[0].cubics.length === 3) {
|
||||
const [start, end] = split(features[0].cubics[1], 0.5)
|
||||
firstFeatureSplitStart = [features[0].cubics[0], start]
|
||||
firstFeatureSplitEnd = [end, features[0].cubics[2]]
|
||||
}
|
||||
|
||||
for (let i = 0; i <= features.length; i++) {
|
||||
let featureCubics
|
||||
|
||||
if (i === 0 && firstFeatureSplitEnd !== null) {
|
||||
featureCubics = firstFeatureSplitEnd
|
||||
} else if (i === features.length) {
|
||||
if (firstFeatureSplitStart === null) {
|
||||
break
|
||||
}
|
||||
|
||||
featureCubics = firstFeatureSplitStart
|
||||
} else {
|
||||
featureCubics = features[i].cubics
|
||||
}
|
||||
|
||||
for (const c of featureCubics) {
|
||||
if (!zeroLength(c)) {
|
||||
if (lastCubic !== null) {
|
||||
out.push(lastCubic)
|
||||
}
|
||||
|
||||
lastCubic = c
|
||||
|
||||
if (firstCubic === null) {
|
||||
firstCubic = c
|
||||
}
|
||||
} else if (lastCubic !== null) {
|
||||
lastCubic = [...lastCubic]
|
||||
lastCubic[6] = c[0]
|
||||
lastCubic[7] = c[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lastCubic !== null && firstCubic !== null) {
|
||||
out.push([...lastCubic.slice(0, 6), firstCubic[0], firstCubic[1]])
|
||||
} else {
|
||||
out.push([center.x, center.y, center.x, center.y, center.x, center.y, center.x, center.y])
|
||||
}
|
||||
|
||||
for (let i = 0; i < out.length; i++) {
|
||||
const previous = out[(i + out.length - 1) % out.length]
|
||||
|
||||
if (
|
||||
Math.abs(out[i][0] - previous[6]) > DISTANCE_EPSILON ||
|
||||
Math.abs(out[i][1] - previous[7]) > DISTANCE_EPSILON
|
||||
) {
|
||||
throw new Error('RoundedPolygon must be contiguous')
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
/** RoundedPolygon(vertices, rounding, perVertexRounding, centerX, centerY) */
|
||||
function polygonFromVertices(vertices, { cornerRounding = UNROUNDED, perVertexRounding = null, center = null } = {}) {
|
||||
const n = vertices.length
|
||||
|
||||
if (n < 3) {
|
||||
throw new Error('Polygons must have at least 3 vertices')
|
||||
}
|
||||
|
||||
if (perVertexRounding !== null && perVertexRounding.length !== n) {
|
||||
throw new Error('perVertexRounding list should be either null or the same size as the number of vertices')
|
||||
}
|
||||
|
||||
const roundedCorners = vertices.map(
|
||||
(vertex, i) =>
|
||||
new RoundedCorner(vertices[(i + n - 1) % n], vertex, vertices[(i + 1) % n], perVertexRounding?.[i] ?? cornerRounding),
|
||||
)
|
||||
|
||||
const cutAdjusts = vertices.map((vertex, i) => {
|
||||
const next = (i + 1) % n
|
||||
const expectedRoundCut = roundedCorners[i].expectedRoundCut + roundedCorners[next].expectedRoundCut
|
||||
const expectedCut = roundedCorners[i].expectedCut + roundedCorners[next].expectedCut
|
||||
const sideSize = length(minus(vertex, vertices[next]))
|
||||
|
||||
if (expectedRoundCut > sideSize) {
|
||||
return [sideSize / expectedRoundCut, 0]
|
||||
}
|
||||
|
||||
if (expectedCut > sideSize) {
|
||||
return [1, (sideSize - expectedRoundCut) / (expectedCut - expectedRoundCut)]
|
||||
}
|
||||
|
||||
return [1, 1]
|
||||
})
|
||||
|
||||
const corners = roundedCorners.map((corner, i) => {
|
||||
const allowedCuts = [0, 1].map((delta) => {
|
||||
const [roundCutRatio, cutRatio] = cutAdjusts[(i + n - 1 + delta) % n]
|
||||
|
||||
return corner.expectedRoundCut * roundCutRatio + (corner.expectedCut - corner.expectedRoundCut) * cutRatio
|
||||
})
|
||||
|
||||
return corner.getCubics(allowedCuts[0], allowedCuts[1])
|
||||
})
|
||||
|
||||
const features = []
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const previous = vertices[(i + n - 1) % n]
|
||||
const next = vertices[(i + 1) % n]
|
||||
const end = corners[i].at(-1)
|
||||
const start = corners[(i + 1) % n][0]
|
||||
|
||||
features.push({ type: 'corner', convex: convex(previous, vertices[i], next), cubics: corners[i] })
|
||||
features.push({ type: 'edge', cubics: [straightLine(end[6], end[7], start[0], start[1])] })
|
||||
}
|
||||
|
||||
if (center === null) {
|
||||
center = point(
|
||||
vertices.reduce((sum, v) => sum + v.x, 0) / n,
|
||||
vertices.reduce((sum, v) => sum + v.y, 0) / n,
|
||||
)
|
||||
}
|
||||
|
||||
return new RoundedPolygon(features, center)
|
||||
}
|
||||
|
||||
// Shapes.kt -----------------------------------------------------------------------------
|
||||
|
||||
/** RoundedPolygon(numVertices, radius, centerX, centerY, rounding, perVertexRounding) */
|
||||
function regularPolygon(numVertices, { radius = 1, cornerRounding = UNROUNDED, perVertexRounding = null } = {}) {
|
||||
const vertices = Array.from({ length: numVertices }, (_, i) =>
|
||||
radialToCartesian(radius, (Math.PI / numVertices) * 2 * i),
|
||||
)
|
||||
|
||||
return polygonFromVertices(vertices, { cornerRounding, perVertexRounding, center: point(0, 0) })
|
||||
}
|
||||
|
||||
function circlePolygon(numVertices = 8, radius = 1) {
|
||||
const polygonRadius = radius / Math.cos(Math.PI / numVertices)
|
||||
|
||||
return regularPolygon(numVertices, { radius: polygonRadius, cornerRounding: rounding(radius) })
|
||||
}
|
||||
|
||||
function rectangle({ width = 2, height = 2, cornerRounding = UNROUNDED, perVertexRounding = null } = {}) {
|
||||
const [left, top, right, bottom] = [-width / 2, -height / 2, width / 2, height / 2]
|
||||
|
||||
@@ -529,17 +60,6 @@ function rectangle({ width = 2, height = 2, cornerRounding = UNROUNDED, perVerte
|
||||
})
|
||||
}
|
||||
|
||||
function star(numVerticesPerRadius, { radius = 1, innerRadius = 0.5, cornerRounding = UNROUNDED } = {}) {
|
||||
const vertices = []
|
||||
|
||||
for (let i = 0; i < numVerticesPerRadius; i++) {
|
||||
vertices.push(radialToCartesian(radius, (Math.PI / numVerticesPerRadius) * 2 * i))
|
||||
vertices.push(radialToCartesian(innerRadius, (Math.PI / numVerticesPerRadius) * (2 * i + 1)))
|
||||
}
|
||||
|
||||
return polygonFromVertices(vertices, { cornerRounding, center: point(0, 0) })
|
||||
}
|
||||
|
||||
// Matrix.kt (rotateZ, scale) and ShapeUtil.kt (RoundedPolygon.transformed(Matrix)) --------
|
||||
|
||||
function rotateZ(degrees) {
|
||||
@@ -865,8 +385,8 @@ function pathData(polygon) {
|
||||
return `${d}Z`
|
||||
}
|
||||
|
||||
/** The geometry, for other build scripts (bin/loading-indicator.mjs); importing this module writes nothing. */
|
||||
export { point, pointOnCurve, split, cubicBounds, RoundedPolygon, SHAPES }
|
||||
/** The shape catalog, for other build scripts (bin/loading-indicator.mjs); importing this module writes nothing. */
|
||||
export { SHAPES }
|
||||
|
||||
if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
mkdirSync(OUTPUT, { recursive: true })
|
||||
|
||||
Reference in New Issue
Block a user