This commit is contained in:
Akko
2025-08-04 18:57:35 +02:00
parent 8cf6e78a79
commit 9495868c2e
5030 changed files with 518594 additions and 17609 deletions

View File

@@ -0,0 +1,2 @@
import type { GraphicsContext } from '../GraphicsContext';
export declare function SVGParser(svg: string | SVGElement | SVGSVGElement, graphicsContext?: GraphicsContext): GraphicsContext;

View File

@@ -0,0 +1,203 @@
'use strict';
var Color = require('../../../../color/Color.js');
var GraphicsPath = require('../path/GraphicsPath.js');
"use strict";
function SVGParser(svg, graphicsContext) {
if (typeof svg === "string") {
const div = document.createElement("div");
div.innerHTML = svg.trim();
svg = div.querySelector("svg");
}
const session = {
context: graphicsContext,
path: new GraphicsPath.GraphicsPath()
};
renderChildren(svg, session, null, null);
return graphicsContext;
}
function renderChildren(svg, session, fillStyle, strokeStyle) {
const children = svg.children;
const { fillStyle: f1, strokeStyle: s1 } = parseStyle(svg);
if (f1 && fillStyle) {
fillStyle = { ...fillStyle, ...f1 };
} else if (f1) {
fillStyle = f1;
}
if (s1 && strokeStyle) {
strokeStyle = { ...strokeStyle, ...s1 };
} else if (s1) {
strokeStyle = s1;
}
session.context.fillStyle = fillStyle;
session.context.strokeStyle = strokeStyle;
let x;
let y;
let x1;
let y1;
let x2;
let y2;
let cx;
let cy;
let r;
let rx;
let ry;
let points;
let pointsString;
let d;
let graphicsPath;
let width;
let height;
switch (svg.nodeName.toLowerCase()) {
case "path":
d = svg.getAttribute("d");
graphicsPath = new GraphicsPath.GraphicsPath(d);
session.context.path(graphicsPath);
if (fillStyle)
session.context.fill();
if (strokeStyle)
session.context.stroke();
break;
case "circle":
cx = parseFloatAttribute(svg, "cx", 0);
cy = parseFloatAttribute(svg, "cy", 0);
r = parseFloatAttribute(svg, "r", 0);
session.context.ellipse(cx, cy, r, r);
if (fillStyle)
session.context.fill();
if (strokeStyle)
session.context.stroke();
break;
case "rect":
x = parseFloatAttribute(svg, "x", 0);
y = parseFloatAttribute(svg, "y", 0);
width = parseFloatAttribute(svg, "width", 0);
height = parseFloatAttribute(svg, "height", 0);
rx = parseFloatAttribute(svg, "rx", 0);
ry = parseFloatAttribute(svg, "ry", 0);
if (rx || ry) {
session.context.roundRect(x, y, width, height, rx || ry);
} else {
session.context.rect(x, y, width, height);
}
if (fillStyle)
session.context.fill();
if (strokeStyle)
session.context.stroke();
break;
case "ellipse":
cx = parseFloatAttribute(svg, "cx", 0);
cy = parseFloatAttribute(svg, "cy", 0);
rx = parseFloatAttribute(svg, "rx", 0);
ry = parseFloatAttribute(svg, "ry", 0);
session.context.beginPath();
session.context.ellipse(cx, cy, rx, ry);
if (fillStyle)
session.context.fill();
if (strokeStyle)
session.context.stroke();
break;
case "line":
x1 = parseFloatAttribute(svg, "x1", 0);
y1 = parseFloatAttribute(svg, "y1", 0);
x2 = parseFloatAttribute(svg, "x2", 0);
y2 = parseFloatAttribute(svg, "y2", 0);
session.context.beginPath();
session.context.moveTo(x1, y1);
session.context.lineTo(x2, y2);
if (strokeStyle)
session.context.stroke();
break;
case "polygon":
pointsString = svg.getAttribute("points");
points = pointsString.match(/\d+/g).map((n) => parseInt(n, 10));
session.context.poly(points, true);
if (fillStyle)
session.context.fill();
if (strokeStyle)
session.context.stroke();
break;
case "polyline":
pointsString = svg.getAttribute("points");
points = pointsString.match(/\d+/g).map((n) => parseInt(n, 10));
session.context.poly(points, false);
if (strokeStyle)
session.context.stroke();
break;
case "g":
case "svg":
break;
default: {
console.info(`[SVG parser] <${svg.nodeName}> elements unsupported`);
break;
}
}
for (let i = 0; i < children.length; i++) {
renderChildren(children[i], session, fillStyle, strokeStyle);
}
}
function parseFloatAttribute(svg, id, defaultValue) {
const value = svg.getAttribute(id);
return value ? Number(value) : defaultValue;
}
function parseStyle(svg) {
const style = svg.getAttribute("style");
const strokeStyle = {};
const fillStyle = {};
let useFill = false;
let useStroke = false;
if (style) {
const styleParts = style.split(";");
for (let i = 0; i < styleParts.length; i++) {
const stylePart = styleParts[i];
const [key, value] = stylePart.split(":");
switch (key) {
case "stroke":
if (value !== "none") {
strokeStyle.color = Color.Color.shared.setValue(value).toNumber();
useStroke = true;
}
break;
case "stroke-width":
strokeStyle.width = Number(value);
break;
case "fill":
if (value !== "none") {
useFill = true;
fillStyle.color = Color.Color.shared.setValue(value).toNumber();
}
break;
case "fill-opacity":
fillStyle.alpha = Number(value);
break;
case "stroke-opacity":
strokeStyle.alpha = Number(value);
break;
case "opacity":
fillStyle.alpha = Number(value);
strokeStyle.alpha = Number(value);
break;
}
}
} else {
const stroke = svg.getAttribute("stroke");
if (stroke && stroke !== "none") {
useStroke = true;
strokeStyle.color = Color.Color.shared.setValue(stroke).toNumber();
strokeStyle.width = parseFloatAttribute(svg, "stroke-width", 1);
}
const fill = svg.getAttribute("fill");
if (fill && fill !== "none") {
useFill = true;
fillStyle.color = Color.Color.shared.setValue(fill).toNumber();
}
}
return {
strokeStyle: useStroke ? strokeStyle : null,
fillStyle: useFill ? fillStyle : null
};
}
exports.SVGParser = SVGParser;
//# sourceMappingURL=SVGParser.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,201 @@
import { Color } from '../../../../color/Color.mjs';
import { GraphicsPath } from '../path/GraphicsPath.mjs';
"use strict";
function SVGParser(svg, graphicsContext) {
if (typeof svg === "string") {
const div = document.createElement("div");
div.innerHTML = svg.trim();
svg = div.querySelector("svg");
}
const session = {
context: graphicsContext,
path: new GraphicsPath()
};
renderChildren(svg, session, null, null);
return graphicsContext;
}
function renderChildren(svg, session, fillStyle, strokeStyle) {
const children = svg.children;
const { fillStyle: f1, strokeStyle: s1 } = parseStyle(svg);
if (f1 && fillStyle) {
fillStyle = { ...fillStyle, ...f1 };
} else if (f1) {
fillStyle = f1;
}
if (s1 && strokeStyle) {
strokeStyle = { ...strokeStyle, ...s1 };
} else if (s1) {
strokeStyle = s1;
}
session.context.fillStyle = fillStyle;
session.context.strokeStyle = strokeStyle;
let x;
let y;
let x1;
let y1;
let x2;
let y2;
let cx;
let cy;
let r;
let rx;
let ry;
let points;
let pointsString;
let d;
let graphicsPath;
let width;
let height;
switch (svg.nodeName.toLowerCase()) {
case "path":
d = svg.getAttribute("d");
graphicsPath = new GraphicsPath(d);
session.context.path(graphicsPath);
if (fillStyle)
session.context.fill();
if (strokeStyle)
session.context.stroke();
break;
case "circle":
cx = parseFloatAttribute(svg, "cx", 0);
cy = parseFloatAttribute(svg, "cy", 0);
r = parseFloatAttribute(svg, "r", 0);
session.context.ellipse(cx, cy, r, r);
if (fillStyle)
session.context.fill();
if (strokeStyle)
session.context.stroke();
break;
case "rect":
x = parseFloatAttribute(svg, "x", 0);
y = parseFloatAttribute(svg, "y", 0);
width = parseFloatAttribute(svg, "width", 0);
height = parseFloatAttribute(svg, "height", 0);
rx = parseFloatAttribute(svg, "rx", 0);
ry = parseFloatAttribute(svg, "ry", 0);
if (rx || ry) {
session.context.roundRect(x, y, width, height, rx || ry);
} else {
session.context.rect(x, y, width, height);
}
if (fillStyle)
session.context.fill();
if (strokeStyle)
session.context.stroke();
break;
case "ellipse":
cx = parseFloatAttribute(svg, "cx", 0);
cy = parseFloatAttribute(svg, "cy", 0);
rx = parseFloatAttribute(svg, "rx", 0);
ry = parseFloatAttribute(svg, "ry", 0);
session.context.beginPath();
session.context.ellipse(cx, cy, rx, ry);
if (fillStyle)
session.context.fill();
if (strokeStyle)
session.context.stroke();
break;
case "line":
x1 = parseFloatAttribute(svg, "x1", 0);
y1 = parseFloatAttribute(svg, "y1", 0);
x2 = parseFloatAttribute(svg, "x2", 0);
y2 = parseFloatAttribute(svg, "y2", 0);
session.context.beginPath();
session.context.moveTo(x1, y1);
session.context.lineTo(x2, y2);
if (strokeStyle)
session.context.stroke();
break;
case "polygon":
pointsString = svg.getAttribute("points");
points = pointsString.match(/\d+/g).map((n) => parseInt(n, 10));
session.context.poly(points, true);
if (fillStyle)
session.context.fill();
if (strokeStyle)
session.context.stroke();
break;
case "polyline":
pointsString = svg.getAttribute("points");
points = pointsString.match(/\d+/g).map((n) => parseInt(n, 10));
session.context.poly(points, false);
if (strokeStyle)
session.context.stroke();
break;
case "g":
case "svg":
break;
default: {
console.info(`[SVG parser] <${svg.nodeName}> elements unsupported`);
break;
}
}
for (let i = 0; i < children.length; i++) {
renderChildren(children[i], session, fillStyle, strokeStyle);
}
}
function parseFloatAttribute(svg, id, defaultValue) {
const value = svg.getAttribute(id);
return value ? Number(value) : defaultValue;
}
function parseStyle(svg) {
const style = svg.getAttribute("style");
const strokeStyle = {};
const fillStyle = {};
let useFill = false;
let useStroke = false;
if (style) {
const styleParts = style.split(";");
for (let i = 0; i < styleParts.length; i++) {
const stylePart = styleParts[i];
const [key, value] = stylePart.split(":");
switch (key) {
case "stroke":
if (value !== "none") {
strokeStyle.color = Color.shared.setValue(value).toNumber();
useStroke = true;
}
break;
case "stroke-width":
strokeStyle.width = Number(value);
break;
case "fill":
if (value !== "none") {
useFill = true;
fillStyle.color = Color.shared.setValue(value).toNumber();
}
break;
case "fill-opacity":
fillStyle.alpha = Number(value);
break;
case "stroke-opacity":
strokeStyle.alpha = Number(value);
break;
case "opacity":
fillStyle.alpha = Number(value);
strokeStyle.alpha = Number(value);
break;
}
}
} else {
const stroke = svg.getAttribute("stroke");
if (stroke && stroke !== "none") {
useStroke = true;
strokeStyle.color = Color.shared.setValue(stroke).toNumber();
strokeStyle.width = parseFloatAttribute(svg, "stroke-width", 1);
}
const fill = svg.getAttribute("fill");
if (fill && fill !== "none") {
useFill = true;
fillStyle.color = Color.shared.setValue(fill).toNumber();
}
}
return {
strokeStyle: useStroke ? strokeStyle : null,
fillStyle: useFill ? fillStyle : null
};
}
export { SVGParser };
//# sourceMappingURL=SVGParser.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
import type { GraphicsPath } from '../path/GraphicsPath';
export declare function SVGToGraphicsPath(svgPath: string, path: GraphicsPath): GraphicsPath;

