diff --git a/docs/.vitepress/api/gh-icon/[...data].get.ts b/docs/.vitepress/api/gh-icon/[...data].get.ts
index bdd4af66d..725db4848 100644
--- a/docs/.vitepress/api/gh-icon/[...data].get.ts
+++ b/docs/.vitepress/api/gh-icon/[...data].get.ts
@@ -1,7 +1,11 @@
-import { eventHandler, getQuery, setResponseHeader, defaultContentType } from 'h3'
-import { renderToString } from 'react-dom/server'
+import { eventHandler, setResponseHeader, defaultContentType } from 'h3'
+import { renderToString, renderToStaticMarkup } from 'react-dom/server'
import { createElement } from 'react'
import SvgPreview from '../../lib/SvgPreview/index.tsx';
+import * as iconNodes from '../../data/iconNodes'
+import { camelCase } from 'lodash-es'
+import createLucideIcon from 'lucide-react/src/createLucideIcon'
+import Backdrop from '../../lib/SvgPreview/Backdrop.tsx';
export default eventHandler((event) => {
const { params } = event.context
@@ -11,8 +15,21 @@ export default eventHandler((event) => {
const src = Buffer.from(data, 'base64').toString('utf8');
+ const children = []
+ const camelCaseName = camelCase(name)
+
+ if (camelCaseName in iconNodes) {
+ const iconNode = iconNodes[camelCaseName]
+
+ const LucideIcon = createLucideIcon(name, iconNode)
+ const svg = renderToStaticMarkup(createElement(LucideIcon))
+ const backdropString = svg.replace(/
diff --git a/docs/.vitepress/lib/SvgPreview/types.ts b/docs/.vitepress/lib/SvgPreview/types.ts
index 66af74a94..15023fd0e 100644
--- a/docs/.vitepress/lib/SvgPreview/types.ts
+++ b/docs/.vitepress/lib/SvgPreview/types.ts
@@ -8,6 +8,7 @@ export type Path = {
prev: Point;
next: Point;
isStart: boolean;
+ circle: { x: number; y: number; r: number };
c: ReturnType[number];
};
diff --git a/docs/.vitepress/lib/SvgPreview/utils.ts b/docs/.vitepress/lib/SvgPreview/utils.ts
index 03f767f84..7c9d79ecb 100644
--- a/docs/.vitepress/lib/SvgPreview/utils.ts
+++ b/docs/.vitepress/lib/SvgPreview/utils.ts
@@ -12,33 +12,66 @@ export function assert(value: unknown): asserts value {
}
}
-const extractPaths = (node: INode): { d: string; name: typeof node.name }[] => {
- if (/(rect|circle|ellipse|polygon|polyline|line|path)/.test(node.name)) {
- return [{ d: toPath(node), name: node.name }];
+const convertToPathNode = (node: INode): { d: string; name: typeof node.name } => {
+ if (node.name === 'path') {
+ return { d: node.attributes.d, name: node.name };
+ }
+ if (node.name === 'circle') {
+ const cx = parseFloat(node.attributes.cx);
+ const cy = parseFloat(node.attributes.cy);
+ const r = parseFloat(node.attributes.r);
+ return {
+ d: [
+ `M ${cx} ${cy - r}`,
+ `a ${r} ${r} 0 0 1 ${r} ${r}`,
+ `a ${r} ${r} 0 0 1 ${0 - r} ${r}`,
+ `a ${r} ${r} 0 0 1 ${0 - r} ${0 - r}`,
+ `a ${r} ${r} 0 0 1 ${r} ${0 - r}`,
+ ].join(''),
+ name: node.name,
+ };
+ }
+ return { d: toPath(node).replace(/z$/i, ''), name: node.name };
+};
+
+const extractNodes = (node: INode): INode[] => {
+ if (['rect', 'circle', 'ellipse', 'polygon', 'polyline', 'line', 'path'].includes(node.name)) {
+ return [node];
} else if (node.children && Array.isArray(node.children)) {
- return node.children.flatMap(extractPaths);
+ return node.children.flatMap(extractNodes);
}
return [];
};
+export const getNodes = (src: string) =>
+ extractNodes(parseSync(src.includes('`));
+
export const getCommands = (src: string) =>
- extractPaths(parseSync(src)).flatMap(({ d, name }, idx) =>
- new SVGPathData(d).toAbs().commands.map((c) => ({ ...c, id: idx, name }))
- );
+ getNodes(src)
+ .map(convertToPathNode)
+ .flatMap(({ d, name }, idx) =>
+ new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name }))
+ );
export const getPaths = (src: string) => {
const commands = getCommands(src.includes('`);
const paths: Path[] = [];
let prev: Point | undefined = undefined;
let start: Point | undefined = undefined;
- const addPath = (c: (typeof commands)[number], next: Point, d?: string) => {
+ const addPath = (
+ c: typeof commands[number],
+ next: Point,
+ d?: string,
+ circle?: Path['circle']
+ ) => {
assert(prev);
paths.push({
c,
- d: d || `M${prev.x} ${prev.y}L${next.x} ${next.y}`,
+ d: d || `M ${prev.x} ${prev.y} L ${next.x} ${next.y}`,
prev,
next,
+ circle,
isStart: start === prev,
});
prev = next;
@@ -77,7 +110,7 @@ export const getPaths = (src: string) => {
}
case SVGPathData.CURVE_TO: {
assert(prev);
- addPath(c, c, `M ${prev.x},${prev.y} ${encodeSVGPath(c)}`);
+ addPath(c, c, `M ${prev.x} ${prev.y} ${encodeSVGPath(c)}`);
break;
}
case SVGPathData.SMOOTH_CURVE_TO: {
@@ -86,16 +119,16 @@ export const getPaths = (src: string) => {
const reflectedCp1 = {
x:
previousCommand &&
- (previousCommand.type === SVGPathData.SMOOTH_CURVE_TO ||
- previousCommand.type === SVGPathData.CURVE_TO)
+ (previousCommand.type === SVGPathData.SMOOTH_CURVE_TO ||
+ previousCommand.type === SVGPathData.CURVE_TO)
? previousCommand.relative
? previousCommand.x2 - previousCommand.x
: previousCommand.x2 - prev.x
: 0,
y:
previousCommand &&
- (previousCommand.type === SVGPathData.SMOOTH_CURVE_TO ||
- previousCommand.type === SVGPathData.CURVE_TO)
+ (previousCommand.type === SVGPathData.SMOOTH_CURVE_TO ||
+ previousCommand.type === SVGPathData.CURVE_TO)
? previousCommand.relative
? previousCommand.y2 - previousCommand.y
: previousCommand.y2 - prev.y
@@ -104,7 +137,7 @@ export const getPaths = (src: string) => {
addPath(
c,
c,
- `M ${prev.x},${prev.y} ${encodeSVGPath({
+ `M ${prev.x} ${prev.y} ${encodeSVGPath({
type: SVGPathData.CURVE_TO,
relative: false,
x: c.x,
@@ -119,7 +152,7 @@ export const getPaths = (src: string) => {
}
case SVGPathData.QUAD_TO: {
assert(prev);
- addPath(c, c, `M ${prev.x},${prev.y} ${encodeSVGPath(c)}`);
+ addPath(c, c, `M ${prev.x} ${prev.y} ${encodeSVGPath(c)}`);
break;
}
case SVGPathData.SMOOTH_QUAD_TO: {
@@ -157,7 +190,7 @@ export const getPaths = (src: string) => {
addPath(
c,
c,
- `M ${prev.x},${prev.y} ${encodeSVGPath({
+ `M ${prev.x} ${prev.y} ${encodeSVGPath({
type: SVGPathData.QUAD_TO,
relative: false,
x: c.x,
@@ -170,10 +203,22 @@ export const getPaths = (src: string) => {
}
case SVGPathData.ARC: {
assert(prev);
+ const center = arcEllipseCenter(
+ prev.x,
+ prev.y,
+ c.rX,
+ c.rY,
+ c.xRot,
+ c.lArcFlag,
+ c.sweepFlag,
+ c.x,
+ c.y
+ );
addPath(
c,
c,
- `M ${prev.x},${prev.y} A ${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`
+ `M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`,
+ c.rX === c.rY ? { ...center, r: c.rX } : undefined
);
break;
}
@@ -184,3 +229,58 @@ export const getPaths = (src: string) => {
}
return paths;
};
+
+export const arcEllipseCenter = (
+ x1: number,
+ y1: number,
+ rx: number,
+ ry: number,
+ a: number,
+ fa: number,
+ fs: number,
+ x2: number,
+ y2: number
+) => {
+ const phi = (a * Math.PI) / 180;
+
+ const M = [
+ [Math.cos(phi), Math.sin(phi)],
+ [-Math.sin(phi), Math.cos(phi)],
+ ];
+ const V = [(x1 - x2) / 2, (y1 - y2) / 2];
+
+ const [x1p, y1p] = [M[0][0] * V[0] + M[0][1] * V[1], M[1][0] * V[0] + M[1][1] * V[1]];
+
+ rx = Math.abs(rx);
+ ry = Math.abs(ry);
+
+ const lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
+ if (lambda > 1) {
+ rx = Math.sqrt(lambda) * rx;
+ ry = Math.sqrt(lambda) * ry;
+ }
+
+ const sign = fa === fs ? -1 : 1;
+
+ const co =
+ sign *
+ Math.sqrt(
+ Math.max(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p, 0) /
+ (rx * rx * y1p * y1p + ry * ry * x1p * x1p)
+ );
+
+ const V2 = [(rx * y1p) / ry, (-ry * x1p) / rx];
+ const Cp = [V2[0] * co, V2[1] * co];
+
+ const M2 = [
+ [Math.cos(phi), -Math.sin(phi)],
+ [Math.sin(phi), Math.cos(phi)],
+ ];
+ const V3 = [(x1 + x2) / 2, (y1 + y2) / 2];
+ const C = [
+ M2[0][0] * Cp[0] + M2[0][1] * Cp[1] + V3[0],
+ M2[1][0] * Cp[0] + M2[1][1] * Cp[1] + V3[1],
+ ];
+
+ return { x: C[0], y: C[1] };
+};