From e90661d5007d19f6d0a4de68be6027bcb3629ff9 Mon Sep 17 00:00:00 2001 From: wengxiangmin <157215725@qq.com> Date: Mon, 14 Oct 2024 15:52:09 +0800 Subject: [PATCH] fix: getMinMax --- src/curves/Curve.ts | 13 ++++++++++- src/curves/EllipseCurve.ts | 47 ++++++++++++++++++++++++++------------ src/paths/CurvePath.ts | 6 ++++- src/paths/Path2D.ts | 41 +++++---------------------------- src/svg-dom/parseSvg.ts | 18 +++++++++------ 5 files changed, 66 insertions(+), 59 deletions(-) diff --git a/src/curves/Curve.ts b/src/curves/Curve.ts index daefcbc..9ec8ee7 100644 --- a/src/curves/Curve.ts +++ b/src/curves/Curve.ts @@ -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 { @@ -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 [] diff --git a/src/curves/EllipseCurve.ts b/src/curves/EllipseCurve.ts index bbdaeb2..c74f2ff 100644 --- a/src/curves/EllipseCurve.ts +++ b/src/curves/EllipseCurve.ts @@ -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, @@ -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 diff --git a/src/paths/CurvePath.ts b/src/paths/CurvePath.ts index 5080296..dcdda3d 100644 --- a/src/paths/CurvePath.ts +++ b/src/paths/CurvePath.ts @@ -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 diff --git a/src/paths/Path2D.ts b/src/paths/Path2D.ts index 12998bd..66a35b3 100644 --- a/src/paths/Path2D.ts +++ b/src/paths/Path2D.ts @@ -76,35 +76,10 @@ export class Path2D { 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 } @@ -150,12 +125,7 @@ export class Path2D { 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[] { @@ -168,7 +138,8 @@ export class Path2D { getSvgString(): string { const { x, y, width, height } = this.getBoundingBox() - return `` + const strokeWidth = 1 + return `` } getSvgDataUri(): string { diff --git a/src/svg-dom/parseSvg.ts b/src/svg-dom/parseSvg.ts index 55f070b..c0187b5 100644 --- a/src/svg-dom/parseSvg.ts +++ b/src/svg-dom/parseSvg.ts @@ -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) @@ -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,