UNPKG

@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
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