Skip to content

Commit

Permalink
Merge pull request #1142 from tommadams/arc
Browse files Browse the repository at this point in the history
Fix a couple of defects in SVGContext.arc
  • Loading branch information
0xfe authored Sep 20, 2021
2 parents 46e4cac + 658633f commit 07301c1
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 49 deletions.
85 changes: 36 additions & 49 deletions src/svgcontext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// MIT License
// @author Gregory Ristow (2015)

import { RuntimeError, prefix } from './util';
import { RuntimeError, normalizeAngle, prefix } from './util';
import { RenderContext, TextMeasure } from './types/common';

// eslint-disable-next-line
Expand Down Expand Up @@ -523,62 +523,49 @@ export class SVGContext implements RenderContext {
return this;
}

// This is an attempt (hack) to simulate the HTML5 canvas arc method.
arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, antiClockwise: boolean): this {
function normalizeAngle(angle: number) {
while (angle < 0) {
angle += Math.PI * 2;
}
while (angle > Math.PI * 2) {
angle -= Math.PI * 2;
}
return angle;
}

startAngle = normalizeAngle(startAngle);
endAngle = normalizeAngle(endAngle);

// Swap the start and end angles if necessary.
if (startAngle > endAngle) {
const tmp = startAngle;
startAngle = endAngle;
endAngle = tmp;
antiClockwise = !antiClockwise;
}

const delta = endAngle - startAngle;
if (delta > Math.PI) {
this.arcHelper(x, y, radius, startAngle, startAngle + delta / 2, antiClockwise);
this.arcHelper(x, y, radius, startAngle + delta / 2, endAngle, antiClockwise);
const x0 = x + radius * Math.cos(startAngle);
const y0 = y + radius * Math.sin(startAngle);

// Handle the edge case from the Canvas spec where arc length is greater than
// the circle's circumference:
// https://html.spec.whatwg.org/multipage/canvas.html#ellipse-method-steps
if (
(!antiClockwise && endAngle - startAngle > 2 * Math.PI) ||
(antiClockwise && startAngle - endAngle > 2 * Math.PI)
) {
const x1 = x + radius * Math.cos(startAngle + Math.PI);
const y1 = y + radius * Math.sin(startAngle + Math.PI);
// There's no way to specify a completely circular arc in SVG so we have to
// use two semi-circular arcs.
this.path += `M${x0} ${y0} A${radius} ${radius} 0 0 0 ${x1} ${y1} `;
this.path += `A${radius} ${radius} 0 0 0 ${x0} ${y0}`;
this.pen.x = x0;
this.pen.y = y0;
} else {
this.arcHelper(x, y, radius, startAngle, endAngle, antiClockwise);
}
return this;
}

arcHelper(x: number, y: number, radius: number, startAngle: number, endAngle: number, antiClockwise: boolean): void {
const x1 = x + radius * Math.cos(startAngle);
const y1 = y + radius * Math.sin(startAngle);
const x1 = x + radius * Math.cos(endAngle);
const y1 = y + radius * Math.sin(endAngle);

const x2 = x + radius * Math.cos(endAngle);
const y2 = y + radius * Math.sin(endAngle);
startAngle = normalizeAngle(startAngle);
endAngle = normalizeAngle(endAngle);

let largeArcFlag = 0;
let sweepFlag = 0;
if (antiClockwise) {
sweepFlag = 1;
if (endAngle - startAngle < Math.PI) {
largeArcFlag = 1;
let large: boolean;
if (Math.abs(endAngle - startAngle) < Math.PI) {
large = antiClockwise;
} else {
large = !antiClockwise;
}
if (startAngle > endAngle) {
large = !large;
}
} else if (endAngle - startAngle > Math.PI) {
largeArcFlag = 1;
}

this.path += `M${x1} ${y1} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
const sweep = !antiClockwise;

if (!isNaN(this.pen.x) && !isNaN(this.pen.y)) {
this.path += 'M' + this.pen.x + ' ' + this.pen.y;
this.path += `M${x0} ${y0} A${radius} ${radius} 0 ${+large} ${+sweep} ${x1} ${y1}`;
this.pen.x = x1;
this.pen.y = y1;
}
return this;
}

closePath(): this {
Expand Down
11 changes: 11 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,14 @@ export function drawDot(ctx: RenderContext, x: number, y: number, color = '#55')
export function prefix(text: string): string {
return `vf-${text}`;
}

/**
* Convert an arbitrary angle in radians to the equivalent one in the range [0, pi).
*/
export function normalizeAngle(a: number): number {
a = a % (2 * Math.PI);
if (a < 0) {
a += 2 * Math.PI;
}
return a;
}

0 comments on commit 07301c1

Please sign in to comment.