@drauu/core
Advanced tools
Comparing version 0.4.1 to 0.4.2
@@ -179,29 +179,2 @@ interface Unsubscribe { | ||
declare class StylusModel extends BaseModel<SVGPathElement> { | ||
points: Point[]; | ||
onStart(point: Point): SVGPathElement; | ||
onMove(point: Point): boolean; | ||
onEnd(): boolean; | ||
getSvgData(points: Point[]): string; | ||
static getSvgData(points: Point[], brush: Brush): string; | ||
} | ||
declare class EllipseModel extends BaseModel<SVGEllipseElement> { | ||
onStart(point: Point): SVGEllipseElement; | ||
onMove(point: Point): boolean; | ||
onEnd(): boolean; | ||
} | ||
declare class LineModel extends BaseModel<SVGLineElement> { | ||
onStart(point: Point): SVGGElement; | ||
onMove(point: Point): boolean; | ||
onEnd(): boolean; | ||
} | ||
declare class RectModel extends BaseModel<SVGRectElement> { | ||
onStart(point: Point): SVGRectElement; | ||
onMove(point: Point): boolean; | ||
onEnd(): boolean; | ||
} | ||
declare class DrawModel extends BaseModel<SVGPathElement> { | ||
@@ -226,2 +199,8 @@ points: Point[]; | ||
declare class EllipseModel extends BaseModel<SVGEllipseElement> { | ||
onStart(point: Point): SVGEllipseElement; | ||
onMove(point: Point): boolean; | ||
onEnd(): boolean; | ||
} | ||
interface EraserPathFragment { | ||
@@ -250,2 +229,23 @@ x1: number; | ||
declare class LineModel extends BaseModel<SVGLineElement> { | ||
onStart(point: Point): SVGGElement; | ||
onMove(point: Point): boolean; | ||
onEnd(): boolean; | ||
} | ||
declare class RectModel extends BaseModel<SVGRectElement> { | ||
onStart(point: Point): SVGRectElement; | ||
onMove(point: Point): boolean; | ||
onEnd(): boolean; | ||
} | ||
declare class StylusModel extends BaseModel<SVGPathElement> { | ||
points: Point[]; | ||
onStart(point: Point): SVGPathElement; | ||
onMove(point: Point): boolean; | ||
onEnd(): boolean; | ||
getSvgData(points: Point[]): string; | ||
static getSvgData(points: Point[], brush: Brush): string; | ||
} | ||
declare class Drauu { | ||
@@ -268,3 +268,3 @@ options: Options; | ||
constructor(options?: Options); | ||
get model(): StylusModel | EllipseModel | LineModel | RectModel | DrawModel | EraserModel; | ||
get model(): DrawModel | EllipseModel | EraserModel | LineModel | RectModel | StylusModel; | ||
get mounted(): boolean; | ||
@@ -271,0 +271,0 @@ get mode(): DrawingMode; |
@@ -20,6 +20,6 @@ "use strict"; | ||
// ../../node_modules/.pnpm/nanoevents@9.0.0/node_modules/nanoevents/index.js | ||
// ../../node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js | ||
var createNanoEvents = () => ({ | ||
emit(event, ...args) { | ||
for (let i = 0, callbacks = this.events[event] || [], length = callbacks.length; i < length; i++) { | ||
for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) { | ||
callbacks[i](...args); | ||
@@ -40,150 +40,2 @@ } | ||
// ../../node_modules/.pnpm/perfect-freehand@1.2.2/node_modules/perfect-freehand/dist/esm/index.mjs | ||
function $(e, t, u, x = (h) => h) { | ||
return e * x(0.5 - t * (0.5 - u)); | ||
} | ||
function se(e) { | ||
return [-e[0], -e[1]]; | ||
} | ||
function l(e, t) { | ||
return [e[0] + t[0], e[1] + t[1]]; | ||
} | ||
function a(e, t) { | ||
return [e[0] - t[0], e[1] - t[1]]; | ||
} | ||
function b(e, t) { | ||
return [e[0] * t, e[1] * t]; | ||
} | ||
function he(e, t) { | ||
return [e[0] / t, e[1] / t]; | ||
} | ||
function R(e) { | ||
return [e[1], -e[0]]; | ||
} | ||
function B(e, t) { | ||
return e[0] * t[0] + e[1] * t[1]; | ||
} | ||
function ue(e, t) { | ||
return e[0] === t[0] && e[1] === t[1]; | ||
} | ||
function ge(e) { | ||
return Math.hypot(e[0], e[1]); | ||
} | ||
function de(e) { | ||
return e[0] * e[0] + e[1] * e[1]; | ||
} | ||
function A(e, t) { | ||
return de(a(e, t)); | ||
} | ||
function G(e) { | ||
return he(e, ge(e)); | ||
} | ||
function ie(e, t) { | ||
return Math.hypot(e[1] - t[1], e[0] - t[0]); | ||
} | ||
function L(e, t, u) { | ||
let x = Math.sin(u), h = Math.cos(u), y = e[0] - t[0], n = e[1] - t[1], f = y * h - n * x, d = y * x + n * h; | ||
return [f + t[0], d + t[1]]; | ||
} | ||
function K(e, t, u) { | ||
return l(e, b(a(t, e), u)); | ||
} | ||
function ee(e, t, u) { | ||
return l(e, b(t, u)); | ||
} | ||
var { min: C, PI: xe } = Math; | ||
var pe = 0.275; | ||
var V = xe + 1e-4; | ||
function ce(e, t = {}) { | ||
let { size: u = 16, smoothing: x = 0.5, thinning: h = 0.5, simulatePressure: y = true, easing: n = (r) => r, start: f = {}, end: d = {}, last: D2 = false } = t, { cap: S = true, easing: j = (r) => r * (2 - r) } = f, { cap: q = true, easing: c = (r) => --r * r * r + 1 } = d; | ||
if (e.length === 0 || u <= 0) return []; | ||
let p = e[e.length - 1].runningLength, g = f.taper === false ? 0 : f.taper === true ? Math.max(u, p) : f.taper, T = d.taper === false ? 0 : d.taper === true ? Math.max(u, p) : d.taper, te = Math.pow(u * x, 2), _ = [], M = [], H = e.slice(0, 10).reduce((r, i) => { | ||
let o = i.pressure; | ||
if (y) { | ||
let s = C(1, i.distance / u), W = C(1, 1 - s); | ||
o = C(1, r + (W - r) * (s * pe)); | ||
} | ||
return (r + o) / 2; | ||
}, e[0].pressure), m = $(u, h, e[e.length - 1].pressure, n), U, X = e[0].vector, z = e[0].point, F = z, O = z, E = F, J = false; | ||
for (let r = 0; r < e.length; r++) { | ||
let { pressure: i } = e[r], { point: o, vector: s, distance: W, runningLength: I } = e[r]; | ||
if (r < e.length - 1 && p - I < 3) continue; | ||
if (h) { | ||
if (y) { | ||
let v = C(1, W / u), Z = C(1, 1 - v); | ||
i = C(1, H + (Z - H) * (v * pe)); | ||
} | ||
m = $(u, h, i, n); | ||
} else m = u / 2; | ||
U === void 0 && (U = m); | ||
let le = I < g ? j(I / g) : 1, fe = p - I < T ? c((p - I) / T) : 1; | ||
m = Math.max(0.01, m * Math.min(le, fe)); | ||
let re = (r < e.length - 1 ? e[r + 1] : e[r]).vector, Y = r < e.length - 1 ? B(s, re) : 1, be = B(s, X) < 0 && !J, ne = Y !== null && Y < 0; | ||
if (be || ne) { | ||
let v = b(R(X), m); | ||
for (let Z = 1 / 13, w = 0; w <= 1; w += Z) O = L(a(o, v), o, V * w), _.push(O), E = L(l(o, v), o, V * -w), M.push(E); | ||
z = O, F = E, ne && (J = true); | ||
continue; | ||
} | ||
if (J = false, r === e.length - 1) { | ||
let v = b(R(s), m); | ||
_.push(a(o, v)), M.push(l(o, v)); | ||
continue; | ||
} | ||
let oe = b(R(K(re, s, Y)), m); | ||
O = a(o, oe), (r <= 1 || A(z, O) > te) && (_.push(O), z = O), E = l(o, oe), (r <= 1 || A(F, E) > te) && (M.push(E), F = E), H = i, X = s; | ||
} | ||
let P = e[0].point.slice(0, 2), k = e.length > 1 ? e[e.length - 1].point.slice(0, 2) : l(e[0].point, [1, 1]), Q = [], N = []; | ||
if (e.length === 1) { | ||
if (!(g || T) || D2) { | ||
let r = ee(P, G(R(a(P, k))), -(U || m)), i = []; | ||
for (let o = 1 / 13, s = o; s <= 1; s += o) i.push(L(r, P, V * 2 * s)); | ||
return i; | ||
} | ||
} else { | ||
if (!(g || T && e.length === 1)) if (S) for (let i = 1 / 13, o = i; o <= 1; o += i) { | ||
let s = L(M[0], P, V * o); | ||
Q.push(s); | ||
} | ||
else { | ||
let i = a(_[0], M[0]), o = b(i, 0.5), s = b(i, 0.51); | ||
Q.push(a(P, o), a(P, s), l(P, s), l(P, o)); | ||
} | ||
let r = R(se(e[e.length - 1].vector)); | ||
if (T || g && e.length === 1) N.push(k); | ||
else if (q) { | ||
let i = ee(k, r, m); | ||
for (let o = 1 / 29, s = o; s < 1; s += o) N.push(L(i, k, V * 3 * s)); | ||
} else N.push(l(k, b(r, m)), l(k, b(r, m * 0.99)), a(k, b(r, m * 0.99)), a(k, b(r, m))); | ||
} | ||
return _.concat(N, M.reverse(), Q); | ||
} | ||
function me(e, t = {}) { | ||
var q; | ||
let { streamline: u = 0.5, size: x = 16, last: h = false } = t; | ||
if (e.length === 0) return []; | ||
let y = 0.15 + (1 - u) * 0.85, n = Array.isArray(e[0]) ? e : e.map(({ x: c, y: p, pressure: g = 0.5 }) => [c, p, g]); | ||
if (n.length === 2) { | ||
let c = n[1]; | ||
n = n.slice(0, -1); | ||
for (let p = 1; p < 5; p++) n.push(K(n[0], c, p / 4)); | ||
} | ||
n.length === 1 && (n = [...n, [...l(n[0], [1, 1]), ...n[0].slice(2)]]); | ||
let f = [{ point: [n[0][0], n[0][1]], pressure: n[0][2] >= 0 ? n[0][2] : 0.25, vector: [1, 1], distance: 0, runningLength: 0 }], d = false, D2 = 0, S = f[0], j = n.length - 1; | ||
for (let c = 1; c < n.length; c++) { | ||
let p = h && c === j ? n[c].slice(0, 2) : K(S.point, n[c], y); | ||
if (ue(S.point, p)) continue; | ||
let g = ie(p, S.point); | ||
if (D2 += g, c < j && !d) { | ||
if (D2 < x) continue; | ||
d = true; | ||
} | ||
S = { point: p, pressure: n[c][2] >= 0 ? n[c][2] : 0.5, vector: G(a(S.point, p)), distance: g, runningLength: D2 }, f.push(S); | ||
} | ||
return f[0].vector = ((q = f[1]) == null ? void 0 : q.vector) || [0, 0], f; | ||
} | ||
function ae(e, t = {}) { | ||
return ce(me(e, t), t); | ||
} | ||
// src/utils/index.ts | ||
@@ -208,2 +60,95 @@ function numSort(a2, b2) { | ||
// src/utils/dom.ts | ||
function createArrowHead(id, fill) { | ||
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); | ||
const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker"); | ||
const head = document.createElementNS("http://www.w3.org/2000/svg", "path"); | ||
head.setAttribute("fill", fill); | ||
marker.setAttribute("id", id); | ||
marker.setAttribute("viewBox", "0 -5 10 10"); | ||
marker.setAttribute("refX", "5"); | ||
marker.setAttribute("refY", "0"); | ||
marker.setAttribute("markerWidth", "4"); | ||
marker.setAttribute("markerHeight", "4"); | ||
marker.setAttribute("orient", "auto"); | ||
head.setAttribute("d", "M0,-5L10,0L0,5"); | ||
marker.appendChild(head); | ||
defs.appendChild(marker); | ||
return defs; | ||
} | ||
// src/utils/simplify.ts | ||
function getSqDist(p1, p2) { | ||
const dx = p1.x - p2.x; | ||
const dy = p1.y - p2.y; | ||
return dx * dx + dy * dy; | ||
} | ||
function getSqSegDist(p, p1, p2) { | ||
let x = p1.x; | ||
let y = p1.y; | ||
let dx = p2.x - x; | ||
let dy = p2.y - y; | ||
if (dx !== 0 || dy !== 0) { | ||
const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy); | ||
if (t > 1) { | ||
x = p2.x; | ||
y = p2.y; | ||
} else if (t > 0) { | ||
x += dx * t; | ||
y += dy * t; | ||
} | ||
} | ||
dx = p.x - x; | ||
dy = p.y - y; | ||
return dx * dx + dy * dy; | ||
} | ||
function simplifyRadialDist(points, sqTolerance) { | ||
let prevPoint = points[0]; | ||
const newPoints = [prevPoint]; | ||
let point; | ||
for (let i = 1, len = points.length; i < len; i++) { | ||
point = points[i]; | ||
if (getSqDist(point, prevPoint) > sqTolerance) { | ||
newPoints.push(point); | ||
prevPoint = point; | ||
} | ||
} | ||
if (prevPoint !== point && point) | ||
newPoints.push(point); | ||
return newPoints; | ||
} | ||
function simplifyDPStep(points, first, last, sqTolerance, simplified) { | ||
let maxSqDist = sqTolerance; | ||
let index = 0; | ||
for (let i = first + 1; i < last; i++) { | ||
const sqDist = getSqSegDist(points[i], points[first], points[last]); | ||
if (sqDist > maxSqDist) { | ||
index = i; | ||
maxSqDist = sqDist; | ||
} | ||
} | ||
if (maxSqDist > sqTolerance) { | ||
if (index - first > 1) | ||
simplifyDPStep(points, first, index, sqTolerance, simplified); | ||
simplified.push(points[index]); | ||
if (last - index > 1) | ||
simplifyDPStep(points, index, last, sqTolerance, simplified); | ||
} | ||
} | ||
function simplifyDouglasPeucker(points, sqTolerance) { | ||
const last = points.length - 1; | ||
const simplified = [points[0]]; | ||
simplifyDPStep(points, 0, last, sqTolerance, simplified); | ||
simplified.push(points[last]); | ||
return simplified; | ||
} | ||
function simplify(points, tolerance, highestQuality = false) { | ||
if (points.length <= 2) | ||
return points; | ||
const sqTolerance = tolerance !== void 0 ? tolerance * tolerance : 1; | ||
points = highestQuality ? points : simplifyRadialDist(points, sqTolerance); | ||
points = simplifyDouglasPeucker(points, sqTolerance); | ||
return points; | ||
} | ||
// src/models/base.ts | ||
@@ -310,13 +255,17 @@ var BaseModel = class { | ||
// src/models/stylus.ts | ||
var StylusModel = class _StylusModel extends BaseModel { | ||
// src/models/draw.ts | ||
var DrawModel = class _DrawModel extends BaseModel { | ||
constructor() { | ||
super(...arguments); | ||
this.points = []; | ||
this.count = 0; | ||
} | ||
onStart(point) { | ||
this.el = document.createElementNS("http://www.w3.org/2000/svg", "path"); | ||
this.el = this.createElement("path", { fill: "transparent" }); | ||
this.points = [point]; | ||
this.attr("fill", this.brush.color); | ||
this.attr("d", this.getSvgData(this.points)); | ||
if (this.brush.arrowEnd) { | ||
this.arrowId = guid(); | ||
const head = createArrowHead(this.arrowId, this.brush.color); | ||
this.el.appendChild(head); | ||
} | ||
return this.el; | ||
@@ -327,5 +276,11 @@ } | ||
this.onStart(point); | ||
if (this.points[this.points.length - 1] !== point) | ||
if (this.points[this.points.length - 1] !== point) { | ||
this.points.push(point); | ||
this.attr("d", this.getSvgData(this.points)); | ||
this.count += 1; | ||
} | ||
if (this.count > 5) { | ||
this.points = simplify(this.points, 1, true); | ||
this.count = 0; | ||
} | ||
this.attr("d", _DrawModel.toSvgData(this.points)); | ||
return true; | ||
@@ -338,31 +293,42 @@ } | ||
return false; | ||
path.setAttribute("d", _DrawModel.toSvgData(simplify(this.points, 1, true))); | ||
if (!path.getTotalLength()) { | ||
const { x, y } = this.points[0]; | ||
const r = this.brush.size / 2; | ||
path.setAttribute("d", `M ${x - r} ${y} a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 ${-r * 2},0`); | ||
path.setAttribute("fill", this.brush.color); | ||
path.setAttribute("stroke-width", "0"); | ||
} | ||
return true; | ||
} | ||
getSvgData(points) { | ||
return _StylusModel.getSvgData(points, this.brush); | ||
// https://francoisromain.medium.com/smooth-a-svg-path-with-cubic-bezier-curves-e37b49d46c74 | ||
static line(a2, b2) { | ||
const lengthX = b2.x - a2.x; | ||
const lengthY = b2.y - a2.y; | ||
return { | ||
length: Math.sqrt(lengthX ** 2 + lengthY ** 2), | ||
angle: Math.atan2(lengthY, lengthX) | ||
}; | ||
} | ||
static getSvgData(points, brush) { | ||
const stroke = ae(points, __spreadValues({ | ||
size: brush.size, | ||
thinning: 0.9, | ||
simulatePressure: false, | ||
start: { | ||
taper: 5 | ||
}, | ||
end: { | ||
taper: 5 | ||
} | ||
}, brush.stylusOptions)); | ||
if (!stroke.length) | ||
return ""; | ||
const d = stroke.reduce( | ||
(acc, [x0, y0], i, arr) => { | ||
const [x1, y1] = arr[(i + 1) % arr.length]; | ||
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2); | ||
return acc; | ||
}, | ||
["M", ...stroke[0], "Q"] | ||
static controlPoint(current, previous, next, reverse) { | ||
const p = previous || current; | ||
const n = next || current; | ||
const smoothing = 0.2; | ||
const o = _DrawModel.line(p, n); | ||
const angle = o.angle + (reverse ? Math.PI : 0); | ||
const length = o.length * smoothing; | ||
const x = current.x + Math.cos(angle) * length; | ||
const y = current.y + Math.sin(angle) * length; | ||
return { x, y }; | ||
} | ||
static bezierCommand(point, i, points) { | ||
const cps = _DrawModel.controlPoint(points[i - 1], points[i - 2], point); | ||
const cpe = _DrawModel.controlPoint(point, points[i - 1], points[i + 1], true); | ||
return `C ${cps.x.toFixed(D)},${cps.y.toFixed(D)} ${cpe.x.toFixed(D)},${cpe.y.toFixed(D)} ${point.x.toFixed(D)},${point.y.toFixed(D)}`; | ||
} | ||
static toSvgData(points) { | ||
return points.reduce( | ||
(acc, point, i, a2) => i === 0 ? `M ${point.x.toFixed(D)},${point.y.toFixed(D)}` : `${acc} ${_DrawModel.bezierCommand(point, i, a2)}`, | ||
"" | ||
); | ||
d.push("Z"); | ||
return d.map((i) => typeof i === "number" ? i.toFixed(2) : i).join(" "); | ||
} | ||
@@ -409,4 +375,8 @@ }; | ||
return false; | ||
if (!path.getTotalLength()) | ||
try { | ||
if (!path.getTotalLength()) | ||
return false; | ||
} catch (e) { | ||
return false; | ||
} | ||
return true; | ||
@@ -416,20 +386,116 @@ } | ||
// src/utils/dom.ts | ||
function createArrowHead(id, fill) { | ||
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); | ||
const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker"); | ||
const head = document.createElementNS("http://www.w3.org/2000/svg", "path"); | ||
head.setAttribute("fill", fill); | ||
marker.setAttribute("id", id); | ||
marker.setAttribute("viewBox", "0 -5 10 10"); | ||
marker.setAttribute("refX", "5"); | ||
marker.setAttribute("refY", "0"); | ||
marker.setAttribute("markerWidth", "4"); | ||
marker.setAttribute("markerHeight", "4"); | ||
marker.setAttribute("orient", "auto"); | ||
head.setAttribute("d", "M0,-5L10,0L0,5"); | ||
marker.appendChild(head); | ||
defs.appendChild(marker); | ||
return defs; | ||
} | ||
// src/models/eraser.ts | ||
var EraserModel = class extends BaseModel { | ||
constructor() { | ||
super(...arguments); | ||
this.pathSubFactor = 20; | ||
this.pathFragments = []; | ||
this._erased = []; | ||
} | ||
onSelected(el) { | ||
const calculatePathFragments = (children, element) => { | ||
if (children && children.length) { | ||
for (let i = 0; i < children.length; i++) { | ||
const ele = children[i]; | ||
if (ele.getTotalLength) { | ||
const pathLength = ele.getTotalLength(); | ||
for (let j = 0; j < this.pathSubFactor; j++) { | ||
const pos1 = ele.getPointAtLength(pathLength * j / this.pathSubFactor); | ||
const pos2 = ele.getPointAtLength(pathLength * (j + 1) / this.pathSubFactor); | ||
this.pathFragments.push({ | ||
x1: pos1.x, | ||
x2: pos2.x, | ||
y1: pos1.y, | ||
y2: pos2.y, | ||
segment: j, | ||
element: element || ele | ||
}); | ||
} | ||
} else { | ||
if (ele.children) | ||
calculatePathFragments(ele.children, ele); | ||
} | ||
} | ||
} | ||
}; | ||
if (el) | ||
calculatePathFragments(el.children); | ||
} | ||
onUnselected() { | ||
this.pathFragments = []; | ||
} | ||
onStart(point) { | ||
this.svgPointPrevious = this.svgElement.createSVGPoint(); | ||
this.svgPointPrevious.x = point.x; | ||
this.svgPointPrevious.y = point.y; | ||
return void 0; | ||
} | ||
onMove(point) { | ||
this.svgPointCurrent = this.svgElement.createSVGPoint(); | ||
this.svgPointCurrent.x = point.x; | ||
this.svgPointCurrent.y = point.y; | ||
const erased = this.checkAndEraseElement(); | ||
this.svgPointPrevious = this.svgPointCurrent; | ||
return erased; | ||
} | ||
onEnd() { | ||
this.svgPointPrevious = void 0; | ||
this.svgPointCurrent = void 0; | ||
const erased = this._erased; | ||
this._erased = []; | ||
return { | ||
undo: () => erased.forEach((v) => this.drauu._restoreNode(v)), | ||
redo: () => erased.forEach((v) => this.drauu._removeNode(v)) | ||
}; | ||
} | ||
checkAndEraseElement() { | ||
if (this.pathFragments.length) { | ||
for (let i = 0; i < this.pathFragments.length; i++) { | ||
const segment = this.pathFragments[i]; | ||
if (this.svgPointPrevious && this.svgPointCurrent) { | ||
const line = { | ||
x1: this.svgPointPrevious.x, | ||
x2: this.svgPointCurrent.x, | ||
y1: this.svgPointPrevious.y, | ||
y2: this.svgPointCurrent.y | ||
}; | ||
if (this.lineLineIntersect(segment, line)) { | ||
this.drauu._removeNode(segment.element); | ||
this._erased.push(segment.element); | ||
} | ||
} | ||
} | ||
} | ||
if (this._erased.length) | ||
this.pathFragments = this.pathFragments.filter((v) => !this._erased.includes(v.element)); | ||
return this._erased.length > 0; | ||
} | ||
lineLineIntersect(line1, line2) { | ||
const x1 = line1.x1; | ||
const x2 = line1.x2; | ||
const x3 = line2.x1; | ||
const x4 = line2.x2; | ||
const y1 = line1.y1; | ||
const y2 = line1.y2; | ||
const y3 = line2.y1; | ||
const y4 = line2.y2; | ||
const pt_denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); | ||
const pt_x_num = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4); | ||
const pt_y_num = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4); | ||
const btwn = (a2, b1, b2) => { | ||
if (a2 >= b1 && a2 <= b2) | ||
return true; | ||
return a2 >= b2 && a2 <= b1; | ||
}; | ||
if (pt_denom === 0) { | ||
return false; | ||
} else { | ||
const pt = { | ||
x: pt_x_num / pt_denom, | ||
y: pt_y_num / pt_denom | ||
}; | ||
return btwn(pt.x, x1, x2) && btwn(pt.y, y1, y2) && btwn(pt.x, x3, x4) && btwn(pt.y, y3, y4); | ||
} | ||
} | ||
}; | ||
@@ -491,4 +557,8 @@ // src/models/line.ts | ||
return false; | ||
if (path.getTotalLength() < 5) | ||
try { | ||
if (path.getTotalLength() < 5) | ||
return false; | ||
} catch (e) { | ||
return false; | ||
} | ||
return true; | ||
@@ -540,4 +610,8 @@ } | ||
return false; | ||
if (!path.getTotalLength()) | ||
try { | ||
if (!path.getTotalLength()) | ||
return false; | ||
} catch (e) { | ||
return false; | ||
} | ||
return true; | ||
@@ -547,91 +621,161 @@ } | ||
// src/utils/simplify.ts | ||
function getSqDist(p1, p2) { | ||
const dx = p1.x - p2.x; | ||
const dy = p1.y - p2.y; | ||
return dx * dx + dy * dy; | ||
// ../../node_modules/.pnpm/perfect-freehand@1.2.2/node_modules/perfect-freehand/dist/esm/index.mjs | ||
function $(e, t, u, x = (h) => h) { | ||
return e * x(0.5 - t * (0.5 - u)); | ||
} | ||
function getSqSegDist(p, p1, p2) { | ||
let x = p1.x; | ||
let y = p1.y; | ||
let dx = p2.x - x; | ||
let dy = p2.y - y; | ||
if (dx !== 0 || dy !== 0) { | ||
const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy); | ||
if (t > 1) { | ||
x = p2.x; | ||
y = p2.y; | ||
} else if (t > 0) { | ||
x += dx * t; | ||
y += dy * t; | ||
function se(e) { | ||
return [-e[0], -e[1]]; | ||
} | ||
function l(e, t) { | ||
return [e[0] + t[0], e[1] + t[1]]; | ||
} | ||
function a(e, t) { | ||
return [e[0] - t[0], e[1] - t[1]]; | ||
} | ||
function b(e, t) { | ||
return [e[0] * t, e[1] * t]; | ||
} | ||
function he(e, t) { | ||
return [e[0] / t, e[1] / t]; | ||
} | ||
function R(e) { | ||
return [e[1], -e[0]]; | ||
} | ||
function B(e, t) { | ||
return e[0] * t[0] + e[1] * t[1]; | ||
} | ||
function ue(e, t) { | ||
return e[0] === t[0] && e[1] === t[1]; | ||
} | ||
function ge(e) { | ||
return Math.hypot(e[0], e[1]); | ||
} | ||
function de(e) { | ||
return e[0] * e[0] + e[1] * e[1]; | ||
} | ||
function A(e, t) { | ||
return de(a(e, t)); | ||
} | ||
function G(e) { | ||
return he(e, ge(e)); | ||
} | ||
function ie(e, t) { | ||
return Math.hypot(e[1] - t[1], e[0] - t[0]); | ||
} | ||
function L(e, t, u) { | ||
let x = Math.sin(u), h = Math.cos(u), y = e[0] - t[0], n = e[1] - t[1], f = y * h - n * x, d = y * x + n * h; | ||
return [f + t[0], d + t[1]]; | ||
} | ||
function K(e, t, u) { | ||
return l(e, b(a(t, e), u)); | ||
} | ||
function ee(e, t, u) { | ||
return l(e, b(t, u)); | ||
} | ||
var { min: C, PI: xe } = Math; | ||
var pe = 0.275; | ||
var V = xe + 1e-4; | ||
function ce(e, t = {}) { | ||
let { size: u = 16, smoothing: x = 0.5, thinning: h = 0.5, simulatePressure: y = true, easing: n = (r) => r, start: f = {}, end: d = {}, last: D2 = false } = t, { cap: S = true, easing: j = (r) => r * (2 - r) } = f, { cap: q = true, easing: c = (r) => --r * r * r + 1 } = d; | ||
if (e.length === 0 || u <= 0) return []; | ||
let p = e[e.length - 1].runningLength, g = f.taper === false ? 0 : f.taper === true ? Math.max(u, p) : f.taper, T = d.taper === false ? 0 : d.taper === true ? Math.max(u, p) : d.taper, te = Math.pow(u * x, 2), _ = [], M = [], H = e.slice(0, 10).reduce((r, i) => { | ||
let o = i.pressure; | ||
if (y) { | ||
let s = C(1, i.distance / u), W = C(1, 1 - s); | ||
o = C(1, r + (W - r) * (s * pe)); | ||
} | ||
return (r + o) / 2; | ||
}, e[0].pressure), m = $(u, h, e[e.length - 1].pressure, n), U, X = e[0].vector, z = e[0].point, F = z, O = z, E = F, J = false; | ||
for (let r = 0; r < e.length; r++) { | ||
let { pressure: i } = e[r], { point: o, vector: s, distance: W, runningLength: I } = e[r]; | ||
if (r < e.length - 1 && p - I < 3) continue; | ||
if (h) { | ||
if (y) { | ||
let v = C(1, W / u), Z = C(1, 1 - v); | ||
i = C(1, H + (Z - H) * (v * pe)); | ||
} | ||
m = $(u, h, i, n); | ||
} else m = u / 2; | ||
U === void 0 && (U = m); | ||
let le = I < g ? j(I / g) : 1, fe = p - I < T ? c((p - I) / T) : 1; | ||
m = Math.max(0.01, m * Math.min(le, fe)); | ||
let re = (r < e.length - 1 ? e[r + 1] : e[r]).vector, Y = r < e.length - 1 ? B(s, re) : 1, be = B(s, X) < 0 && !J, ne = Y !== null && Y < 0; | ||
if (be || ne) { | ||
let v = b(R(X), m); | ||
for (let Z = 1 / 13, w = 0; w <= 1; w += Z) O = L(a(o, v), o, V * w), _.push(O), E = L(l(o, v), o, V * -w), M.push(E); | ||
z = O, F = E, ne && (J = true); | ||
continue; | ||
} | ||
if (J = false, r === e.length - 1) { | ||
let v = b(R(s), m); | ||
_.push(a(o, v)), M.push(l(o, v)); | ||
continue; | ||
} | ||
let oe = b(R(K(re, s, Y)), m); | ||
O = a(o, oe), (r <= 1 || A(z, O) > te) && (_.push(O), z = O), E = l(o, oe), (r <= 1 || A(F, E) > te) && (M.push(E), F = E), H = i, X = s; | ||
} | ||
dx = p.x - x; | ||
dy = p.y - y; | ||
return dx * dx + dy * dy; | ||
} | ||
function simplifyRadialDist(points, sqTolerance) { | ||
let prevPoint = points[0]; | ||
const newPoints = [prevPoint]; | ||
let point; | ||
for (let i = 1, len = points.length; i < len; i++) { | ||
point = points[i]; | ||
if (getSqDist(point, prevPoint) > sqTolerance) { | ||
newPoints.push(point); | ||
prevPoint = point; | ||
let P = e[0].point.slice(0, 2), k = e.length > 1 ? e[e.length - 1].point.slice(0, 2) : l(e[0].point, [1, 1]), Q = [], N = []; | ||
if (e.length === 1) { | ||
if (!(g || T) || D2) { | ||
let r = ee(P, G(R(a(P, k))), -(U || m)), i = []; | ||
for (let o = 1 / 13, s = o; s <= 1; s += o) i.push(L(r, P, V * 2 * s)); | ||
return i; | ||
} | ||
} else { | ||
if (!(g || T && e.length === 1)) if (S) for (let i = 1 / 13, o = i; o <= 1; o += i) { | ||
let s = L(M[0], P, V * o); | ||
Q.push(s); | ||
} | ||
else { | ||
let i = a(_[0], M[0]), o = b(i, 0.5), s = b(i, 0.51); | ||
Q.push(a(P, o), a(P, s), l(P, s), l(P, o)); | ||
} | ||
let r = R(se(e[e.length - 1].vector)); | ||
if (T || g && e.length === 1) N.push(k); | ||
else if (q) { | ||
let i = ee(k, r, m); | ||
for (let o = 1 / 29, s = o; s < 1; s += o) N.push(L(i, k, V * 3 * s)); | ||
} else N.push(l(k, b(r, m)), l(k, b(r, m * 0.99)), a(k, b(r, m * 0.99)), a(k, b(r, m))); | ||
} | ||
if (prevPoint !== point && point) | ||
newPoints.push(point); | ||
return newPoints; | ||
return _.concat(N, M.reverse(), Q); | ||
} | ||
function simplifyDPStep(points, first, last, sqTolerance, simplified) { | ||
let maxSqDist = sqTolerance; | ||
let index = 0; | ||
for (let i = first + 1; i < last; i++) { | ||
const sqDist = getSqSegDist(points[i], points[first], points[last]); | ||
if (sqDist > maxSqDist) { | ||
index = i; | ||
maxSqDist = sqDist; | ||
function me(e, t = {}) { | ||
var q; | ||
let { streamline: u = 0.5, size: x = 16, last: h = false } = t; | ||
if (e.length === 0) return []; | ||
let y = 0.15 + (1 - u) * 0.85, n = Array.isArray(e[0]) ? e : e.map(({ x: c, y: p, pressure: g = 0.5 }) => [c, p, g]); | ||
if (n.length === 2) { | ||
let c = n[1]; | ||
n = n.slice(0, -1); | ||
for (let p = 1; p < 5; p++) n.push(K(n[0], c, p / 4)); | ||
} | ||
n.length === 1 && (n = [...n, [...l(n[0], [1, 1]), ...n[0].slice(2)]]); | ||
let f = [{ point: [n[0][0], n[0][1]], pressure: n[0][2] >= 0 ? n[0][2] : 0.25, vector: [1, 1], distance: 0, runningLength: 0 }], d = false, D2 = 0, S = f[0], j = n.length - 1; | ||
for (let c = 1; c < n.length; c++) { | ||
let p = h && c === j ? n[c].slice(0, 2) : K(S.point, n[c], y); | ||
if (ue(S.point, p)) continue; | ||
let g = ie(p, S.point); | ||
if (D2 += g, c < j && !d) { | ||
if (D2 < x) continue; | ||
d = true; | ||
} | ||
S = { point: p, pressure: n[c][2] >= 0 ? n[c][2] : 0.5, vector: G(a(S.point, p)), distance: g, runningLength: D2 }, f.push(S); | ||
} | ||
if (maxSqDist > sqTolerance) { | ||
if (index - first > 1) | ||
simplifyDPStep(points, first, index, sqTolerance, simplified); | ||
simplified.push(points[index]); | ||
if (last - index > 1) | ||
simplifyDPStep(points, index, last, sqTolerance, simplified); | ||
} | ||
return f[0].vector = ((q = f[1]) == null ? void 0 : q.vector) || [0, 0], f; | ||
} | ||
function simplifyDouglasPeucker(points, sqTolerance) { | ||
const last = points.length - 1; | ||
const simplified = [points[0]]; | ||
simplifyDPStep(points, 0, last, sqTolerance, simplified); | ||
simplified.push(points[last]); | ||
return simplified; | ||
function ae(e, t = {}) { | ||
return ce(me(e, t), t); | ||
} | ||
function simplify(points, tolerance, highestQuality = false) { | ||
if (points.length <= 2) | ||
return points; | ||
const sqTolerance = tolerance !== void 0 ? tolerance * tolerance : 1; | ||
points = highestQuality ? points : simplifyRadialDist(points, sqTolerance); | ||
points = simplifyDouglasPeucker(points, sqTolerance); | ||
return points; | ||
} | ||
// src/models/draw.ts | ||
var DrawModel = class _DrawModel extends BaseModel { | ||
// src/models/stylus.ts | ||
var StylusModel = class _StylusModel extends BaseModel { | ||
constructor() { | ||
super(...arguments); | ||
this.points = []; | ||
this.count = 0; | ||
} | ||
onStart(point) { | ||
this.el = this.createElement("path", { fill: "transparent" }); | ||
this.el = document.createElementNS("http://www.w3.org/2000/svg", "path"); | ||
this.points = [point]; | ||
if (this.brush.arrowEnd) { | ||
this.arrowId = guid(); | ||
const head = createArrowHead(this.arrowId, this.brush.color); | ||
this.el.appendChild(head); | ||
} | ||
this.attr("fill", this.brush.color); | ||
this.attr("d", this.getSvgData(this.points)); | ||
return this.el; | ||
@@ -642,11 +786,5 @@ } | ||
this.onStart(point); | ||
if (this.points[this.points.length - 1] !== point) { | ||
if (this.points[this.points.length - 1] !== point) | ||
this.points.push(point); | ||
this.count += 1; | ||
} | ||
if (this.count > 5) { | ||
this.points = simplify(this.points, 1, true); | ||
this.count = 0; | ||
} | ||
this.attr("d", _DrawModel.toSvgData(this.points)); | ||
this.attr("d", this.getSvgData(this.points)); | ||
return true; | ||
@@ -659,158 +797,34 @@ } | ||
return false; | ||
path.setAttribute("d", _DrawModel.toSvgData(simplify(this.points, 1, true))); | ||
if (!path.getTotalLength()) { | ||
const { x, y } = this.points[0]; | ||
const r = this.brush.size / 2; | ||
path.setAttribute("d", `M ${x - r} ${y} a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 ${-r * 2},0`); | ||
path.setAttribute("fill", this.brush.color); | ||
path.setAttribute("stroke-width", "0"); | ||
} | ||
return true; | ||
} | ||
// https://francoisromain.medium.com/smooth-a-svg-path-with-cubic-bezier-curves-e37b49d46c74 | ||
static line(a2, b2) { | ||
const lengthX = b2.x - a2.x; | ||
const lengthY = b2.y - a2.y; | ||
return { | ||
length: Math.sqrt(lengthX ** 2 + lengthY ** 2), | ||
angle: Math.atan2(lengthY, lengthX) | ||
}; | ||
getSvgData(points) { | ||
return _StylusModel.getSvgData(points, this.brush); | ||
} | ||
static controlPoint(current, previous, next, reverse) { | ||
const p = previous || current; | ||
const n = next || current; | ||
const smoothing = 0.2; | ||
const o = _DrawModel.line(p, n); | ||
const angle = o.angle + (reverse ? Math.PI : 0); | ||
const length = o.length * smoothing; | ||
const x = current.x + Math.cos(angle) * length; | ||
const y = current.y + Math.sin(angle) * length; | ||
return { x, y }; | ||
} | ||
static bezierCommand(point, i, points) { | ||
const cps = _DrawModel.controlPoint(points[i - 1], points[i - 2], point); | ||
const cpe = _DrawModel.controlPoint(point, points[i - 1], points[i + 1], true); | ||
return `C ${cps.x.toFixed(D)},${cps.y.toFixed(D)} ${cpe.x.toFixed(D)},${cpe.y.toFixed(D)} ${point.x.toFixed(D)},${point.y.toFixed(D)}`; | ||
} | ||
static toSvgData(points) { | ||
return points.reduce( | ||
(acc, point, i, a2) => i === 0 ? `M ${point.x.toFixed(D)},${point.y.toFixed(D)}` : `${acc} ${_DrawModel.bezierCommand(point, i, a2)}`, | ||
"" | ||
static getSvgData(points, brush) { | ||
const stroke = ae(points, __spreadValues({ | ||
size: brush.size, | ||
thinning: 0.9, | ||
simulatePressure: false, | ||
start: { | ||
taper: 5 | ||
}, | ||
end: { | ||
taper: 5 | ||
} | ||
}, brush.stylusOptions)); | ||
if (!stroke.length) | ||
return ""; | ||
const d = stroke.reduce( | ||
(acc, [x0, y0], i, arr) => { | ||
const [x1, y1] = arr[(i + 1) % arr.length]; | ||
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2); | ||
return acc; | ||
}, | ||
["M", ...stroke[0], "Q"] | ||
); | ||
d.push("Z"); | ||
return d.map((i) => typeof i === "number" ? i.toFixed(2) : i).join(" "); | ||
} | ||
}; | ||
// src/models/eraser.ts | ||
var EraserModel = class extends BaseModel { | ||
constructor() { | ||
super(...arguments); | ||
this.pathSubFactor = 20; | ||
this.pathFragments = []; | ||
this._erased = []; | ||
} | ||
onSelected(el) { | ||
const calculatePathFragments = (children, element) => { | ||
if (children && children.length) { | ||
for (let i = 0; i < children.length; i++) { | ||
const ele = children[i]; | ||
if (ele.getTotalLength) { | ||
const pathLength = ele.getTotalLength(); | ||
for (let j = 0; j < this.pathSubFactor; j++) { | ||
const pos1 = ele.getPointAtLength(pathLength * j / this.pathSubFactor); | ||
const pos2 = ele.getPointAtLength(pathLength * (j + 1) / this.pathSubFactor); | ||
this.pathFragments.push({ | ||
x1: pos1.x, | ||
x2: pos2.x, | ||
y1: pos1.y, | ||
y2: pos2.y, | ||
segment: j, | ||
element: element || ele | ||
}); | ||
} | ||
} else { | ||
if (ele.children) | ||
calculatePathFragments(ele.children, ele); | ||
} | ||
} | ||
} | ||
}; | ||
if (el) | ||
calculatePathFragments(el.children); | ||
} | ||
onUnselected() { | ||
this.pathFragments = []; | ||
} | ||
onStart(point) { | ||
this.svgPointPrevious = this.svgElement.createSVGPoint(); | ||
this.svgPointPrevious.x = point.x; | ||
this.svgPointPrevious.y = point.y; | ||
return void 0; | ||
} | ||
onMove(point) { | ||
this.svgPointCurrent = this.svgElement.createSVGPoint(); | ||
this.svgPointCurrent.x = point.x; | ||
this.svgPointCurrent.y = point.y; | ||
const erased = this.checkAndEraseElement(); | ||
this.svgPointPrevious = this.svgPointCurrent; | ||
return erased; | ||
} | ||
onEnd() { | ||
this.svgPointPrevious = void 0; | ||
this.svgPointCurrent = void 0; | ||
const erased = this._erased; | ||
this._erased = []; | ||
return { | ||
undo: () => erased.forEach((v) => this.drauu._restoreNode(v)), | ||
redo: () => erased.forEach((v) => this.drauu._removeNode(v)) | ||
}; | ||
} | ||
checkAndEraseElement() { | ||
if (this.pathFragments.length) { | ||
for (let i = 0; i < this.pathFragments.length; i++) { | ||
const segment = this.pathFragments[i]; | ||
const line = { | ||
x1: this.svgPointPrevious.x, | ||
x2: this.svgPointCurrent.x, | ||
y1: this.svgPointPrevious.y, | ||
y2: this.svgPointCurrent.y | ||
}; | ||
if (this.lineLineIntersect(segment, line)) { | ||
this.drauu._removeNode(segment.element); | ||
this._erased.push(segment.element); | ||
} | ||
} | ||
} | ||
if (this._erased.length) | ||
this.pathFragments = this.pathFragments.filter((v) => !this._erased.includes(v.element)); | ||
return this._erased.length > 0; | ||
} | ||
lineLineIntersect(line1, line2) { | ||
const x1 = line1.x1; | ||
const x2 = line1.x2; | ||
const x3 = line2.x1; | ||
const x4 = line2.x2; | ||
const y1 = line1.y1; | ||
const y2 = line1.y2; | ||
const y3 = line2.y1; | ||
const y4 = line2.y2; | ||
const pt_denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); | ||
const pt_x_num = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4); | ||
const pt_y_num = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4); | ||
const btwn = (a2, b1, b2) => { | ||
if (a2 >= b1 && a2 <= b2) | ||
return true; | ||
return a2 >= b2 && a2 <= b1; | ||
}; | ||
if (pt_denom === 0) { | ||
return false; | ||
} else { | ||
const pt = { | ||
x: pt_x_num / pt_denom, | ||
y: pt_y_num / pt_denom | ||
}; | ||
return btwn(pt.x, x1, x2) && btwn(pt.y, y1, y2) && btwn(pt.x, x3, x4) && btwn(pt.y, y3, y4); | ||
} | ||
} | ||
}; | ||
// src/models/index.ts | ||
@@ -971,7 +985,9 @@ function createModels(drauu) { | ||
const el = this._currentNode; | ||
this._appendNode(el); | ||
this.commit({ | ||
undo: () => this._removeNode(el), | ||
redo: () => this._restoreNode(el) | ||
}); | ||
if (el) { | ||
this._appendNode(el); | ||
this.commit({ | ||
undo: () => this._removeNode(el), | ||
redo: () => this._restoreNode(el) | ||
}); | ||
} | ||
} else { | ||
@@ -978,0 +994,0 @@ this.commit(result); |
@@ -48,6 +48,6 @@ "use strict"; | ||
// ../../node_modules/.pnpm/nanoevents@9.0.0/node_modules/nanoevents/index.js | ||
// ../../node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js | ||
var createNanoEvents = () => ({ | ||
emit(event, ...args) { | ||
for (let i = 0, callbacks = this.events[event] || [], length = callbacks.length; i < length; i++) { | ||
for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) { | ||
callbacks[i](...args); | ||
@@ -68,150 +68,2 @@ } | ||
// ../../node_modules/.pnpm/perfect-freehand@1.2.2/node_modules/perfect-freehand/dist/esm/index.mjs | ||
function $(e, t, u, x = (h) => h) { | ||
return e * x(0.5 - t * (0.5 - u)); | ||
} | ||
function se(e) { | ||
return [-e[0], -e[1]]; | ||
} | ||
function l(e, t) { | ||
return [e[0] + t[0], e[1] + t[1]]; | ||
} | ||
function a(e, t) { | ||
return [e[0] - t[0], e[1] - t[1]]; | ||
} | ||
function b(e, t) { | ||
return [e[0] * t, e[1] * t]; | ||
} | ||
function he(e, t) { | ||
return [e[0] / t, e[1] / t]; | ||
} | ||
function R(e) { | ||
return [e[1], -e[0]]; | ||
} | ||
function B(e, t) { | ||
return e[0] * t[0] + e[1] * t[1]; | ||
} | ||
function ue(e, t) { | ||
return e[0] === t[0] && e[1] === t[1]; | ||
} | ||
function ge(e) { | ||
return Math.hypot(e[0], e[1]); | ||
} | ||
function de(e) { | ||
return e[0] * e[0] + e[1] * e[1]; | ||
} | ||
function A(e, t) { | ||
return de(a(e, t)); | ||
} | ||
function G(e) { | ||
return he(e, ge(e)); | ||
} | ||
function ie(e, t) { | ||
return Math.hypot(e[1] - t[1], e[0] - t[0]); | ||
} | ||
function L(e, t, u) { | ||
let x = Math.sin(u), h = Math.cos(u), y = e[0] - t[0], n = e[1] - t[1], f = y * h - n * x, d = y * x + n * h; | ||
return [f + t[0], d + t[1]]; | ||
} | ||
function K(e, t, u) { | ||
return l(e, b(a(t, e), u)); | ||
} | ||
function ee(e, t, u) { | ||
return l(e, b(t, u)); | ||
} | ||
var { min: C, PI: xe } = Math; | ||
var pe = 0.275; | ||
var V = xe + 1e-4; | ||
function ce(e, t = {}) { | ||
let { size: u = 16, smoothing: x = 0.5, thinning: h = 0.5, simulatePressure: y = true, easing: n = (r) => r, start: f = {}, end: d = {}, last: D2 = false } = t, { cap: S = true, easing: j = (r) => r * (2 - r) } = f, { cap: q = true, easing: c = (r) => --r * r * r + 1 } = d; | ||
if (e.length === 0 || u <= 0) return []; | ||
let p = e[e.length - 1].runningLength, g = f.taper === false ? 0 : f.taper === true ? Math.max(u, p) : f.taper, T = d.taper === false ? 0 : d.taper === true ? Math.max(u, p) : d.taper, te = Math.pow(u * x, 2), _ = [], M = [], H = e.slice(0, 10).reduce((r, i) => { | ||
let o = i.pressure; | ||
if (y) { | ||
let s = C(1, i.distance / u), W = C(1, 1 - s); | ||
o = C(1, r + (W - r) * (s * pe)); | ||
} | ||
return (r + o) / 2; | ||
}, e[0].pressure), m = $(u, h, e[e.length - 1].pressure, n), U, X = e[0].vector, z = e[0].point, F = z, O = z, E = F, J = false; | ||
for (let r = 0; r < e.length; r++) { | ||
let { pressure: i } = e[r], { point: o, vector: s, distance: W, runningLength: I } = e[r]; | ||
if (r < e.length - 1 && p - I < 3) continue; | ||
if (h) { | ||
if (y) { | ||
let v = C(1, W / u), Z = C(1, 1 - v); | ||
i = C(1, H + (Z - H) * (v * pe)); | ||
} | ||
m = $(u, h, i, n); | ||
} else m = u / 2; | ||
U === void 0 && (U = m); | ||
let le = I < g ? j(I / g) : 1, fe = p - I < T ? c((p - I) / T) : 1; | ||
m = Math.max(0.01, m * Math.min(le, fe)); | ||
let re = (r < e.length - 1 ? e[r + 1] : e[r]).vector, Y = r < e.length - 1 ? B(s, re) : 1, be = B(s, X) < 0 && !J, ne = Y !== null && Y < 0; | ||
if (be || ne) { | ||
let v = b(R(X), m); | ||
for (let Z = 1 / 13, w = 0; w <= 1; w += Z) O = L(a(o, v), o, V * w), _.push(O), E = L(l(o, v), o, V * -w), M.push(E); | ||
z = O, F = E, ne && (J = true); | ||
continue; | ||
} | ||
if (J = false, r === e.length - 1) { | ||
let v = b(R(s), m); | ||
_.push(a(o, v)), M.push(l(o, v)); | ||
continue; | ||
} | ||
let oe = b(R(K(re, s, Y)), m); | ||
O = a(o, oe), (r <= 1 || A(z, O) > te) && (_.push(O), z = O), E = l(o, oe), (r <= 1 || A(F, E) > te) && (M.push(E), F = E), H = i, X = s; | ||
} | ||
let P = e[0].point.slice(0, 2), k = e.length > 1 ? e[e.length - 1].point.slice(0, 2) : l(e[0].point, [1, 1]), Q = [], N = []; | ||
if (e.length === 1) { | ||
if (!(g || T) || D2) { | ||
let r = ee(P, G(R(a(P, k))), -(U || m)), i = []; | ||
for (let o = 1 / 13, s = o; s <= 1; s += o) i.push(L(r, P, V * 2 * s)); | ||
return i; | ||
} | ||
} else { | ||
if (!(g || T && e.length === 1)) if (S) for (let i = 1 / 13, o = i; o <= 1; o += i) { | ||
let s = L(M[0], P, V * o); | ||
Q.push(s); | ||
} | ||
else { | ||
let i = a(_[0], M[0]), o = b(i, 0.5), s = b(i, 0.51); | ||
Q.push(a(P, o), a(P, s), l(P, s), l(P, o)); | ||
} | ||
let r = R(se(e[e.length - 1].vector)); | ||
if (T || g && e.length === 1) N.push(k); | ||
else if (q) { | ||
let i = ee(k, r, m); | ||
for (let o = 1 / 29, s = o; s < 1; s += o) N.push(L(i, k, V * 3 * s)); | ||
} else N.push(l(k, b(r, m)), l(k, b(r, m * 0.99)), a(k, b(r, m * 0.99)), a(k, b(r, m))); | ||
} | ||
return _.concat(N, M.reverse(), Q); | ||
} | ||
function me(e, t = {}) { | ||
var q; | ||
let { streamline: u = 0.5, size: x = 16, last: h = false } = t; | ||
if (e.length === 0) return []; | ||
let y = 0.15 + (1 - u) * 0.85, n = Array.isArray(e[0]) ? e : e.map(({ x: c, y: p, pressure: g = 0.5 }) => [c, p, g]); | ||
if (n.length === 2) { | ||
let c = n[1]; | ||
n = n.slice(0, -1); | ||
for (let p = 1; p < 5; p++) n.push(K(n[0], c, p / 4)); | ||
} | ||
n.length === 1 && (n = [...n, [...l(n[0], [1, 1]), ...n[0].slice(2)]]); | ||
let f = [{ point: [n[0][0], n[0][1]], pressure: n[0][2] >= 0 ? n[0][2] : 0.25, vector: [1, 1], distance: 0, runningLength: 0 }], d = false, D2 = 0, S = f[0], j = n.length - 1; | ||
for (let c = 1; c < n.length; c++) { | ||
let p = h && c === j ? n[c].slice(0, 2) : K(S.point, n[c], y); | ||
if (ue(S.point, p)) continue; | ||
let g = ie(p, S.point); | ||
if (D2 += g, c < j && !d) { | ||
if (D2 < x) continue; | ||
d = true; | ||
} | ||
S = { point: p, pressure: n[c][2] >= 0 ? n[c][2] : 0.5, vector: G(a(S.point, p)), distance: g, runningLength: D2 }, f.push(S); | ||
} | ||
return f[0].vector = ((q = f[1]) == null ? void 0 : q.vector) || [0, 0], f; | ||
} | ||
function ae(e, t = {}) { | ||
return ce(me(e, t), t); | ||
} | ||
// src/utils/index.ts | ||
@@ -236,2 +88,95 @@ function numSort(a2, b2) { | ||
// src/utils/dom.ts | ||
function createArrowHead(id, fill) { | ||
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); | ||
const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker"); | ||
const head = document.createElementNS("http://www.w3.org/2000/svg", "path"); | ||
head.setAttribute("fill", fill); | ||
marker.setAttribute("id", id); | ||
marker.setAttribute("viewBox", "0 -5 10 10"); | ||
marker.setAttribute("refX", "5"); | ||
marker.setAttribute("refY", "0"); | ||
marker.setAttribute("markerWidth", "4"); | ||
marker.setAttribute("markerHeight", "4"); | ||
marker.setAttribute("orient", "auto"); | ||
head.setAttribute("d", "M0,-5L10,0L0,5"); | ||
marker.appendChild(head); | ||
defs.appendChild(marker); | ||
return defs; | ||
} | ||
// src/utils/simplify.ts | ||
function getSqDist(p1, p2) { | ||
const dx = p1.x - p2.x; | ||
const dy = p1.y - p2.y; | ||
return dx * dx + dy * dy; | ||
} | ||
function getSqSegDist(p, p1, p2) { | ||
let x = p1.x; | ||
let y = p1.y; | ||
let dx = p2.x - x; | ||
let dy = p2.y - y; | ||
if (dx !== 0 || dy !== 0) { | ||
const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy); | ||
if (t > 1) { | ||
x = p2.x; | ||
y = p2.y; | ||
} else if (t > 0) { | ||
x += dx * t; | ||
y += dy * t; | ||
} | ||
} | ||
dx = p.x - x; | ||
dy = p.y - y; | ||
return dx * dx + dy * dy; | ||
} | ||
function simplifyRadialDist(points, sqTolerance) { | ||
let prevPoint = points[0]; | ||
const newPoints = [prevPoint]; | ||
let point; | ||
for (let i = 1, len = points.length; i < len; i++) { | ||
point = points[i]; | ||
if (getSqDist(point, prevPoint) > sqTolerance) { | ||
newPoints.push(point); | ||
prevPoint = point; | ||
} | ||
} | ||
if (prevPoint !== point && point) | ||
newPoints.push(point); | ||
return newPoints; | ||
} | ||
function simplifyDPStep(points, first, last, sqTolerance, simplified) { | ||
let maxSqDist = sqTolerance; | ||
let index = 0; | ||
for (let i = first + 1; i < last; i++) { | ||
const sqDist = getSqSegDist(points[i], points[first], points[last]); | ||
if (sqDist > maxSqDist) { | ||
index = i; | ||
maxSqDist = sqDist; | ||
} | ||
} | ||
if (maxSqDist > sqTolerance) { | ||
if (index - first > 1) | ||
simplifyDPStep(points, first, index, sqTolerance, simplified); | ||
simplified.push(points[index]); | ||
if (last - index > 1) | ||
simplifyDPStep(points, index, last, sqTolerance, simplified); | ||
} | ||
} | ||
function simplifyDouglasPeucker(points, sqTolerance) { | ||
const last = points.length - 1; | ||
const simplified = [points[0]]; | ||
simplifyDPStep(points, 0, last, sqTolerance, simplified); | ||
simplified.push(points[last]); | ||
return simplified; | ||
} | ||
function simplify(points, tolerance, highestQuality = false) { | ||
if (points.length <= 2) | ||
return points; | ||
const sqTolerance = tolerance !== void 0 ? tolerance * tolerance : 1; | ||
points = highestQuality ? points : simplifyRadialDist(points, sqTolerance); | ||
points = simplifyDouglasPeucker(points, sqTolerance); | ||
return points; | ||
} | ||
// src/models/base.ts | ||
@@ -338,13 +283,17 @@ var BaseModel = class { | ||
// src/models/stylus.ts | ||
var StylusModel = class _StylusModel extends BaseModel { | ||
// src/models/draw.ts | ||
var DrawModel = class _DrawModel extends BaseModel { | ||
constructor() { | ||
super(...arguments); | ||
this.points = []; | ||
this.count = 0; | ||
} | ||
onStart(point) { | ||
this.el = document.createElementNS("http://www.w3.org/2000/svg", "path"); | ||
this.el = this.createElement("path", { fill: "transparent" }); | ||
this.points = [point]; | ||
this.attr("fill", this.brush.color); | ||
this.attr("d", this.getSvgData(this.points)); | ||
if (this.brush.arrowEnd) { | ||
this.arrowId = guid(); | ||
const head = createArrowHead(this.arrowId, this.brush.color); | ||
this.el.appendChild(head); | ||
} | ||
return this.el; | ||
@@ -355,5 +304,11 @@ } | ||
this.onStart(point); | ||
if (this.points[this.points.length - 1] !== point) | ||
if (this.points[this.points.length - 1] !== point) { | ||
this.points.push(point); | ||
this.attr("d", this.getSvgData(this.points)); | ||
this.count += 1; | ||
} | ||
if (this.count > 5) { | ||
this.points = simplify(this.points, 1, true); | ||
this.count = 0; | ||
} | ||
this.attr("d", _DrawModel.toSvgData(this.points)); | ||
return true; | ||
@@ -366,31 +321,42 @@ } | ||
return false; | ||
path.setAttribute("d", _DrawModel.toSvgData(simplify(this.points, 1, true))); | ||
if (!path.getTotalLength()) { | ||
const { x, y } = this.points[0]; | ||
const r = this.brush.size / 2; | ||
path.setAttribute("d", `M ${x - r} ${y} a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 ${-r * 2},0`); | ||
path.setAttribute("fill", this.brush.color); | ||
path.setAttribute("stroke-width", "0"); | ||
} | ||
return true; | ||
} | ||
getSvgData(points) { | ||
return _StylusModel.getSvgData(points, this.brush); | ||
// https://francoisromain.medium.com/smooth-a-svg-path-with-cubic-bezier-curves-e37b49d46c74 | ||
static line(a2, b2) { | ||
const lengthX = b2.x - a2.x; | ||
const lengthY = b2.y - a2.y; | ||
return { | ||
length: Math.sqrt(lengthX ** 2 + lengthY ** 2), | ||
angle: Math.atan2(lengthY, lengthX) | ||
}; | ||
} | ||
static getSvgData(points, brush) { | ||
const stroke = ae(points, __spreadValues({ | ||
size: brush.size, | ||
thinning: 0.9, | ||
simulatePressure: false, | ||
start: { | ||
taper: 5 | ||
}, | ||
end: { | ||
taper: 5 | ||
} | ||
}, brush.stylusOptions)); | ||
if (!stroke.length) | ||
return ""; | ||
const d = stroke.reduce( | ||
(acc, [x0, y0], i, arr) => { | ||
const [x1, y1] = arr[(i + 1) % arr.length]; | ||
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2); | ||
return acc; | ||
}, | ||
["M", ...stroke[0], "Q"] | ||
static controlPoint(current, previous, next, reverse) { | ||
const p = previous || current; | ||
const n = next || current; | ||
const smoothing = 0.2; | ||
const o = _DrawModel.line(p, n); | ||
const angle = o.angle + (reverse ? Math.PI : 0); | ||
const length = o.length * smoothing; | ||
const x = current.x + Math.cos(angle) * length; | ||
const y = current.y + Math.sin(angle) * length; | ||
return { x, y }; | ||
} | ||
static bezierCommand(point, i, points) { | ||
const cps = _DrawModel.controlPoint(points[i - 1], points[i - 2], point); | ||
const cpe = _DrawModel.controlPoint(point, points[i - 1], points[i + 1], true); | ||
return `C ${cps.x.toFixed(D)},${cps.y.toFixed(D)} ${cpe.x.toFixed(D)},${cpe.y.toFixed(D)} ${point.x.toFixed(D)},${point.y.toFixed(D)}`; | ||
} | ||
static toSvgData(points) { | ||
return points.reduce( | ||
(acc, point, i, a2) => i === 0 ? `M ${point.x.toFixed(D)},${point.y.toFixed(D)}` : `${acc} ${_DrawModel.bezierCommand(point, i, a2)}`, | ||
"" | ||
); | ||
d.push("Z"); | ||
return d.map((i) => typeof i === "number" ? i.toFixed(2) : i).join(" "); | ||
} | ||
@@ -437,4 +403,8 @@ }; | ||
return false; | ||
if (!path.getTotalLength()) | ||
try { | ||
if (!path.getTotalLength()) | ||
return false; | ||
} catch (e) { | ||
return false; | ||
} | ||
return true; | ||
@@ -444,20 +414,116 @@ } | ||
// src/utils/dom.ts | ||
function createArrowHead(id, fill) { | ||
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); | ||
const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker"); | ||
const head = document.createElementNS("http://www.w3.org/2000/svg", "path"); | ||
head.setAttribute("fill", fill); | ||
marker.setAttribute("id", id); | ||
marker.setAttribute("viewBox", "0 -5 10 10"); | ||
marker.setAttribute("refX", "5"); | ||
marker.setAttribute("refY", "0"); | ||
marker.setAttribute("markerWidth", "4"); | ||
marker.setAttribute("markerHeight", "4"); | ||
marker.setAttribute("orient", "auto"); | ||
head.setAttribute("d", "M0,-5L10,0L0,5"); | ||
marker.appendChild(head); | ||
defs.appendChild(marker); | ||
return defs; | ||
} | ||
// src/models/eraser.ts | ||
var EraserModel = class extends BaseModel { | ||
constructor() { | ||
super(...arguments); | ||
this.pathSubFactor = 20; | ||
this.pathFragments = []; | ||
this._erased = []; | ||
} | ||
onSelected(el) { | ||
const calculatePathFragments = (children, element) => { | ||
if (children && children.length) { | ||
for (let i = 0; i < children.length; i++) { | ||
const ele = children[i]; | ||
if (ele.getTotalLength) { | ||
const pathLength = ele.getTotalLength(); | ||
for (let j = 0; j < this.pathSubFactor; j++) { | ||
const pos1 = ele.getPointAtLength(pathLength * j / this.pathSubFactor); | ||
const pos2 = ele.getPointAtLength(pathLength * (j + 1) / this.pathSubFactor); | ||
this.pathFragments.push({ | ||
x1: pos1.x, | ||
x2: pos2.x, | ||
y1: pos1.y, | ||
y2: pos2.y, | ||
segment: j, | ||
element: element || ele | ||
}); | ||
} | ||
} else { | ||
if (ele.children) | ||
calculatePathFragments(ele.children, ele); | ||
} | ||
} | ||
} | ||
}; | ||
if (el) | ||
calculatePathFragments(el.children); | ||
} | ||
onUnselected() { | ||
this.pathFragments = []; | ||
} | ||
onStart(point) { | ||
this.svgPointPrevious = this.svgElement.createSVGPoint(); | ||
this.svgPointPrevious.x = point.x; | ||
this.svgPointPrevious.y = point.y; | ||
return void 0; | ||
} | ||
onMove(point) { | ||
this.svgPointCurrent = this.svgElement.createSVGPoint(); | ||
this.svgPointCurrent.x = point.x; | ||
this.svgPointCurrent.y = point.y; | ||
const erased = this.checkAndEraseElement(); | ||
this.svgPointPrevious = this.svgPointCurrent; | ||
return erased; | ||
} | ||
onEnd() { | ||
this.svgPointPrevious = void 0; | ||
this.svgPointCurrent = void 0; | ||
const erased = this._erased; | ||
this._erased = []; | ||
return { | ||
undo: () => erased.forEach((v) => this.drauu._restoreNode(v)), | ||
redo: () => erased.forEach((v) => this.drauu._removeNode(v)) | ||
}; | ||
} | ||
checkAndEraseElement() { | ||
if (this.pathFragments.length) { | ||
for (let i = 0; i < this.pathFragments.length; i++) { | ||
const segment = this.pathFragments[i]; | ||
if (this.svgPointPrevious && this.svgPointCurrent) { | ||
const line = { | ||
x1: this.svgPointPrevious.x, | ||
x2: this.svgPointCurrent.x, | ||
y1: this.svgPointPrevious.y, | ||
y2: this.svgPointCurrent.y | ||
}; | ||
if (this.lineLineIntersect(segment, line)) { | ||
this.drauu._removeNode(segment.element); | ||
this._erased.push(segment.element); | ||
} | ||
} | ||
} | ||
} | ||
if (this._erased.length) | ||
this.pathFragments = this.pathFragments.filter((v) => !this._erased.includes(v.element)); | ||
return this._erased.length > 0; | ||
} | ||
lineLineIntersect(line1, line2) { | ||
const x1 = line1.x1; | ||
const x2 = line1.x2; | ||
const x3 = line2.x1; | ||
const x4 = line2.x2; | ||
const y1 = line1.y1; | ||
const y2 = line1.y2; | ||
const y3 = line2.y1; | ||
const y4 = line2.y2; | ||
const pt_denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); | ||
const pt_x_num = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4); | ||
const pt_y_num = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4); | ||
const btwn = (a2, b1, b2) => { | ||
if (a2 >= b1 && a2 <= b2) | ||
return true; | ||
return a2 >= b2 && a2 <= b1; | ||
}; | ||
if (pt_denom === 0) { | ||
return false; | ||
} else { | ||
const pt = { | ||
x: pt_x_num / pt_denom, | ||
y: pt_y_num / pt_denom | ||
}; | ||
return btwn(pt.x, x1, x2) && btwn(pt.y, y1, y2) && btwn(pt.x, x3, x4) && btwn(pt.y, y3, y4); | ||
} | ||
} | ||
}; | ||
@@ -519,4 +585,8 @@ // src/models/line.ts | ||
return false; | ||
if (path.getTotalLength() < 5) | ||
try { | ||
if (path.getTotalLength() < 5) | ||
return false; | ||
} catch (e) { | ||
return false; | ||
} | ||
return true; | ||
@@ -568,4 +638,8 @@ } | ||
return false; | ||
if (!path.getTotalLength()) | ||
try { | ||
if (!path.getTotalLength()) | ||
return false; | ||
} catch (e) { | ||
return false; | ||
} | ||
return true; | ||
@@ -575,91 +649,161 @@ } | ||
// src/utils/simplify.ts | ||
function getSqDist(p1, p2) { | ||
const dx = p1.x - p2.x; | ||
const dy = p1.y - p2.y; | ||
return dx * dx + dy * dy; | ||
// ../../node_modules/.pnpm/perfect-freehand@1.2.2/node_modules/perfect-freehand/dist/esm/index.mjs | ||
function $(e, t, u, x = (h) => h) { | ||
return e * x(0.5 - t * (0.5 - u)); | ||
} | ||
function getSqSegDist(p, p1, p2) { | ||
let x = p1.x; | ||
let y = p1.y; | ||
let dx = p2.x - x; | ||
let dy = p2.y - y; | ||
if (dx !== 0 || dy !== 0) { | ||
const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy); | ||
if (t > 1) { | ||
x = p2.x; | ||
y = p2.y; | ||
} else if (t > 0) { | ||
x += dx * t; | ||
y += dy * t; | ||
function se(e) { | ||
return [-e[0], -e[1]]; | ||
} | ||
function l(e, t) { | ||
return [e[0] + t[0], e[1] + t[1]]; | ||
} | ||
function a(e, t) { | ||
return [e[0] - t[0], e[1] - t[1]]; | ||
} | ||
function b(e, t) { | ||
return [e[0] * t, e[1] * t]; | ||
} | ||
function he(e, t) { | ||
return [e[0] / t, e[1] / t]; | ||
} | ||
function R(e) { | ||
return [e[1], -e[0]]; | ||
} | ||
function B(e, t) { | ||
return e[0] * t[0] + e[1] * t[1]; | ||
} | ||
function ue(e, t) { | ||
return e[0] === t[0] && e[1] === t[1]; | ||
} | ||
function ge(e) { | ||
return Math.hypot(e[0], e[1]); | ||
} | ||
function de(e) { | ||
return e[0] * e[0] + e[1] * e[1]; | ||
} | ||
function A(e, t) { | ||
return de(a(e, t)); | ||
} | ||
function G(e) { | ||
return he(e, ge(e)); | ||
} | ||
function ie(e, t) { | ||
return Math.hypot(e[1] - t[1], e[0] - t[0]); | ||
} | ||
function L(e, t, u) { | ||
let x = Math.sin(u), h = Math.cos(u), y = e[0] - t[0], n = e[1] - t[1], f = y * h - n * x, d = y * x + n * h; | ||
return [f + t[0], d + t[1]]; | ||
} | ||
function K(e, t, u) { | ||
return l(e, b(a(t, e), u)); | ||
} | ||
function ee(e, t, u) { | ||
return l(e, b(t, u)); | ||
} | ||
var { min: C, PI: xe } = Math; | ||
var pe = 0.275; | ||
var V = xe + 1e-4; | ||
function ce(e, t = {}) { | ||
let { size: u = 16, smoothing: x = 0.5, thinning: h = 0.5, simulatePressure: y = true, easing: n = (r) => r, start: f = {}, end: d = {}, last: D2 = false } = t, { cap: S = true, easing: j = (r) => r * (2 - r) } = f, { cap: q = true, easing: c = (r) => --r * r * r + 1 } = d; | ||
if (e.length === 0 || u <= 0) return []; | ||
let p = e[e.length - 1].runningLength, g = f.taper === false ? 0 : f.taper === true ? Math.max(u, p) : f.taper, T = d.taper === false ? 0 : d.taper === true ? Math.max(u, p) : d.taper, te = Math.pow(u * x, 2), _ = [], M = [], H = e.slice(0, 10).reduce((r, i) => { | ||
let o = i.pressure; | ||
if (y) { | ||
let s = C(1, i.distance / u), W = C(1, 1 - s); | ||
o = C(1, r + (W - r) * (s * pe)); | ||
} | ||
return (r + o) / 2; | ||
}, e[0].pressure), m = $(u, h, e[e.length - 1].pressure, n), U, X = e[0].vector, z = e[0].point, F = z, O = z, E = F, J = false; | ||
for (let r = 0; r < e.length; r++) { | ||
let { pressure: i } = e[r], { point: o, vector: s, distance: W, runningLength: I } = e[r]; | ||
if (r < e.length - 1 && p - I < 3) continue; | ||
if (h) { | ||
if (y) { | ||
let v = C(1, W / u), Z = C(1, 1 - v); | ||
i = C(1, H + (Z - H) * (v * pe)); | ||
} | ||
m = $(u, h, i, n); | ||
} else m = u / 2; | ||
U === void 0 && (U = m); | ||
let le = I < g ? j(I / g) : 1, fe = p - I < T ? c((p - I) / T) : 1; | ||
m = Math.max(0.01, m * Math.min(le, fe)); | ||
let re = (r < e.length - 1 ? e[r + 1] : e[r]).vector, Y = r < e.length - 1 ? B(s, re) : 1, be = B(s, X) < 0 && !J, ne = Y !== null && Y < 0; | ||
if (be || ne) { | ||
let v = b(R(X), m); | ||
for (let Z = 1 / 13, w = 0; w <= 1; w += Z) O = L(a(o, v), o, V * w), _.push(O), E = L(l(o, v), o, V * -w), M.push(E); | ||
z = O, F = E, ne && (J = true); | ||
continue; | ||
} | ||
if (J = false, r === e.length - 1) { | ||
let v = b(R(s), m); | ||
_.push(a(o, v)), M.push(l(o, v)); | ||
continue; | ||
} | ||
let oe = b(R(K(re, s, Y)), m); | ||
O = a(o, oe), (r <= 1 || A(z, O) > te) && (_.push(O), z = O), E = l(o, oe), (r <= 1 || A(F, E) > te) && (M.push(E), F = E), H = i, X = s; | ||
} | ||
dx = p.x - x; | ||
dy = p.y - y; | ||
return dx * dx + dy * dy; | ||
} | ||
function simplifyRadialDist(points, sqTolerance) { | ||
let prevPoint = points[0]; | ||
const newPoints = [prevPoint]; | ||
let point; | ||
for (let i = 1, len = points.length; i < len; i++) { | ||
point = points[i]; | ||
if (getSqDist(point, prevPoint) > sqTolerance) { | ||
newPoints.push(point); | ||
prevPoint = point; | ||
let P = e[0].point.slice(0, 2), k = e.length > 1 ? e[e.length - 1].point.slice(0, 2) : l(e[0].point, [1, 1]), Q = [], N = []; | ||
if (e.length === 1) { | ||
if (!(g || T) || D2) { | ||
let r = ee(P, G(R(a(P, k))), -(U || m)), i = []; | ||
for (let o = 1 / 13, s = o; s <= 1; s += o) i.push(L(r, P, V * 2 * s)); | ||
return i; | ||
} | ||
} else { | ||
if (!(g || T && e.length === 1)) if (S) for (let i = 1 / 13, o = i; o <= 1; o += i) { | ||
let s = L(M[0], P, V * o); | ||
Q.push(s); | ||
} | ||
else { | ||
let i = a(_[0], M[0]), o = b(i, 0.5), s = b(i, 0.51); | ||
Q.push(a(P, o), a(P, s), l(P, s), l(P, o)); | ||
} | ||
let r = R(se(e[e.length - 1].vector)); | ||
if (T || g && e.length === 1) N.push(k); | ||
else if (q) { | ||
let i = ee(k, r, m); | ||
for (let o = 1 / 29, s = o; s < 1; s += o) N.push(L(i, k, V * 3 * s)); | ||
} else N.push(l(k, b(r, m)), l(k, b(r, m * 0.99)), a(k, b(r, m * 0.99)), a(k, b(r, m))); | ||
} | ||
if (prevPoint !== point && point) | ||
newPoints.push(point); | ||
return newPoints; | ||
return _.concat(N, M.reverse(), Q); | ||
} | ||
function simplifyDPStep(points, first, last, sqTolerance, simplified) { | ||
let maxSqDist = sqTolerance; | ||
let index = 0; | ||
for (let i = first + 1; i < last; i++) { | ||
const sqDist = getSqSegDist(points[i], points[first], points[last]); | ||
if (sqDist > maxSqDist) { | ||
index = i; | ||
maxSqDist = sqDist; | ||
function me(e, t = {}) { | ||
var q; | ||
let { streamline: u = 0.5, size: x = 16, last: h = false } = t; | ||
if (e.length === 0) return []; | ||
let y = 0.15 + (1 - u) * 0.85, n = Array.isArray(e[0]) ? e : e.map(({ x: c, y: p, pressure: g = 0.5 }) => [c, p, g]); | ||
if (n.length === 2) { | ||
let c = n[1]; | ||
n = n.slice(0, -1); | ||
for (let p = 1; p < 5; p++) n.push(K(n[0], c, p / 4)); | ||
} | ||
n.length === 1 && (n = [...n, [...l(n[0], [1, 1]), ...n[0].slice(2)]]); | ||
let f = [{ point: [n[0][0], n[0][1]], pressure: n[0][2] >= 0 ? n[0][2] : 0.25, vector: [1, 1], distance: 0, runningLength: 0 }], d = false, D2 = 0, S = f[0], j = n.length - 1; | ||
for (let c = 1; c < n.length; c++) { | ||
let p = h && c === j ? n[c].slice(0, 2) : K(S.point, n[c], y); | ||
if (ue(S.point, p)) continue; | ||
let g = ie(p, S.point); | ||
if (D2 += g, c < j && !d) { | ||
if (D2 < x) continue; | ||
d = true; | ||
} | ||
S = { point: p, pressure: n[c][2] >= 0 ? n[c][2] : 0.5, vector: G(a(S.point, p)), distance: g, runningLength: D2 }, f.push(S); | ||
} | ||
if (maxSqDist > sqTolerance) { | ||
if (index - first > 1) | ||
simplifyDPStep(points, first, index, sqTolerance, simplified); | ||
simplified.push(points[index]); | ||
if (last - index > 1) | ||
simplifyDPStep(points, index, last, sqTolerance, simplified); | ||
} | ||
return f[0].vector = ((q = f[1]) == null ? void 0 : q.vector) || [0, 0], f; | ||
} | ||
function simplifyDouglasPeucker(points, sqTolerance) { | ||
const last = points.length - 1; | ||
const simplified = [points[0]]; | ||
simplifyDPStep(points, 0, last, sqTolerance, simplified); | ||
simplified.push(points[last]); | ||
return simplified; | ||
function ae(e, t = {}) { | ||
return ce(me(e, t), t); | ||
} | ||
function simplify(points, tolerance, highestQuality = false) { | ||
if (points.length <= 2) | ||
return points; | ||
const sqTolerance = tolerance !== void 0 ? tolerance * tolerance : 1; | ||
points = highestQuality ? points : simplifyRadialDist(points, sqTolerance); | ||
points = simplifyDouglasPeucker(points, sqTolerance); | ||
return points; | ||
} | ||
// src/models/draw.ts | ||
var DrawModel = class _DrawModel extends BaseModel { | ||
// src/models/stylus.ts | ||
var StylusModel = class _StylusModel extends BaseModel { | ||
constructor() { | ||
super(...arguments); | ||
this.points = []; | ||
this.count = 0; | ||
} | ||
onStart(point) { | ||
this.el = this.createElement("path", { fill: "transparent" }); | ||
this.el = document.createElementNS("http://www.w3.org/2000/svg", "path"); | ||
this.points = [point]; | ||
if (this.brush.arrowEnd) { | ||
this.arrowId = guid(); | ||
const head = createArrowHead(this.arrowId, this.brush.color); | ||
this.el.appendChild(head); | ||
} | ||
this.attr("fill", this.brush.color); | ||
this.attr("d", this.getSvgData(this.points)); | ||
return this.el; | ||
@@ -670,11 +814,5 @@ } | ||
this.onStart(point); | ||
if (this.points[this.points.length - 1] !== point) { | ||
if (this.points[this.points.length - 1] !== point) | ||
this.points.push(point); | ||
this.count += 1; | ||
} | ||
if (this.count > 5) { | ||
this.points = simplify(this.points, 1, true); | ||
this.count = 0; | ||
} | ||
this.attr("d", _DrawModel.toSvgData(this.points)); | ||
this.attr("d", this.getSvgData(this.points)); | ||
return true; | ||
@@ -687,158 +825,34 @@ } | ||
return false; | ||
path.setAttribute("d", _DrawModel.toSvgData(simplify(this.points, 1, true))); | ||
if (!path.getTotalLength()) { | ||
const { x, y } = this.points[0]; | ||
const r = this.brush.size / 2; | ||
path.setAttribute("d", `M ${x - r} ${y} a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 ${-r * 2},0`); | ||
path.setAttribute("fill", this.brush.color); | ||
path.setAttribute("stroke-width", "0"); | ||
} | ||
return true; | ||
} | ||
// https://francoisromain.medium.com/smooth-a-svg-path-with-cubic-bezier-curves-e37b49d46c74 | ||
static line(a2, b2) { | ||
const lengthX = b2.x - a2.x; | ||
const lengthY = b2.y - a2.y; | ||
return { | ||
length: Math.sqrt(lengthX ** 2 + lengthY ** 2), | ||
angle: Math.atan2(lengthY, lengthX) | ||
}; | ||
getSvgData(points) { | ||
return _StylusModel.getSvgData(points, this.brush); | ||
} | ||
static controlPoint(current, previous, next, reverse) { | ||
const p = previous || current; | ||
const n = next || current; | ||
const smoothing = 0.2; | ||
const o = _DrawModel.line(p, n); | ||
const angle = o.angle + (reverse ? Math.PI : 0); | ||
const length = o.length * smoothing; | ||
const x = current.x + Math.cos(angle) * length; | ||
const y = current.y + Math.sin(angle) * length; | ||
return { x, y }; | ||
} | ||
static bezierCommand(point, i, points) { | ||
const cps = _DrawModel.controlPoint(points[i - 1], points[i - 2], point); | ||
const cpe = _DrawModel.controlPoint(point, points[i - 1], points[i + 1], true); | ||
return `C ${cps.x.toFixed(D)},${cps.y.toFixed(D)} ${cpe.x.toFixed(D)},${cpe.y.toFixed(D)} ${point.x.toFixed(D)},${point.y.toFixed(D)}`; | ||
} | ||
static toSvgData(points) { | ||
return points.reduce( | ||
(acc, point, i, a2) => i === 0 ? `M ${point.x.toFixed(D)},${point.y.toFixed(D)}` : `${acc} ${_DrawModel.bezierCommand(point, i, a2)}`, | ||
"" | ||
static getSvgData(points, brush) { | ||
const stroke = ae(points, __spreadValues({ | ||
size: brush.size, | ||
thinning: 0.9, | ||
simulatePressure: false, | ||
start: { | ||
taper: 5 | ||
}, | ||
end: { | ||
taper: 5 | ||
} | ||
}, brush.stylusOptions)); | ||
if (!stroke.length) | ||
return ""; | ||
const d = stroke.reduce( | ||
(acc, [x0, y0], i, arr) => { | ||
const [x1, y1] = arr[(i + 1) % arr.length]; | ||
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2); | ||
return acc; | ||
}, | ||
["M", ...stroke[0], "Q"] | ||
); | ||
d.push("Z"); | ||
return d.map((i) => typeof i === "number" ? i.toFixed(2) : i).join(" "); | ||
} | ||
}; | ||
// src/models/eraser.ts | ||
var EraserModel = class extends BaseModel { | ||
constructor() { | ||
super(...arguments); | ||
this.pathSubFactor = 20; | ||
this.pathFragments = []; | ||
this._erased = []; | ||
} | ||
onSelected(el) { | ||
const calculatePathFragments = (children, element) => { | ||
if (children && children.length) { | ||
for (let i = 0; i < children.length; i++) { | ||
const ele = children[i]; | ||
if (ele.getTotalLength) { | ||
const pathLength = ele.getTotalLength(); | ||
for (let j = 0; j < this.pathSubFactor; j++) { | ||
const pos1 = ele.getPointAtLength(pathLength * j / this.pathSubFactor); | ||
const pos2 = ele.getPointAtLength(pathLength * (j + 1) / this.pathSubFactor); | ||
this.pathFragments.push({ | ||
x1: pos1.x, | ||
x2: pos2.x, | ||
y1: pos1.y, | ||
y2: pos2.y, | ||
segment: j, | ||
element: element || ele | ||
}); | ||
} | ||
} else { | ||
if (ele.children) | ||
calculatePathFragments(ele.children, ele); | ||
} | ||
} | ||
} | ||
}; | ||
if (el) | ||
calculatePathFragments(el.children); | ||
} | ||
onUnselected() { | ||
this.pathFragments = []; | ||
} | ||
onStart(point) { | ||
this.svgPointPrevious = this.svgElement.createSVGPoint(); | ||
this.svgPointPrevious.x = point.x; | ||
this.svgPointPrevious.y = point.y; | ||
return void 0; | ||
} | ||
onMove(point) { | ||
this.svgPointCurrent = this.svgElement.createSVGPoint(); | ||
this.svgPointCurrent.x = point.x; | ||
this.svgPointCurrent.y = point.y; | ||
const erased = this.checkAndEraseElement(); | ||
this.svgPointPrevious = this.svgPointCurrent; | ||
return erased; | ||
} | ||
onEnd() { | ||
this.svgPointPrevious = void 0; | ||
this.svgPointCurrent = void 0; | ||
const erased = this._erased; | ||
this._erased = []; | ||
return { | ||
undo: () => erased.forEach((v) => this.drauu._restoreNode(v)), | ||
redo: () => erased.forEach((v) => this.drauu._removeNode(v)) | ||
}; | ||
} | ||
checkAndEraseElement() { | ||
if (this.pathFragments.length) { | ||
for (let i = 0; i < this.pathFragments.length; i++) { | ||
const segment = this.pathFragments[i]; | ||
const line = { | ||
x1: this.svgPointPrevious.x, | ||
x2: this.svgPointCurrent.x, | ||
y1: this.svgPointPrevious.y, | ||
y2: this.svgPointCurrent.y | ||
}; | ||
if (this.lineLineIntersect(segment, line)) { | ||
this.drauu._removeNode(segment.element); | ||
this._erased.push(segment.element); | ||
} | ||
} | ||
} | ||
if (this._erased.length) | ||
this.pathFragments = this.pathFragments.filter((v) => !this._erased.includes(v.element)); | ||
return this._erased.length > 0; | ||
} | ||
lineLineIntersect(line1, line2) { | ||
const x1 = line1.x1; | ||
const x2 = line1.x2; | ||
const x3 = line2.x1; | ||
const x4 = line2.x2; | ||
const y1 = line1.y1; | ||
const y2 = line1.y2; | ||
const y3 = line2.y1; | ||
const y4 = line2.y2; | ||
const pt_denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); | ||
const pt_x_num = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4); | ||
const pt_y_num = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4); | ||
const btwn = (a2, b1, b2) => { | ||
if (a2 >= b1 && a2 <= b2) | ||
return true; | ||
return a2 >= b2 && a2 <= b1; | ||
}; | ||
if (pt_denom === 0) { | ||
return false; | ||
} else { | ||
const pt = { | ||
x: pt_x_num / pt_denom, | ||
y: pt_y_num / pt_denom | ||
}; | ||
return btwn(pt.x, x1, x2) && btwn(pt.y, y1, y2) && btwn(pt.x, x3, x4) && btwn(pt.y, y3, y4); | ||
} | ||
} | ||
}; | ||
// src/models/index.ts | ||
@@ -999,7 +1013,9 @@ function createModels(drauu) { | ||
const el = this._currentNode; | ||
this._appendNode(el); | ||
this.commit({ | ||
undo: () => this._removeNode(el), | ||
redo: () => this._restoreNode(el) | ||
}); | ||
if (el) { | ||
this._appendNode(el); | ||
this.commit({ | ||
undo: () => this._removeNode(el), | ||
redo: () => this._restoreNode(el) | ||
}); | ||
} | ||
} else { | ||
@@ -1006,0 +1022,0 @@ this.commit(result); |
{ | ||
"name": "@drauu/core", | ||
"version": "0.4.1", | ||
"version": "0.4.2", | ||
"author": "Anthony Fu <anthonyfu117@hotmail.com>", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
117735
3499