@aurigma/design-atoms-model
Version:
Design Atoms is a part of Customer's Canvas SDK which allows for manipulating individual design elements through your code.
412 lines • 15.6 kB
JavaScript
import { PointF } from "./PointF";
import { EventObject } from "../EventObject";
import { EqualsOfFloatNumbers } from "./Common";
import { RectangleF } from "./RectangleF";
export class PathSegment {
constructor(name, points) {
this._name = "";
this._points = [];
this._name = (name) ? name.toUpperCase() : "";
this._points = (points) ? points : [];
}
get name() {
return this._name;
}
getPoint(index) {
return this._points[index];
}
get length() {
return this._points.length;
}
transform(transform, center) {
for (var i = 0; i < this.length; i++) {
this.getPoint(i).transform(transform, center);
}
}
rotateAt(angle, center) {
for (var i = 0; i < this.length; i++)
this.getPoint(i).rotateAt(angle, center);
}
translate(offsetX, offsetY) {
for (var i = 0; i < this.length; i++)
this.getPoint(i).translate(offsetX, offsetY);
}
scale(scaleX, scaleY) {
for (var i = 0; i < this.length; i++)
this.getPoint(i).scale(scaleX, scaleY);
}
draw(ctx) {
switch (this.name) {
case "Z":
ctx.closePath();
break;
case "M":
ctx.moveTo(this.getPoint(0).x, this.getPoint(0).y);
break;
case "L":
ctx.lineTo(this.getPoint(0).x, this.getPoint(0).y);
break;
case "Q":
ctx.quadraticCurveTo(this.getPoint(0).x, this.getPoint(0).y, this.getPoint(1).x, this.getPoint(1).y);
break;
case "C":
ctx.bezierCurveTo(this.getPoint(0).x, this.getPoint(0).y, this.getPoint(1).x, this.getPoint(1).y, this.getPoint(2).x, this.getPoint(2).y);
break;
}
}
toString() {
var s = this.name;
for (var i = 0; i < this.length; i++) {
s += ` ${this.getPoint(i).x} ${this.getPoint(i).y}`;
}
return s;
}
clone() {
return new PathSegment(this.name, this._points.map(p => p.clone()));
}
}
export class Path {
constructor(path) {
this._pathChangedEvent = new EventObject();
this._segments = [];
if (path != null)
this._fromString(path);
}
get length() {
return this._segments.length;
}
get segments() {
return this._segments;
}
addPathChanged(handler) {
this._pathChangedEvent.add(handler);
}
removePathChanged(handler) {
this._pathChangedEvent.remove(handler);
}
moveTo(x, y) {
this._segments.push(new PathSegment("M", [new PointF(x, y)]));
this._onPathChanged();
}
lineTo(x, y) {
this._segments.push(new PathSegment("L", [new PointF(x, y)]));
this._onPathChanged();
}
quadraticTo(cx, cy, x, y) {
this._segments.push(new PathSegment("Q", [new PointF(cx, cy), new PointF(x, y)]));
this._onPathChanged();
}
cubicTo(cx1, cy1, cx2, cy2, x, y) {
this._segments.push(new PathSegment("C", [new PointF(cx1, cy1), new PointF(cx2, cy2), new PointF(x, y)]));
this._onPathChanged();
}
close() {
this._segments.push(new PathSegment("Z"));
this._onPathChanged();
}
_onPathChanged() {
this._pathChangedEvent.notify();
}
transform(transform, center) {
for (var i = 0; i < this.length; i++)
this._segments[i].transform(transform, center);
this._onPathChanged();
}
rotateAt(angle, center) {
for (var i = 0; i < this.length; i++)
this._segments[i].rotateAt(angle, center);
this._onPathChanged();
}
translate(offsetX, offsetY) {
for (var i = 0; i < this.length; i++)
this._segments[i].translate(offsetX, offsetY);
this._onPathChanged();
}
scale(scaleX, scaleY) {
for (var i = 0; i < this.length; i++)
this._segments[i].scale(scaleX, scaleY);
this._onPathChanged();
}
calculateBounds() {
const rects = [];
let start = new PointF(0, 0);
const getMinMaxRect = (xArray, yArray) => {
const minX = Math.min(...xArray);
const maxX = Math.max(...xArray);
const minY = Math.min(...yArray);
const maxY = Math.max(...yArray);
return new RectangleF(minX, minY, maxX - minX, maxY - minY);
};
const addLineRect = (lineStart, segment) => {
const end = segment.getPoint(0);
const rect = getMinMaxRect([lineStart.x, end.x], [lineStart.y, end.y]);
rects.push(rect);
return end;
};
const cubicBezierMinMax = (x0, y0, x1, y1, x2, y2, x3, y3) => {
const xArr = [x0, x3];
const yArr = [y0, y3];
const tArr = [];
let a, b, c;
let t, t1, t2;
let b2ac, sqrt_b2ac;
for (let i = 0; i < 2; ++i) {
if (i === 0) {
b = 6 * x0 - 12 * x1 + 6 * x2;
a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
c = 3 * x1 - 3 * x0;
}
else {
b = 6 * y0 - 12 * y1 + 6 * y2;
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
c = 3 * y1 - 3 * y0;
}
if (Math.abs(a) < 1e-12) {
if (Math.abs(b) < 1e-12)
continue;
t = -c / b;
if (0 < t && t < 1)
tArr.push(t);
continue;
}
b2ac = b * b - 4 * c * a;
if (b2ac < 0) {
if (Math.abs(b2ac) < 1e-12) {
t = -b / (2 * a);
if (0 < t && t < 1)
tArr.push(t);
}
continue;
}
sqrt_b2ac = Math.sqrt(b2ac);
t1 = (-b + sqrt_b2ac) / (2 * a);
if (0 < t1 && t1 < 1)
tArr.push(t1);
t2 = (-b - sqrt_b2ac) / (2 * a);
if (0 < t2 && t2 < 1)
tArr.push(t2);
}
let j = tArr.length;
while (j-- > 0) {
t = tArr[j];
const mt = 1 - t;
xArr.push((mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3));
yArr.push((mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3));
}
return getMinMaxRect(xArr, yArr);
};
const quadraticBezierMinMax = (x0, y0, x1, y1, x2, y2) => {
const xArr = [x0, x2];
const yArr = [y0, y2];
for (let i = 0; i < 2; ++i) {
const a = i === 0 ? x0 - 2 * x1 + x2 : y0 - 2 * y1 + y2;
const b = i === 0 ? -2 * x0 + 2 * x1 : -2 * y0 + 2 * y1;
const c = i === 0 ? x0 : y0;
if (Math.abs(a) > 1e-12) {
const t = -b / (2 * a);
if (0 < t && t < 1) {
const v = a * t * t + b * t + c;
xArr.push(v);
yArr.push(v);
}
}
}
return getMinMaxRect(xArr, yArr);
};
const addQuadraticCurveRect = (curveStart, segment) => {
const control = segment.getPoint(0);
const end = segment.getPoint(1);
const rect = quadraticBezierMinMax(curveStart.x, curveStart.y, control.x, control.y, end.x, end.y);
rects.push(rect);
return end;
};
const addCubicCurveRect = (curveStart, segment) => {
const control0 = segment.getPoint(0);
const control1 = segment.getPoint(1);
const end = segment.getPoint(2);
const rect = cubicBezierMinMax(curveStart.x, curveStart.y, control0.x, control0.y, control1.x, control1.y, end.x, end.y);
rects.push(rect);
return end;
};
const addSegmentRect = (currentStart, segment) => {
switch (segment.name) {
case "Z":
return currentStart;
case "M":
return segment.getPoint(0);
case "L":
return addLineRect(currentStart, segment);
case "C":
return addCubicCurveRect(currentStart, segment);
case "Q":
return addQuadraticCurveRect(currentStart, segment);
default:
throw new Error(`Unexpected segment type: ${segment.name}`);
}
};
for (const segment of this._segments)
start = addSegmentRect(start, segment);
if (rects.length === 0)
return new RectangleF(0, 0, 0, 0);
let current = rects[0];
for (const rect of rects.slice(1))
current = RectangleF.union(current, rect);
return current;
}
draw(ctx) {
if (!ctx)
return;
ctx.beginPath();
for (var i = 0; i < this.length; i++)
this._segments[i].draw(ctx);
}
toString() {
var s = "";
for (var i = 0; i < this.length; i++) {
s += this._segments[i].toString();
if (i < this._segments.length - 1)
s += " ";
}
return s;
}
clone() {
return new Path(this.toString());
}
equals(p) {
return this.isEqual(p);
}
isEqual(path) {
if (path == null)
return false;
return (this.toString() === path.toString());
}
addPath(path) {
for (let s of path._segments) {
this._segments.push(s.clone());
}
}
_fromString(s) {
var parts = s.split(" ");
var startIndex = 0;
while (startIndex < parts.length) {
var name = parts[startIndex];
var endIndex = startIndex + 1;
var values = [];
while (endIndex < parts.length) {
var value = parseFloat(parts[endIndex]);
if (!isNaN(value) && isFinite(value)) {
values.push(value);
endIndex++;
}
else
break;
}
var i = 0;
var points = [];
while (i < values.length) {
points.push(new PointF(values[i], values[i + 1]));
i += 2;
}
var segment = new PathSegment(name, points);
this._segments.push(segment);
startIndex = endIndex;
}
}
static rectangle(left, top, width, height) {
var path = new Path();
path.moveTo(left, top);
path.lineTo(left + width, top);
path.lineTo(left + width, top + height);
path.lineTo(left, top + height);
path.lineTo(left, top);
path.close();
return path;
}
static ellipse(left, top, width, height) {
var eNumber = 0.5517;
var hWidth = width / 2;
var hHeight = height / 2;
var path = new Path();
path.moveTo(left, top + hHeight);
path.cubicTo(left, top + hHeight - hHeight * eNumber, left + hWidth - hWidth * eNumber, top, left + hWidth, top);
path.cubicTo(left + hWidth + hWidth * eNumber, top, left + width, top + hHeight - hHeight * eNumber, left + width, top + hHeight);
path.cubicTo(left + width, top + hHeight + hHeight * eNumber, left + hWidth + hWidth * eNumber, top + height, left + hWidth, top + height);
path.cubicTo(left + hWidth - hWidth * eNumber, top + height, left, top + hHeight + hHeight * eNumber, left, top + hHeight);
path.close();
return path;
}
static roundedRectangle(left, top, width, height, radiuses) {
if (radiuses == null || radiuses.length === 0)
return Path.rectangle(left, top, width, height);
const eNumber = 0.5517;
const path = new Path();
const x = left;
const y = top;
let w0 = radiuses[0].width;
let h0 = radiuses[0].height;
let w1 = radiuses[1].width;
let h1 = radiuses[1].height;
let w2 = radiuses[2].width;
let h2 = radiuses[2].height;
let w3 = radiuses[3].width;
let h3 = radiuses[3].height;
if (EqualsOfFloatNumbers(w0, 0) || EqualsOfFloatNumbers(h0, 0))
w0 = h0 = 0;
if (EqualsOfFloatNumbers(w1, 0) || EqualsOfFloatNumbers(h1, 0))
w1 = h1 = 0;
if (EqualsOfFloatNumbers(w2, 0) || EqualsOfFloatNumbers(h2, 0))
w2 = h2 = 0;
if (EqualsOfFloatNumbers(w3, 0) || EqualsOfFloatNumbers(h3, 0))
w3 = h3 = 0;
path.moveTo(x + w0, y);
// Top line:
if (!EqualsOfFloatNumbers(w0 + w1, width)) {
path.lineTo(x + width - w1, y);
}
// corner1 ellipse part
if (!EqualsOfFloatNumbers(w1, 0) && !EqualsOfFloatNumbers(h1, 0)) {
path.cubicTo(x + width - w1 * (1 - eNumber), y, x + width, y + h1 * (1 - eNumber), x + width, y + h1);
}
// Right line:
if (!EqualsOfFloatNumbers(h1 + h2, height)) {
path.lineTo(x + width, y + height - h2);
}
// corner2 ellipse part
if (!EqualsOfFloatNumbers(w2, 0) && !EqualsOfFloatNumbers(h2, 0)) {
path.cubicTo(x + width, y + height - h2 * (1 - eNumber), x + width - w2 * (1 - eNumber), y + height, x + width - w2, y + height);
}
// Bottom line:
if (!EqualsOfFloatNumbers(w2 + w3, width)) {
path.lineTo(x + w3, y + height);
}
// corner3 ellipse part
if (!EqualsOfFloatNumbers(w3, 0) && !EqualsOfFloatNumbers(h3, 0)) {
path.cubicTo(x + w3 * (1 - eNumber), y + height, x, y + height - h3 * (1 - eNumber), x, y + height - h3);
}
// Left line:
if (!EqualsOfFloatNumbers(h3 + h0, height)) {
path.lineTo(x, y + h0);
}
// corner0 ellipse part
if (!EqualsOfFloatNumbers(w0, 0) && !EqualsOfFloatNumbers(h0, 0)) {
path.cubicTo(x, y + h0 * (1 - eNumber), x + w0 * (1 - eNumber), y, x + w0, y);
}
path.close();
return path;
}
static rotatedRectangle(rectangle) {
const leftTop = rectangle.getUpperLeftCorner();
const rightTop = rectangle.getUpperRightCorner();
const rightBottom = rectangle.getBottomRightCorner();
const leftBottom = rectangle.getBottomLeftCorner();
var path = new Path();
path.moveTo(leftTop.x, leftTop.y);
path.lineTo(rightTop.x, rightTop.y);
path.lineTo(rightBottom.x, rightBottom.y);
path.lineTo(leftBottom.x, leftBottom.y);
path.lineTo(leftTop.x, leftTop.y);
path.close();
return path;
}
}
//# sourceMappingURL=Path.js.map