View File

@@ -0,0 +1,189 @@
'use strict';
var parse = require('parse-svg-path');
var warn = require('../../../../utils/logging/warn.js');
"use strict";
function SVGToGraphicsPath(svgPath, path) {
const commands = parse(svgPath);
const subpaths = [];
let currentSubPath = null;
let lastX = 0;
let lastY = 0;
for (let i = 0; i < commands.length; i++) {
const command = commands[i];
const type = command[0];
const data = command;
switch (type) {
case "M":
lastX = data[1];
lastY = data[2];
path.moveTo(lastX, lastY);
break;
case "m":
lastX += data[1];
lastY += data[2];
path.moveTo(lastX, lastY);
break;
case "H":
lastX = data[1];
path.lineTo(lastX, lastY);
break;
case "h":
lastX += data[1];
path.lineTo(lastX, lastY);
break;
case "V":
lastY = data[1];
path.lineTo(lastX, lastY);
break;
case "v":
lastY += data[1];
path.lineTo(lastX, lastY);
break;
case "L":
lastX = data[1];
lastY = data[2];
path.lineTo(lastX, lastY);
break;
case "l":
lastX += data[1];
lastY += data[2];
path.lineTo(lastX, lastY);
break;
case "C":
lastX = data[5];
lastY = data[6];
path.bezierCurveTo(
data[1],
data[2],
data[3],
data[4],
lastX,
lastY
);
break;
case "c":
path.bezierCurveTo(
lastX + data[1],
lastY + data[2],
lastX + data[3],
lastY + data[4],
lastX + data[5],
lastY + data[6]
);
lastX += data[5];
lastY += data[6];
break;
case "S":
lastX = data[3];
lastY = data[4];
path.bezierCurveToShort(
data[1],
data[2],
lastX,
lastY
);
break;
case "s":
path.bezierCurveToShort(
lastX + data[1],
lastY + data[2],
lastX + data[3],
lastY + data[4]
);
lastX += data[3];
lastY += data[4];
break;
case "Q":
lastX = data[3];
lastY = data[4];
path.quadraticCurveTo(
data[1],
data[2],
lastX,
lastY
);
break;
case "q":
path.quadraticCurveTo(
lastX + data[1],
lastY + data[2],
lastX + data[3],
lastY + data[4]
);
lastX += data[3];
lastY += data[4];
break;
case "T":
lastX = data[1];
lastY = data[2];
path.quadraticCurveToShort(
lastX,
lastY
);
break;
case "t":
lastX += data[1];
lastY += data[2];
path.quadraticCurveToShort(
lastX,
lastY
);
break;
case "A":
lastX = data[6];
lastY = data[7];
path.arcToSvg(
data[1],
data[2],
data[3],
data[4],
data[5],
lastX,
lastY
);
break;
case "a":
lastX += data[6];
lastY += data[7];
path.arcToSvg(
data[1],
data[2],
data[3],
data[4],
data[5],
lastX,
lastY
);
break;
case "Z":
case "z":
path.closePath();
if (subpaths.length > 0) {
currentSubPath = subpaths.pop();
if (currentSubPath) {
lastX = currentSubPath.startX;
lastY = currentSubPath.startY;
} else {
lastX = 0;
lastY = 0;
}
}
currentSubPath = null;
break;
default:
warn.warn(`Unknown SVG path command: ${type}`);
}
if (type !== "Z" && type !== "z") {
if (currentSubPath === null) {
currentSubPath = { startX: lastX, startY: lastY };
subpaths.push(currentSubPath);
}
}
}
return path;
}
exports.SVGToGraphicsPath = SVGToGraphicsPath;
//# sourceMappingURL=SVGToGraphicsPath.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,187 @@
import parse from 'parse-svg-path';
import { warn } from '../../../../utils/logging/warn.mjs';
"use strict";
function SVGToGraphicsPath(svgPath, path) {
const commands = parse(svgPath);
const subpaths = [];
let currentSubPath = null;
let lastX = 0;
let lastY = 0;
for (let i = 0; i < commands.length; i++) {
const command = commands[i];
const type = command[0];
const data = command;
switch (type) {
case "M":
lastX = data[1];
lastY = data[2];
path.moveTo(lastX, lastY);
break;
case "m":
lastX += data[1];
lastY += data[2];
path.moveTo(lastX, lastY);
break;
case "H":
lastX = data[1];
path.lineTo(lastX, lastY);
break;
case "h":
lastX += data[1];
path.lineTo(lastX, lastY);
break;
case "V":
lastY = data[1];
path.lineTo(lastX, lastY);
break;
case "v":
lastY += data[1];
path.lineTo(lastX, lastY);
break;
case "L":
lastX = data[1];
lastY = data[2];
path.lineTo(lastX, lastY);
break;
case "l":
lastX += data[1];
lastY += data[2];
path.lineTo(lastX, lastY);
break;
case "C":
lastX = data[5];
lastY = data[6];
path.bezierCurveTo(
data[1],
data[2],
data[3],
data[4],
lastX,
lastY
);
break;
case "c":
path.bezierCurveTo(
lastX + data[1],
lastY + data[2],
lastX + data[3],
lastY + data[4],
lastX + data[5],
lastY + data[6]
);
lastX += data[5];
lastY += data[6];
break;
case "S":
lastX = data[3];
lastY = data[4];
path.bezierCurveToShort(
data[1],
data[2],
lastX,
lastY
);
break;
case "s":
path.bezierCurveToShort(
lastX + data[1],
lastY + data[2],
lastX + data[3],
lastY + data[4]
);
lastX += data[3];
lastY += data[4];
break;
case "Q":
lastX = data[3];
lastY = data[4];
path.quadraticCurveTo(
data[1],
data[2],
lastX,
lastY
);
break;
case "q":
path.quadraticCurveTo(
lastX + data[1],
lastY + data[2],
lastX + data[3],
lastY + data[4]
);
lastX += data[3];
lastY += data[4];
break;
case "T":
lastX = data[1];
lastY = data[2];
path.quadraticCurveToShort(
lastX,
lastY
);
break;
case "t":
lastX += data[1];
lastY += data[2];
path.quadraticCurveToShort(
lastX,
lastY
);
break;
case "A":
lastX = data[6];
lastY = data[7];
path.arcToSvg(
data[1],
data[2],
data[3],
data[4],
data[5],
lastX,
lastY
);
break;
case "a":
lastX += data[6];
lastY += data[7];
path.arcToSvg(
data[1],
data[2],
data[3],
data[4],
data[5],
lastX,
lastY
);
break;
case "Z":
case "z":
path.closePath();
if (subpaths.length > 0) {
currentSubPath = subpaths.pop();
if (currentSubPath) {
lastX = currentSubPath.startX;
lastY = currentSubPath.startY;
} else {
lastX = 0;
lastY = 0;
}
}
currentSubPath = null;
break;
default:
warn(`Unknown SVG path command: ${type}`);
}
if (type !== "Z" && type !== "z") {
if (currentSubPath === null) {
currentSubPath = { startX: lastX, startY: lastY };
subpaths.push(currentSubPath);
}
}
}
return path;
}
export { SVGToGraphicsPath };
//# sourceMappingURL=SVGToGraphicsPath.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
declare module 'parse-svg-path'
{
export type Command = [string, ...number[]];
export default function parse(path: string): Command[];
}