diff options
| author | bobzel <zzzman@gmail.com> | 2025-03-04 00:52:53 -0500 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2025-03-04 00:52:53 -0500 |
| commit | 215ad40efa2e343e290d18bffbc55884829f1a0d (patch) | |
| tree | 2e4e3310aad1ea5b39a874ecbc98efb1312bd21b /src/client/util/bezierFit.ts | |
| parent | 0e7ae057264445ece675e4b5d2380893ea124112 (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.ts | 302 |
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); |
