Skip to content

Commit

Permalink
fix: getMinMax
Browse files Browse the repository at this point in the history
  • Loading branch information
qq15725 committed Oct 14, 2024
1 parent 89a53e0 commit e90661d
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 59 deletions.
13 changes: 12 additions & 1 deletion src/curves/Curve.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Matrix3 } from '../math'
import type { PathCommand } from '../svg'
import { Point2D } from '../math'
import { BoundingBox, Point2D } from '../math'
import { pathCommandsToPathData } from '../svg'

export abstract class Curve {
Expand Down Expand Up @@ -131,9 +131,20 @@ export abstract class Curve {

/** overrideable */
getMinMax(min = Point2D.MAX, max = Point2D.MIN): { min: Point2D, max: Point2D } {
this.getPoints().forEach((point) => {
min.x = Math.min(min.x, point.x)
min.y = Math.min(min.y, point.y)
max.x = Math.max(max.x, point.x)
max.y = Math.max(max.y, point.y)
})
return { min, max }
}

getBoundingBox(): BoundingBox {
const { min, max } = this.getMinMax()
return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y)
}

/** overrideable */
getCommands(): PathCommand[] {
return []
Expand Down
47 changes: 32 additions & 15 deletions src/curves/EllipseCurve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,37 +62,35 @@ export class EllipseCurve extends Curve {
}

override getCommands(): PathCommand[] {
const { x, y, radiusX, radiusY, startAngle, endAngle, clockwise } = this
const anticlockwise = !clockwise
const startX = x + radiusX * Math.cos(startAngle)
const startY = y + radiusY * Math.sin(startAngle)
const endX = x + radiusX * Math.cos(endAngle)
const endY = y + radiusY * Math.sin(endAngle)
const { x: cx, y: cy, radiusX: rx, radiusY: ry, startAngle, endAngle, clockwise, rotation } = this
const startX = cx + rx * Math.cos(startAngle) * Math.cos(rotation) - ry * Math.sin(startAngle) * Math.sin(rotation)
const startY = cy + rx * Math.cos(startAngle) * Math.sin(rotation) + ry * Math.sin(startAngle) * Math.cos(rotation)
const angleDiff = Math.abs(startAngle - endAngle)
const largeArcFlag = angleDiff > Math.PI ? 1 : 0
const sweepFlag = anticlockwise ? 0 : 1
const midX = x + radiusX * Math.cos(startAngle + (endAngle - startAngle) / 2)
const midY = y + radiusY * Math.sin(startAngle + (endAngle - startAngle) / 2)
const sweepFlag = clockwise ? 1 : 0
const angle = rotation * 180 / Math.PI
if (angleDiff >= 2 * Math.PI) {
const midAngle = startAngle + Math.PI
const midX = cx + rx * Math.cos(midAngle) * Math.cos(rotation) - ry * Math.sin(midAngle) * Math.sin(rotation)
const midY = cy + rx * Math.cos(midAngle) * Math.sin(rotation) + ry * Math.sin(midAngle) * Math.cos(rotation)
return [
{ type: 'M', x: startX, y: startY },
{ type: 'A', rx: radiusX, ry: radiusY, angle: 0, largeArcFlag: 1, sweepFlag, x: midX, y: midY },
{ type: 'A', rx: radiusX, ry: radiusY, angle: 0, largeArcFlag: 1, sweepFlag, x: startX, y: startY },
{ type: 'A', rx, ry, angle, largeArcFlag: 0, sweepFlag, x: midX, y: midY },
{ type: 'A', rx, ry, angle, largeArcFlag: 0, sweepFlag, x: startX, y: startY },
]
}
else {
const endX = cx + rx * Math.cos(endAngle) * Math.cos(rotation) - ry * Math.sin(endAngle) * Math.sin(rotation)
const endY = cy + rx * Math.cos(endAngle) * Math.sin(rotation) + ry * Math.sin(endAngle) * Math.cos(rotation)
return [
{ type: 'M', x: startX, y: startY },
{ type: 'A', rx: radiusX, ry: radiusY, angle: 0, largeArcFlag, sweepFlag, x: endX, y: endY },
{ type: 'A', rx, ry, angle, largeArcFlag, sweepFlag, x: endX, y: endY },
]
}
}

override drawTo(ctx: CanvasRenderingContext2D): this {
const { x, y, radiusX, radiusY, rotation, startAngle, endAngle, clockwise } = this
const startX = x + radiusX * Math.cos(startAngle)
const startY = y + radiusY * Math.sin(startAngle)
ctx.moveTo(startX, startY)
ctx.ellipse(
x,
y,
Expand Down Expand Up @@ -120,6 +118,25 @@ export class EllipseCurve extends Curve {
return this
}

override getMinMax(min: Point2D = Point2D.MAX, max: Point2D = Point2D.MIN): { min: Point2D, max: Point2D } {
const { x: cx, y: cy, radiusX: rx, radiusY: ry, rotation: theta } = this
const cosTheta = Math.cos(theta)
const sinTheta = Math.sin(theta)
const halfWidth = Math.sqrt(
rx * rx * cosTheta * cosTheta
+ ry * ry * sinTheta * sinTheta,
)
const halfHeight = Math.sqrt(
rx * rx * sinTheta * sinTheta
+ ry * ry * cosTheta * cosTheta,
)
min.x = Math.min(min.x, cx - halfWidth)
min.y = Math.min(min.y, cy - halfHeight)
max.x = Math.max(max.x, cx + halfWidth)
max.y = Math.max(max.y, cy + halfHeight)
return { min, max }
}

override copy(source: EllipseCurve): this {
super.copy(source)
this.x = source.x
Expand Down
6 changes: 5 additions & 1 deletion src/paths/CurvePath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,11 @@ export class CurvePath extends Curve {
last = point
}
}
if (this.autoClose && points.length > 1 && !points[points.length - 1].equals(points[0])) {
if (
this.autoClose
&& points.length > 1
&& !points[points.length - 1].equals(points[0])
) {
points.push(points[0])
}
return points
Expand Down
41 changes: 6 additions & 35 deletions src/paths/Path2D.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,35 +76,10 @@ export class Path2D<T = any> {
return this
}

// TODO
// eslint-disable-next-line unused-imports/no-unused-vars
arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): this {
const point = this.currentPath.currentPoint
const currentX = point.x
const currentY = point.y
const dx1 = x1 - currentX
const dy1 = y1 - currentY
const dx2 = x2 - x1
const dy2 = y2 - y1
const len1 = Math.sqrt(dx1 * dx1 + dy1 * dy1)
const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2)
if (len1 < radius || len2 < radius) {
this.lineTo(x2, y2)
return this
}
const unitV1 = { x: dx1 / len1, y: dy1 / len1 }
const unitV2 = { x: dx2 / len2, y: dy2 / len2 }
const centerX = x1 - unitV1.y * radius
const centerY = y1 + unitV1.x * radius
const startAngle = Math.atan2(unitV1.y, unitV1.x)
const endAngle = Math.atan2(unitV2.y, unitV2.x)
let angleDiff = endAngle - startAngle
if (angleDiff > Math.PI) {
angleDiff -= 2 * Math.PI
}
else if (angleDiff < -Math.PI) {
angleDiff += 2 * Math.PI
}
this.arc(centerX, centerY, radius, startAngle, startAngle + angleDiff, false)
this.lineTo(x2, y2)
console.warn('Method arcTo not supported yet')
return this
}

Expand Down Expand Up @@ -150,12 +125,7 @@ export class Path2D<T = any> {

getBoundingBox(): BoundingBox {
const { min, max } = this.getMinMax()
return new BoundingBox(
min.x,
min.y,
max.x - min.x,
max.y - min.y,
)
return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y)
}

getCommands(): PathCommand[] {
Expand All @@ -168,7 +138,8 @@ export class Path2D<T = any> {

getSvgString(): string {
const { x, y, width, height } = this.getBoundingBox()
return `<svg viewBox="${x} ${y} ${width} ${height}" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="currentColor" d="${this.getData()}"></path></svg>`
const strokeWidth = 1
return `<svg viewBox="${x - strokeWidth} ${y - strokeWidth} ${width + strokeWidth * 2} ${height + strokeWidth * 2}" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="currentColor" d="${this.getData()}"></path></svg>`
}

getSvgDataUri(): string {
Expand Down
18 changes: 11 additions & 7 deletions src/svg-dom/parseSvg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import { parseNode } from './parseNode'
const dataUri = 'data:image/svg+xml;'
const base64DataUri = `${dataUri}base64,`
const utf8DataUri = `${dataUri}charset=utf8,`

export function parseSvg(svg: string | SVGElement): Path2D[] {
let node
let xml
export function parseSvgToDom(svg: string | SVGElement): SVGElement {
if (typeof svg === 'string') {
let xml
if (svg.startsWith(base64DataUri)) {
svg = svg.substring(base64DataUri.length, svg.length)
xml = atob(svg)
Expand All @@ -20,12 +18,18 @@ export function parseSvg(svg: string | SVGElement): Path2D[] {
else {
xml = svg
}
node = new DOMParser().parseFromString(xml, 'image/svg+xml').documentElement as unknown as SVGElement
return new DOMParser().parseFromString(
xml,
'image/svg+xml',
).documentElement as unknown as SVGElement
}
else {
node = svg
return svg
}
return parseNode(node, {
}

export function parseSvg(svg: string | SVGElement): Path2D[] {
return parseNode(parseSvgToDom(svg), {
fill: '#000',
fillOpacity: 1,
strokeOpacity: 1,
Expand Down

0 comments on commit e90661d

Please sign in to comment.