aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/bezierFit.ts
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2025-03-04 00:52:53 -0500
committerbobzel <zzzman@gmail.com>2025-03-04 00:52:53 -0500
commit215ad40efa2e343e290d18bffbc55884829f1a0d (patch)
tree2e4e3310aad1ea5b39a874ecbc98efb1312bd21b /src/client/util/bezierFit.ts
parent0e7ae057264445ece675e4b5d2380893ea124112 (diff)
fixed up smartDrawHandler a bit to support svg's better. you can now drop in a .svg file from the filesystem - still some unfinished business (arcs, background/foreground color inversion)
Diffstat (limited to 'src/client/util/bezierFit.ts')
-rw-r--r--src/client/util/bezierFit.ts302
1 files changed, 249 insertions, 53 deletions
diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index 7ef370d48..65bd44bf9 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -1,7 +1,5 @@
/* eslint-disable no-use-before-define */
-/* eslint-disable prefer-destructuring */
/* eslint-disable no-param-reassign */
-/* eslint-disable camelcase */
import { Point } from '../../pen-gestures/ndollar';
export enum SVGType {
@@ -625,13 +623,130 @@ export function GenerateControlPoints(coordinates: Point[], alpha = 0.1) {
return [...firstEnd, ...points, ...lastEnd];
}
-export function SVGToBezier(name: SVGType, attributes: any): Point[] {
+function convertToAbsolute(pathData: string): string {
+ const commands = pathData.match(/[a-zA-Z][^a-zA-Z]*/g);
+ if (!commands) return pathData;
+
+ let currentX = 0;
+ let currentY = 0;
+ let startX = 0;
+ let startY = 0;
+
+ const absoluteCommands = commands.map(command => {
+ const type = command[0];
+ const values = command
+ .slice(1)
+ .trim()
+ .split(/[\s,]+/)
+ .map(v => +v);
+
+ switch (type) {
+ case 'M':
+ currentX = values[0];
+ currentY = values[1];
+ startX = currentX;
+ startY = currentY;
+ return `M${currentX},${currentY}`;
+ case 'm':
+ currentX += values[0];
+ currentY += values[1];
+ startX = currentX;
+ startY = currentY;
+ return `M${currentX},${currentY}`;
+ case 'L':
+ currentX = values[0];
+ currentY = values[1];
+ return `L${currentX},${currentY}`;
+ case 'l':
+ currentX += values[0];
+ currentY += values[1];
+ return `L${currentX},${currentY}`;
+ case 'H':
+ currentX = values[0];
+ return `H${currentX}`;
+ case 'h':
+ currentX += values[0];
+ return `H${currentX}`;
+ case 'V':
+ currentY = values[0];
+ return `V${currentY}`;
+ case 'v':
+ currentY += values[0];
+ return `V${currentY}`;
+ case 'C':
+ currentX = values[4];
+ currentY = values[5];
+ return `C${values.join(',')}`;
+ case 'c': {
+ let str = '';
+ for (let i = 0; i < values.length; i += 6) {
+ str += (i === 0 ? 'C':',') + (values[i] + currentX) +
+ ',' + (values[i + 1] + currentY) +
+ ',' + (values[i + 2] + currentX) +
+ ',' + (values[i + 3] + currentY) +
+ ',' + (values[i + 4] + currentX) +
+ ',' + (values[i + 5] + currentY); // prettier-ignore
+ currentX += values[i + 4];
+ currentY += values[i + 5];
+ }
+ return str;
+ }
+ case 'S':
+ currentX = values[2];
+ currentY = values[3];
+ return `S${values.join(',')}`;
+ case 's':
+ return `S${values.map((v, i) => (i % 2 === 0 ? (currentX += v) : (currentY += v))).join(',')}`;
+ case 'Q':
+ currentX = values[2];
+ currentY = values[3];
+ return `Q${values.join(',')}`;
+ case 'q': {
+ let str = '';
+ for (let i = 0; i < values.length; i += 4) {
+ str += (i === 0 ? 'Q':',') + (values[i] + currentX) +
+ ',' + (values[i + 1] + currentY) +
+ ',' + (values[i + 2] + currentX) +
+ ',' + (values[i + 3] + currentY); // prettier-ignore
+ currentX += values[i + 2];
+ currentY += values[i + 3];
+ }
+ return str;
+ }
+ case 'T':
+ currentX = values[0];
+ currentY = values[1];
+ return `T${currentX},${currentY}`;
+ case 't':
+ currentX += values[0];
+ currentY += values[1];
+ return `T${currentX},${currentY}`;
+ case 'A':
+ currentX = values[5];
+ currentY = values[6];
+ return `A${values.join(',')}`;
+ case 'a':
+ return `A${values.map((v, i) => (i % 2 === 0 ? (currentX += v) : (currentY += v))).join(',')}`;
+ case 'Z':
+ case 'z':
+ currentX = startX;
+ currentY = startY;
+ return 'Z';
+ default:
+ return command;
+ }
+ });
+
+ return absoluteCommands.join(' ');
+}
+
+export function SVGToBezier(name: SVGType, attributes: Record<string, string>, last: { X: number; Y: number }): Point[] {
switch (name) {
case 'line': {
- const x1 = parseInt(attributes.x1);
- const x2 = parseInt(attributes.x2);
- const y1 = parseInt(attributes.y1);
- const y2 = parseInt(attributes.y2);
+ const x1 = +attributes.x1;
+ const x2 = +attributes.x2;
+ const y1 = +attributes.y1;
+ const y2 = +attributes.y2;
return [
{ X: x1, Y: y1 },
{ X: x1, Y: y1 },
@@ -642,10 +757,10 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] {
case 'circle':
case 'ellipse': {
const c = 0.551915024494;
- const centerX = parseInt(attributes.cx);
- const centerY = parseInt(attributes.cy);
- const radiusX = parseInt(attributes.rx) || parseInt(attributes.r);
- const radiusY = parseInt(attributes.ry) || parseInt(attributes.r);
+ const centerX = +attributes.cx;
+ const centerY = +attributes.cy;
+ const radiusX = +attributes.rx || +attributes.r;
+ const radiusY = +attributes.ry || +attributes.r;
return [
{ X: centerX, Y: centerY + radiusY },
{ X: centerX + c * radiusX, Y: centerY + radiusY },
@@ -666,10 +781,10 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] {
];
}
case 'rect': {
- const x = parseInt(attributes.x);
- const y = parseInt(attributes.y);
- const width = parseInt(attributes.width);
- const height = parseInt(attributes.height);
+ const x = +attributes.x;
+ const y = +attributes.y;
+ const width = +attributes.width;
+ const height = +attributes.height;
return [
{ X: x, Y: y },
{ X: x, Y: y },
@@ -691,41 +806,122 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] {
}
case 'path': {
const coordList: Point[] = [];
- const [startX, startY] = attributes.d.match(/M(-?\d+\.?\d*),(-?\d+\.?\d*)/).slice(1);
- const startPt = { X: parseInt(startX), Y: parseInt(startY) };
- coordList.push(startPt);
- const matches: RegExpMatchArray[] = Array.from(
- attributes.d.matchAll(/Q(-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*)|C(-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*)|L(-?\d+\.?\d*),(-?\d+\.?\d*)/g)
- );
- let lastPt: Point = startPt;
- matches.forEach(match => {
- if (match[0].startsWith('Q')) {
- coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) });
- coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) });
- coordList.push({ X: parseInt(match[3]), Y: parseInt(match[4]) });
- coordList.push({ X: parseInt(match[3]), Y: parseInt(match[4]) });
- lastPt = { X: parseInt(match[3]), Y: parseInt(match[4]) };
- } else if (match[0].startsWith('C')) {
- coordList.push({ X: parseInt(match[5]), Y: parseInt(match[6]) });
- coordList.push({ X: parseInt(match[7]), Y: parseInt(match[8]) });
- coordList.push({ X: parseInt(match[9]), Y: parseInt(match[10]) });
- coordList.push({ X: parseInt(match[9]), Y: parseInt(match[10]) });
- lastPt = { X: parseInt(match[9]), Y: parseInt(match[10]) };
- } else {
- coordList.push(lastPt);
- coordList.push({ X: parseInt(match[11]), Y: parseInt(match[12]) });
- coordList.push({ X: parseInt(match[11]), Y: parseInt(match[12]) });
- coordList.push({ X: parseInt(match[11]), Y: parseInt(match[12]) });
- lastPt = { X: parseInt(match[11]), Y: parseInt(match[12]) };
+ let fixedattrs = attributes.d.trim().replace(/([0-9])-/g, '$1,-');
+ for (let i = 0; i < 100; i++) {
+ const test = fixedattrs.replace(/([0-9]?\.[0-9]+)(\.[0-9]+)/g, '$1,$2');
+ if (test === fixedattrs) break;
+ fixedattrs = test;
+ }
+ const attrdata = convertToAbsolute(fixedattrs);
+ const move = attrdata.match(/M(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)/);
+ const [startX, startY] = move?.slice(1) ?? [last.X + '', last.Y + ''];
+ const startPt = { X: +startX, Y: +startY };
+ let first = true;
+ let lastCmd = '';
+ for (let attr = attrdata.slice(move?.[0].length ?? 0).trim(); attr; ) {
+ lastCmd = 'AQCLVHZ'.includes(attr[0]) ? attr[0] : lastCmd;
+ switch (lastCmd) {
+ case 'Q': {
+ const match = attr.match(/Q?[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)/);
+ if (match) {
+ const prev = first ? startPt : coordList.lastElement();
+ const Q = [+match[1], +match[2], +match[3], +match[4]];
+
+ coordList.push(prev);
+ coordList.push({ X: prev.X + (2 / 3) * (Q[0] - prev.X), Y: prev.Y + (2 / 3) * (Q[1] - prev.Y) });
+ coordList.push({ X: Q[2] + (2 / 3) * (Q[0] - Q[2]), Y: Q[3] + (2 / 3) * (Q[1] - Q[3]) });
+ coordList.push({ X: Q[2], Y: Q[3] });
+ attr = attr.slice(match[0].length).trim();
+ } else {
+ attr = attr.slice(1).trim();
+ alert('error' + attr);
+ }
+ break;
+ }
+ case 'C': {
+ const match = attr.match(/C?[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)/);
+ if (match) {
+ coordList.push(first ? startPt : coordList.lastElement());
+ coordList.push({ X: +match[1], Y: +match[2] });
+ coordList.push({ X: +match[3], Y: +match[4] });
+ coordList.push({ X: +match[5], Y: +match[6] });
+ attr = attr.slice(match[0].length).trim();
+ } else {
+ attr = attr.slice(1).trim();
+ alert('error' + attr);
+ }
+ break;
+ }
+ case 'A': {
+ const match = attr.match(/A?[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)/);
+ if (match) {
+ console.log('SKIPPING arc - not implemented');
+ // coordList.push(first ? startPt : coordList.lastElement());
+ // coordList.push({ X: +match[1], Y: +match[2] });
+ // coordList.push({ X: +match[3], Y: +match[4] });
+ // coordList.push({ X: +match[5], Y: +match[6] });
+ attr = attr.slice(match[0].length).trim();
+ } else {
+ attr = attr.slice(1).trim();
+ alert('error' + attr);
+ }
+ break;
+ }
+ case 'L': {
+ const match = attr.match(/L?[, ]?(-?\d*\.?\d*)[, ]?(-?\d*\.?\d*)/);
+ if (match) {
+ coordList.push(first ? startPt : coordList.lastElement());
+ coordList.push(coordList.lastElement());
+ coordList.push({ X: +match[1], Y: +match[2] });
+ coordList.push({ X: +match[1], Y: +match[2] });
+ attr = attr.slice(match[0].length).trim();
+ } else {
+ attr = attr.slice(1).trim();
+ alert('error' + attr);
+ }
+ break;
+ }
+ case 'H': {
+ const match = attr.match(/H?[, ]?(-?\d*\.?\d*)/);
+ if (match) {
+ coordList.push(first ? startPt : coordList.lastElement());
+ coordList.push(coordList.lastElement());
+ coordList.push({ X: +match[1], Y: coordList.lastElement().Y });
+ coordList.push({ X: +match[1], Y: coordList.lastElement().Y });
+ attr = attr.slice(match[0].length).trim();
+ } else {
+ attr = attr.slice(1).trim();
+ alert('error' + attr);
+ }
+ break;
+ }
+ case 'V': {
+ const match = attr.match(/V?[, ]?(-?\d*\.?\d*)/);
+ if (match) {
+ coordList.push(first ? startPt : coordList.lastElement());
+ coordList.push(coordList.lastElement());
+ coordList.push({ X: coordList.lastElement().X, Y: +match[1] });
+ coordList.push({ X: coordList.lastElement().X, Y: +match[1] });
+ attr = attr.slice(match[0].length).trim();
+ } else {
+ attr = attr.slice(1).trim();
+ alert('error' + attr);
+ }
+ break;
+ }
+ case 'Z': {
+ coordList.push(first ? startPt : coordList.lastElement());
+ coordList.push(first ? startPt : coordList.lastElement());
+ coordList.push(startPt);
+ coordList.push(startPt);
+ attr = attr.slice(1).trim();
+ break;
+ }
+ default:
+ attr = attr.slice(1).trim();
+ debugger;
}
- });
- const hasZ = attributes.d.match(/Z/);
- if (hasZ || attributes.fill) {
- coordList.push(lastPt);
- coordList.push(startPt);
- coordList.push(startPt);
- } else {
- coordList.pop();
+ first = false;
}
return coordList;
}
@@ -733,10 +929,10 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] {
const coords: RegExpMatchArray[] = Array.from(attributes.points.matchAll(/(-?\d+\.?\d*),(-?\d+\.?\d*)/g));
let list: Point[] = [];
coords.forEach(coord => {
- list.push({ X: parseInt(coord[1]), Y: parseInt(coord[2]) });
- list.push({ X: parseInt(coord[1]), Y: parseInt(coord[2]) });
- list.push({ X: parseInt(coord[1]), Y: parseInt(coord[2]) });
- list.push({ X: parseInt(coord[1]), Y: parseInt(coord[2]) });
+ list.push({ X: +coord[1], Y: +coord[2] });
+ list.push({ X: +coord[1], Y: +coord[2] });
+ list.push({ X: +coord[1], Y: +coord[2] });
+ list.push({ X: +coord[1], Y: +coord[2] });
});
const firstPts = list.splice(0, 2);
list = list.concat(firstPts);