@guildai/cli 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent/chat.js +0 -2
- package/dist/commands/agent/init.js +10 -8
- package/dist/commands/agent/list.js +46 -7
- package/dist/commands/agent/owners.js +6 -8
- package/dist/commands/agent/test.js +36 -6
- package/dist/commands/chat.js +3 -10
- package/dist/commands/session/create.js +1 -1
- package/dist/commands/session/tasks.js +8 -2
- package/dist/commands/trigger/create.js +41 -17
- package/dist/commands/trigger/update.js +33 -10
- package/dist/commands/workspace/agent/add.js +3 -3
- package/dist/commands/workspace/agent/remove.js +1 -1
- package/dist/lib/agent-helpers.d.ts +15 -0
- package/dist/lib/agent-helpers.js +69 -0
- package/dist/lib/api-client.js +12 -5
- package/dist/lib/api-types.d.ts +56 -0
- package/dist/lib/auth.d.ts +0 -4
- package/dist/lib/auth.js +0 -11
- package/dist/lib/errors.d.ts +5 -4
- package/dist/lib/errors.js +18 -11
- package/dist/lib/output.d.ts +7 -2
- package/dist/lib/output.js +60 -4
- package/dist/lib/polling.d.ts +0 -24
- package/dist/lib/polling.js +0 -26
- package/dist/mcp/tools.js +2 -2
- package/docs/skills/agent-dev.md +301 -1006
- package/package.json +2 -2
- package/dist/lib/lottie-renderer.d.ts +0 -61
- package/dist/lib/lottie-renderer.js +0 -238
- package/dist/lib/svg-renderer.d.ts +0 -110
- package/dist/lib/svg-renderer.js +0 -858
package/dist/lib/svg-renderer.js
DELETED
|
@@ -1,858 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 Guild.ai
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
/**
|
|
4
|
-
* SVG to Braille Canvas Renderer
|
|
5
|
-
*
|
|
6
|
-
* Renders SVG paths to terminal using braille characters.
|
|
7
|
-
* Each braille character represents a 2x4 pixel grid.
|
|
8
|
-
*
|
|
9
|
-
* Braille dot positions:
|
|
10
|
-
* ┌───┬───┐
|
|
11
|
-
* │ 1 │ 4 │
|
|
12
|
-
* │ 2 │ 5 │
|
|
13
|
-
* │ 3 │ 6 │
|
|
14
|
-
* │ 7 │ 8 │
|
|
15
|
-
* └───┴───┘
|
|
16
|
-
*/
|
|
17
|
-
import { SVGPathData } from 'svg-pathdata';
|
|
18
|
-
import * as fs from 'fs';
|
|
19
|
-
import * as path from 'path';
|
|
20
|
-
import { fileURLToPath } from 'url';
|
|
21
|
-
// ESM equivalent of __dirname
|
|
22
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
-
const __dirname = path.dirname(__filename);
|
|
24
|
-
// Braille Unicode block starts at U+2800
|
|
25
|
-
const BRAILLE_BASE = 0x2800;
|
|
26
|
-
// Dot bit positions in the braille character
|
|
27
|
-
// Left column: bits 0,1,2,6 (positions 1,2,3,7)
|
|
28
|
-
// Right column: bits 3,4,5,7 (positions 4,5,6,8)
|
|
29
|
-
const DOT_BITS = [
|
|
30
|
-
[0x01, 0x08], // Row 0: dots 1,4
|
|
31
|
-
[0x02, 0x10], // Row 1: dots 2,5
|
|
32
|
-
[0x04, 0x20], // Row 2: dots 3,6
|
|
33
|
-
[0x40, 0x80], // Row 3: dots 7,8
|
|
34
|
-
];
|
|
35
|
-
/**
|
|
36
|
-
* Create an identity transform matrix
|
|
37
|
-
*/
|
|
38
|
-
function identityMatrix() {
|
|
39
|
-
return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Multiply two transform matrices
|
|
43
|
-
*/
|
|
44
|
-
function multiplyMatrices(m1, m2) {
|
|
45
|
-
return {
|
|
46
|
-
a: m1.a * m2.a + m1.c * m2.b,
|
|
47
|
-
b: m1.b * m2.a + m1.d * m2.b,
|
|
48
|
-
c: m1.a * m2.c + m1.c * m2.d,
|
|
49
|
-
d: m1.b * m2.c + m1.d * m2.d,
|
|
50
|
-
e: m1.a * m2.e + m1.c * m2.f + m1.e,
|
|
51
|
-
f: m1.b * m2.e + m1.d * m2.f + m1.f,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Apply transform matrix to a point
|
|
56
|
-
*/
|
|
57
|
-
export function transformPoint(x, y, m) {
|
|
58
|
-
return [m.a * x + m.c * y + m.e, m.b * x + m.d * y + m.f];
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Parse SVG transform string into a matrix
|
|
62
|
-
* Handles: translate, scale, rotate, matrix
|
|
63
|
-
*/
|
|
64
|
-
function parseTransform(transform) {
|
|
65
|
-
let result = identityMatrix();
|
|
66
|
-
// Match each transform function
|
|
67
|
-
const regex = /(translate|scale|rotate|matrix)\s*\(([^)]+)\)/gi;
|
|
68
|
-
let match;
|
|
69
|
-
while ((match = regex.exec(transform)) !== null) {
|
|
70
|
-
const type = match[1].toLowerCase();
|
|
71
|
-
const args = match[2]
|
|
72
|
-
.split(/[\s,]+/)
|
|
73
|
-
.filter((s) => s.length > 0)
|
|
74
|
-
.map(Number);
|
|
75
|
-
let m;
|
|
76
|
-
switch (type) {
|
|
77
|
-
case 'translate':
|
|
78
|
-
m = { a: 1, b: 0, c: 0, d: 1, e: args[0] || 0, f: args[1] || 0 };
|
|
79
|
-
break;
|
|
80
|
-
case 'scale':
|
|
81
|
-
m = { a: args[0] || 1, b: 0, c: 0, d: args[1] ?? args[0] ?? 1, e: 0, f: 0 };
|
|
82
|
-
break;
|
|
83
|
-
case 'rotate': {
|
|
84
|
-
const angle = ((args[0] || 0) * Math.PI) / 180;
|
|
85
|
-
const cos = Math.cos(angle);
|
|
86
|
-
const sin = Math.sin(angle);
|
|
87
|
-
m = { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 };
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
case 'matrix':
|
|
91
|
-
m = {
|
|
92
|
-
a: args[0] || 1,
|
|
93
|
-
b: args[1] || 0,
|
|
94
|
-
c: args[2] || 0,
|
|
95
|
-
d: args[3] || 1,
|
|
96
|
-
e: args[4] || 0,
|
|
97
|
-
f: args[5] || 0,
|
|
98
|
-
};
|
|
99
|
-
break;
|
|
100
|
-
default:
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
result = multiplyMatrices(result, m);
|
|
104
|
-
}
|
|
105
|
-
return result;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Create an empty braille canvas
|
|
109
|
-
*/
|
|
110
|
-
export function createCanvas(widthChars, heightChars) {
|
|
111
|
-
const pixelWidth = widthChars * 2;
|
|
112
|
-
const pixelHeight = heightChars * 4;
|
|
113
|
-
const pixels = [];
|
|
114
|
-
for (let y = 0; y < pixelHeight; y++) {
|
|
115
|
-
pixels.push(new Array(pixelWidth).fill(false));
|
|
116
|
-
}
|
|
117
|
-
return { width: widthChars, height: heightChars, pixels };
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Set a pixel in the canvas
|
|
121
|
-
*/
|
|
122
|
-
export function setPixel(canvas, x, y, value = true) {
|
|
123
|
-
const px = Math.floor(x);
|
|
124
|
-
const py = Math.floor(y);
|
|
125
|
-
if (px >= 0 && px < canvas.width * 2 && py >= 0 && py < canvas.height * 4) {
|
|
126
|
-
canvas.pixels[py][px] = value;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Draw a line using Bresenham's algorithm
|
|
131
|
-
*/
|
|
132
|
-
export function drawLine(canvas, x0, y0, x1, y1) {
|
|
133
|
-
x0 = Math.round(x0);
|
|
134
|
-
y0 = Math.round(y0);
|
|
135
|
-
x1 = Math.round(x1);
|
|
136
|
-
y1 = Math.round(y1);
|
|
137
|
-
const dx = Math.abs(x1 - x0);
|
|
138
|
-
const dy = Math.abs(y1 - y0);
|
|
139
|
-
const sx = x0 < x1 ? 1 : -1;
|
|
140
|
-
const sy = y0 < y1 ? 1 : -1;
|
|
141
|
-
let err = dx - dy;
|
|
142
|
-
let x = x0;
|
|
143
|
-
let y = y0;
|
|
144
|
-
while (true) {
|
|
145
|
-
setPixel(canvas, x, y);
|
|
146
|
-
if (x === x1 && y === y1)
|
|
147
|
-
break;
|
|
148
|
-
const e2 = 2 * err;
|
|
149
|
-
if (e2 > -dy) {
|
|
150
|
-
err -= dy;
|
|
151
|
-
x += sx;
|
|
152
|
-
}
|
|
153
|
-
if (e2 < dx) {
|
|
154
|
-
err += dx;
|
|
155
|
-
y += sy;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Draw a cubic bezier curve
|
|
161
|
-
*/
|
|
162
|
-
export function drawCubicBezier(canvas, x0, y0, x1, y1, x2, y2, x3, y3, steps = 20) {
|
|
163
|
-
let lastX = x0;
|
|
164
|
-
let lastY = y0;
|
|
165
|
-
for (let i = 1; i <= steps; i++) {
|
|
166
|
-
const t = i / steps;
|
|
167
|
-
const mt = 1 - t;
|
|
168
|
-
const mt2 = mt * mt;
|
|
169
|
-
const mt3 = mt2 * mt;
|
|
170
|
-
const t2 = t * t;
|
|
171
|
-
const t3 = t2 * t;
|
|
172
|
-
const x = mt3 * x0 + 3 * mt2 * t * x1 + 3 * mt * t2 * x2 + t3 * x3;
|
|
173
|
-
const y = mt3 * y0 + 3 * mt2 * t * y1 + 3 * mt * t2 * y2 + t3 * y3;
|
|
174
|
-
drawLine(canvas, lastX, lastY, x, y);
|
|
175
|
-
lastX = x;
|
|
176
|
-
lastY = y;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Draw a quadratic bezier curve
|
|
181
|
-
*/
|
|
182
|
-
export function drawQuadraticBezier(canvas, x0, y0, x1, y1, x2, y2, steps = 20) {
|
|
183
|
-
let lastX = x0;
|
|
184
|
-
let lastY = y0;
|
|
185
|
-
for (let i = 1; i <= steps; i++) {
|
|
186
|
-
const t = i / steps;
|
|
187
|
-
const mt = 1 - t;
|
|
188
|
-
const mt2 = mt * mt;
|
|
189
|
-
const t2 = t * t;
|
|
190
|
-
const x = mt2 * x0 + 2 * mt * t * x1 + t2 * x2;
|
|
191
|
-
const y = mt2 * y0 + 2 * mt * t * y1 + t2 * y2;
|
|
192
|
-
drawLine(canvas, lastX, lastY, x, y);
|
|
193
|
-
lastX = x;
|
|
194
|
-
lastY = y;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Fill a closed path using scanline algorithm
|
|
199
|
-
*/
|
|
200
|
-
export function fillPath(canvas, points, density = 1.0) {
|
|
201
|
-
if (points.length < 3)
|
|
202
|
-
return;
|
|
203
|
-
// Find bounding box
|
|
204
|
-
let minY = Infinity;
|
|
205
|
-
let maxY = -Infinity;
|
|
206
|
-
for (const [, y] of points) {
|
|
207
|
-
minY = Math.min(minY, y);
|
|
208
|
-
maxY = Math.max(maxY, y);
|
|
209
|
-
}
|
|
210
|
-
minY = Math.max(0, Math.floor(minY));
|
|
211
|
-
maxY = Math.min(canvas.height * 4 - 1, Math.ceil(maxY));
|
|
212
|
-
// Scanline fill
|
|
213
|
-
for (let y = minY; y <= maxY; y++) {
|
|
214
|
-
const intersections = [];
|
|
215
|
-
// Find intersections with all edges
|
|
216
|
-
for (let i = 0; i < points.length; i++) {
|
|
217
|
-
const [x1, y1] = points[i];
|
|
218
|
-
const [x2, y2] = points[(i + 1) % points.length];
|
|
219
|
-
if ((y1 <= y && y2 > y) || (y2 <= y && y1 > y)) {
|
|
220
|
-
const x = x1 + ((y - y1) / (y2 - y1)) * (x2 - x1);
|
|
221
|
-
intersections.push(x);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
// Sort and fill between pairs
|
|
225
|
-
intersections.sort((a, b) => a - b);
|
|
226
|
-
for (let i = 0; i < intersections.length; i += 2) {
|
|
227
|
-
if (i + 1 < intersections.length) {
|
|
228
|
-
const startX = Math.max(0, Math.floor(intersections[i]));
|
|
229
|
-
const endX = Math.min(canvas.width * 2 - 1, Math.ceil(intersections[i + 1]));
|
|
230
|
-
// Apply density-based sparse sampling
|
|
231
|
-
if (density >= 1.0) {
|
|
232
|
-
// Full density - render every pixel
|
|
233
|
-
for (let x = startX; x <= endX; x++) {
|
|
234
|
-
setPixel(canvas, x, y);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
// Sparse sampling using ordered dithering (Bayer-style pattern)
|
|
239
|
-
// This creates a more uniform distribution than random sampling
|
|
240
|
-
// and preserves detail better than simple checkerboard
|
|
241
|
-
for (let x = startX; x <= endX; x++) {
|
|
242
|
-
// 2x2 Bayer matrix pattern for ordered dithering
|
|
243
|
-
// Values: 0.0, 0.5, 0.75, 0.25 in a 2x2 pattern
|
|
244
|
-
const bx = x % 2;
|
|
245
|
-
const by = y % 2;
|
|
246
|
-
let threshold;
|
|
247
|
-
if (bx === 0 && by === 0)
|
|
248
|
-
threshold = 0.0;
|
|
249
|
-
else if (bx === 1 && by === 0)
|
|
250
|
-
threshold = 0.5;
|
|
251
|
-
else if (bx === 0 && by === 1)
|
|
252
|
-
threshold = 0.75;
|
|
253
|
-
else
|
|
254
|
-
threshold = 0.25;
|
|
255
|
-
// Render pixel if density is higher than threshold
|
|
256
|
-
// density=1.0 renders all (all thresholds < 1.0)
|
|
257
|
-
// density=0.75 renders 75% (thresholds 0.0, 0.5, 0.25 < 0.75)
|
|
258
|
-
// density=0.5 renders 50% (thresholds 0.0, 0.25 < 0.5)
|
|
259
|
-
if (density > threshold) {
|
|
260
|
-
setPixel(canvas, x, y);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Extract clipPath rectangles from SVG defs
|
|
270
|
-
* Lottie mask animations become clipPaths with path geometry
|
|
271
|
-
* We extract their bounding rectangles for clipping
|
|
272
|
-
*/
|
|
273
|
-
function extractClipPaths(svgContent) {
|
|
274
|
-
const clipPaths = new Map();
|
|
275
|
-
// Find all clipPath definitions
|
|
276
|
-
const clipPathRegex = /<clipPath id="([^"]+)"[^>]*>([\s\S]*?)<\/clipPath>/g;
|
|
277
|
-
let match;
|
|
278
|
-
while ((match = clipPathRegex.exec(svgContent)) !== null) {
|
|
279
|
-
const id = match[1];
|
|
280
|
-
const content = match[2];
|
|
281
|
-
// Extract path data
|
|
282
|
-
const pathMatch = content.match(/d="([^"]+)"/);
|
|
283
|
-
if (pathMatch) {
|
|
284
|
-
const d = pathMatch[1];
|
|
285
|
-
// Extract all coordinates from the path data
|
|
286
|
-
// Match patterns like: 123.45 or -123.45
|
|
287
|
-
const coords = [];
|
|
288
|
-
const coordRegex = /-?\d+(?:\.\d+)?/g;
|
|
289
|
-
let coordMatch;
|
|
290
|
-
while ((coordMatch = coordRegex.exec(d)) !== null) {
|
|
291
|
-
coords.push(parseFloat(coordMatch[0]));
|
|
292
|
-
}
|
|
293
|
-
if (coords.length >= 4) {
|
|
294
|
-
// Get bounding box from all coordinates
|
|
295
|
-
const xCoords = coords.filter((_, i) => i % 2 === 0); // Even indices are X
|
|
296
|
-
const yCoords = coords.filter((_, i) => i % 2 === 1); // Odd indices are Y
|
|
297
|
-
clipPaths.set(id, {
|
|
298
|
-
minX: Math.min(...xCoords),
|
|
299
|
-
minY: Math.min(...yCoords),
|
|
300
|
-
maxX: Math.max(...xCoords),
|
|
301
|
-
maxY: Math.max(...yCoords),
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return clipPaths;
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Parse SVG hierarchically to match each path with its correct transform chain
|
|
310
|
-
*/
|
|
311
|
-
export function parseHierarchicalPaths(svgContent) {
|
|
312
|
-
const paths = [];
|
|
313
|
-
const transformStack = [identityMatrix()];
|
|
314
|
-
const visibilityStack = [true]; // Track visibility state
|
|
315
|
-
// Extract all clipPath rectangles
|
|
316
|
-
const clipPathRects = extractClipPaths(svgContent);
|
|
317
|
-
// Track current clipPath (stack for nested groups)
|
|
318
|
-
const clipPathStack = [undefined];
|
|
319
|
-
// Track if we're inside <defs> to skip those paths
|
|
320
|
-
let inDefs = false;
|
|
321
|
-
// Use regex to find all <defs>, </defs>, <g>, </g>, and <path> tags in order
|
|
322
|
-
const tagRegex = /<(\/?)(?:defs|g|path)([^>]*)>/g;
|
|
323
|
-
let match;
|
|
324
|
-
while ((match = tagRegex.exec(svgContent)) !== null) {
|
|
325
|
-
const isClosing = match[1] === '/';
|
|
326
|
-
const fullTag = match[0];
|
|
327
|
-
let tagName = 'unknown';
|
|
328
|
-
if (fullTag.startsWith('<defs'))
|
|
329
|
-
tagName = 'defs';
|
|
330
|
-
else if (fullTag.startsWith('</defs'))
|
|
331
|
-
tagName = 'defs';
|
|
332
|
-
else if (fullTag.startsWith('<g'))
|
|
333
|
-
tagName = 'g';
|
|
334
|
-
else if (fullTag.startsWith('</g'))
|
|
335
|
-
tagName = 'g';
|
|
336
|
-
else if (fullTag.startsWith('<path'))
|
|
337
|
-
tagName = 'path';
|
|
338
|
-
const attributes = match[2];
|
|
339
|
-
// Track <defs> sections
|
|
340
|
-
if (tagName === 'defs') {
|
|
341
|
-
inDefs = !isClosing;
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
// Skip everything inside <defs>
|
|
345
|
-
if (inDefs)
|
|
346
|
-
continue;
|
|
347
|
-
if (tagName === 'g' && !isClosing) {
|
|
348
|
-
// Opening <g> tag - check visibility and transform
|
|
349
|
-
const hasDisplayNone = attributes.includes('display: none');
|
|
350
|
-
// Check for opacity="0" which also hides elements
|
|
351
|
-
const opacityMatch = attributes.match(/opacity="([^"]+)"/);
|
|
352
|
-
const hasZeroOpacity = opacityMatch && parseFloat(opacityMatch[1]) === 0;
|
|
353
|
-
const isVisible = !hasDisplayNone && !hasZeroOpacity;
|
|
354
|
-
const currentVisibility = visibilityStack[visibilityStack.length - 1];
|
|
355
|
-
// Group is visible only if parent is visible AND this group isn't hidden
|
|
356
|
-
visibilityStack.push(currentVisibility && isVisible);
|
|
357
|
-
// Check for clip-path attribute
|
|
358
|
-
const clipPathMatch = attributes.match(/clip-path="url\(#([^)]+)\)"/);
|
|
359
|
-
let groupClipRect;
|
|
360
|
-
if (clipPathMatch) {
|
|
361
|
-
const clipPathId = clipPathMatch[1];
|
|
362
|
-
groupClipRect = clipPathRects.get(clipPathId);
|
|
363
|
-
}
|
|
364
|
-
// Inherit parent's clipRect if we don't have our own
|
|
365
|
-
if (!groupClipRect) {
|
|
366
|
-
groupClipRect = clipPathStack[clipPathStack.length - 1];
|
|
367
|
-
}
|
|
368
|
-
clipPathStack.push(groupClipRect);
|
|
369
|
-
// Extract transform and push onto stack
|
|
370
|
-
const transformMatch = attributes.match(/transform="([^"]+)"/);
|
|
371
|
-
if (transformMatch) {
|
|
372
|
-
const transform = parseTransform(transformMatch[1]);
|
|
373
|
-
const currentTransform = transformStack[transformStack.length - 1];
|
|
374
|
-
const newTransform = multiplyMatrices(currentTransform, transform);
|
|
375
|
-
transformStack.push(newTransform);
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
// No transform attribute - duplicate current transform
|
|
379
|
-
transformStack.push(transformStack[transformStack.length - 1]);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
else if (tagName === 'g' && isClosing) {
|
|
383
|
-
// Closing </g> tag - pop from stacks
|
|
384
|
-
if (transformStack.length > 1) {
|
|
385
|
-
transformStack.pop();
|
|
386
|
-
visibilityStack.pop();
|
|
387
|
-
clipPathStack.pop();
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
else if (tagName === 'path') {
|
|
391
|
-
// <path> tag - only record if currently visible
|
|
392
|
-
const currentlyVisible = visibilityStack[visibilityStack.length - 1];
|
|
393
|
-
if (currentlyVisible) {
|
|
394
|
-
const pathDataMatch = attributes.match(/d="([^"]+)"/);
|
|
395
|
-
if (pathDataMatch) {
|
|
396
|
-
// Extract fill color
|
|
397
|
-
const fillMatch = attributes.match(/fill="([^"]+)"/);
|
|
398
|
-
paths.push({
|
|
399
|
-
pathData: pathDataMatch[1],
|
|
400
|
-
transform: transformStack[transformStack.length - 1],
|
|
401
|
-
clipRect: clipPathStack[clipPathStack.length - 1],
|
|
402
|
-
fill: fillMatch ? fillMatch[1] : undefined,
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
return paths;
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
|
-
* Parse SVG content and extract path points with transforms applied
|
|
412
|
-
*/
|
|
413
|
-
export function extractPathPoints(svgContent, targetWidth, targetHeight, rotation = 0, autoFit = false, explicitBounds) {
|
|
414
|
-
// Extract viewBox
|
|
415
|
-
const viewBoxMatch = svgContent.match(/viewBox="([^"]+)"/);
|
|
416
|
-
let viewBoxX = 0;
|
|
417
|
-
let viewBoxY = 0;
|
|
418
|
-
let viewBoxWidth = 100;
|
|
419
|
-
let viewBoxHeight = 100;
|
|
420
|
-
if (viewBoxMatch) {
|
|
421
|
-
const parts = viewBoxMatch[1].split(/\s+/).map(Number);
|
|
422
|
-
viewBoxX = parts[0] || 0;
|
|
423
|
-
viewBoxY = parts[1] || 0;
|
|
424
|
-
viewBoxWidth = parts[2] || 100;
|
|
425
|
-
viewBoxHeight = parts[3] || 100;
|
|
426
|
-
}
|
|
427
|
-
// Parse SVG hierarchically to get paths with their correct transforms
|
|
428
|
-
const parsedPaths = parseHierarchicalPaths(svgContent);
|
|
429
|
-
const pixelWidth = targetWidth * 2;
|
|
430
|
-
const pixelHeight = targetHeight * 4;
|
|
431
|
-
// Determine bounds to use (explicit > autoFit > viewBox)
|
|
432
|
-
let boundsMinX;
|
|
433
|
-
let boundsMinY;
|
|
434
|
-
let boundsMaxX;
|
|
435
|
-
let boundsMaxY;
|
|
436
|
-
if (explicitBounds) {
|
|
437
|
-
// Use explicit bounds provided by caller
|
|
438
|
-
boundsMinX = explicitBounds.minX;
|
|
439
|
-
boundsMinY = explicitBounds.minY;
|
|
440
|
-
boundsMaxX = explicitBounds.maxX;
|
|
441
|
-
boundsMaxY = explicitBounds.maxY;
|
|
442
|
-
}
|
|
443
|
-
else if (autoFit) {
|
|
444
|
-
// Calculate bounds from actual path content WITH CORRECT TRANSFORMS
|
|
445
|
-
boundsMinX = viewBoxX;
|
|
446
|
-
boundsMinY = viewBoxY;
|
|
447
|
-
boundsMaxX = viewBoxX + viewBoxWidth;
|
|
448
|
-
boundsMaxY = viewBoxY + viewBoxHeight;
|
|
449
|
-
let hasPoints = false;
|
|
450
|
-
for (const parsedPath of parsedPaths) {
|
|
451
|
-
try {
|
|
452
|
-
const pathData = new SVGPathData(parsedPath.pathData).toAbs();
|
|
453
|
-
const commands = pathData.commands;
|
|
454
|
-
for (const cmd of commands) {
|
|
455
|
-
// Check all coordinate types
|
|
456
|
-
const coords = [];
|
|
457
|
-
if ('x' in cmd && 'y' in cmd) {
|
|
458
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
459
|
-
coords.push([cmd.x, cmd.y]);
|
|
460
|
-
}
|
|
461
|
-
if ('x1' in cmd && 'y1' in cmd) {
|
|
462
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
463
|
-
coords.push([cmd.x1, cmd.y1]);
|
|
464
|
-
}
|
|
465
|
-
if ('x2' in cmd && 'y2' in cmd) {
|
|
466
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
467
|
-
coords.push([cmd.x2, cmd.y2]);
|
|
468
|
-
}
|
|
469
|
-
// Apply THIS path's specific transform (not all transforms!)
|
|
470
|
-
for (const [x, y] of coords) {
|
|
471
|
-
const [tx, ty] = transformPoint(x, y, parsedPath.transform);
|
|
472
|
-
if (!hasPoints) {
|
|
473
|
-
boundsMinX = boundsMaxX = tx;
|
|
474
|
-
boundsMinY = boundsMaxY = ty;
|
|
475
|
-
hasPoints = true;
|
|
476
|
-
}
|
|
477
|
-
else {
|
|
478
|
-
boundsMinX = Math.min(boundsMinX, tx);
|
|
479
|
-
boundsMinY = Math.min(boundsMinY, ty);
|
|
480
|
-
boundsMaxX = Math.max(boundsMaxX, tx);
|
|
481
|
-
boundsMaxY = Math.max(boundsMaxY, ty);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
catch {
|
|
487
|
-
// Skip invalid paths
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
else {
|
|
492
|
-
// Use viewBox bounds
|
|
493
|
-
boundsMinX = viewBoxX;
|
|
494
|
-
boundsMinY = viewBoxY;
|
|
495
|
-
boundsMaxX = viewBoxX + viewBoxWidth;
|
|
496
|
-
boundsMaxY = viewBoxY + viewBoxHeight;
|
|
497
|
-
}
|
|
498
|
-
// Calculate dimensions based on bounds
|
|
499
|
-
const boundsWidth = boundsMaxX - boundsMinX;
|
|
500
|
-
const boundsHeight = boundsMaxY - boundsMinY;
|
|
501
|
-
// Calculate scale to fit into target pixel dimensions
|
|
502
|
-
const scaleX = pixelWidth / boundsWidth;
|
|
503
|
-
const scaleY = pixelHeight / boundsHeight;
|
|
504
|
-
const scale = Math.min(scaleX, scaleY) * 0.9;
|
|
505
|
-
// Center offset (accounting for bounds offset)
|
|
506
|
-
const offsetX = (pixelWidth - boundsWidth * scale) / 2 - boundsMinX * scale;
|
|
507
|
-
const offsetY = (pixelHeight - boundsHeight * scale) / 2 - boundsMinY * scale;
|
|
508
|
-
// Apply rotation if specified (rotate around center of bounds)
|
|
509
|
-
let rotationTransform = identityMatrix();
|
|
510
|
-
if (rotation !== 0) {
|
|
511
|
-
const angle = (rotation * Math.PI) / 180;
|
|
512
|
-
const cos = Math.cos(angle);
|
|
513
|
-
const sin = Math.sin(angle);
|
|
514
|
-
const centerX = (boundsMinX + boundsMaxX) / 2;
|
|
515
|
-
const centerY = (boundsMinY + boundsMaxY) / 2;
|
|
516
|
-
// Translate to origin, rotate, translate back
|
|
517
|
-
rotationTransform = multiplyMatrices({ a: 1, b: 0, c: 0, d: 1, e: centerX, f: centerY }, multiplyMatrices({ a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 }, { a: 1, b: 0, c: 0, d: 1, e: -centerX, f: -centerY }));
|
|
518
|
-
}
|
|
519
|
-
// Viewport transform: rotation -> scale to fit -> center
|
|
520
|
-
const viewportTransform = multiplyMatrices({ a: scale, b: 0, c: 0, d: scale, e: offsetX, f: offsetY }, rotationTransform);
|
|
521
|
-
// Render each path with its individual transform chain
|
|
522
|
-
const allPaths = [];
|
|
523
|
-
// SVG viewBox bounds for clipping (lottie uses clip-path to enforce this)
|
|
524
|
-
const clipMinX = viewBoxX;
|
|
525
|
-
const clipMinY = viewBoxY;
|
|
526
|
-
const clipMaxX = viewBoxX + viewBoxWidth;
|
|
527
|
-
const clipMaxY = viewBoxY + viewBoxHeight;
|
|
528
|
-
for (const parsedPath of parsedPaths) {
|
|
529
|
-
try {
|
|
530
|
-
// Skip white fills (these are used for negative space in logos)
|
|
531
|
-
// Check for common white color formats: "#ffffff", "#fff", "rgb(255,255,255)", "white"
|
|
532
|
-
const fill = parsedPath.fill?.toLowerCase();
|
|
533
|
-
if (fill === '#ffffff' ||
|
|
534
|
-
fill === '#fff' ||
|
|
535
|
-
fill === 'rgb(255,255,255)' ||
|
|
536
|
-
fill === 'white' ||
|
|
537
|
-
fill === 'rgba(255,255,255,1)') {
|
|
538
|
-
continue; // Skip white fills
|
|
539
|
-
}
|
|
540
|
-
const pathData = new SVGPathData(parsedPath.pathData).toAbs();
|
|
541
|
-
// First, check if this path is within the viewBox clip bounds
|
|
542
|
-
// Apply the path's transform to check bounds before viewport scaling
|
|
543
|
-
let pathBoundsMinX = Infinity;
|
|
544
|
-
let pathBoundsMinY = Infinity;
|
|
545
|
-
let pathBoundsMaxX = -Infinity;
|
|
546
|
-
let pathBoundsMaxY = -Infinity;
|
|
547
|
-
pathData.commands.forEach((cmd) => {
|
|
548
|
-
if ('x' in cmd && 'y' in cmd) {
|
|
549
|
-
const [tx, ty] = transformPoint(
|
|
550
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
551
|
-
cmd.x,
|
|
552
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
553
|
-
cmd.y, parsedPath.transform);
|
|
554
|
-
pathBoundsMinX = Math.min(pathBoundsMinX, tx);
|
|
555
|
-
pathBoundsMinY = Math.min(pathBoundsMinY, ty);
|
|
556
|
-
pathBoundsMaxX = Math.max(pathBoundsMaxX, tx);
|
|
557
|
-
pathBoundsMaxY = Math.max(pathBoundsMaxY, ty);
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
// Skip paths that are completely outside the clip bounds
|
|
561
|
-
if (pathBoundsMaxX < clipMinX ||
|
|
562
|
-
pathBoundsMinX > clipMaxX ||
|
|
563
|
-
pathBoundsMaxY < clipMinY ||
|
|
564
|
-
pathBoundsMinY > clipMaxY) {
|
|
565
|
-
continue; // Path is completely clipped out
|
|
566
|
-
}
|
|
567
|
-
// Final transform: path's SVG transform -> viewport transform
|
|
568
|
-
const finalTransform = multiplyMatrices(viewportTransform, parsedPath.transform);
|
|
569
|
-
const commands = pathData.commands;
|
|
570
|
-
const pathPoints = [];
|
|
571
|
-
let currentX = 0;
|
|
572
|
-
let currentY = 0;
|
|
573
|
-
let startX = 0;
|
|
574
|
-
let startY = 0;
|
|
575
|
-
for (const cmd of commands) {
|
|
576
|
-
switch (cmd.type) {
|
|
577
|
-
case SVGPathData.MOVE_TO: {
|
|
578
|
-
const [tx, ty] = transformPoint(cmd.x, cmd.y, finalTransform);
|
|
579
|
-
currentX = tx;
|
|
580
|
-
currentY = ty;
|
|
581
|
-
startX = tx;
|
|
582
|
-
startY = ty;
|
|
583
|
-
pathPoints.push([tx, ty]);
|
|
584
|
-
break;
|
|
585
|
-
}
|
|
586
|
-
case SVGPathData.LINE_TO: {
|
|
587
|
-
const [tx, ty] = transformPoint(cmd.x, cmd.y, finalTransform);
|
|
588
|
-
// Add intermediate points for better rendering
|
|
589
|
-
const steps = Math.max(1, Math.ceil(Math.hypot(tx - currentX, ty - currentY) / 2));
|
|
590
|
-
for (let i = 1; i <= steps; i++) {
|
|
591
|
-
const t = i / steps;
|
|
592
|
-
pathPoints.push([
|
|
593
|
-
currentX + (tx - currentX) * t,
|
|
594
|
-
currentY + (ty - currentY) * t,
|
|
595
|
-
]);
|
|
596
|
-
}
|
|
597
|
-
currentX = tx;
|
|
598
|
-
currentY = ty;
|
|
599
|
-
break;
|
|
600
|
-
}
|
|
601
|
-
case SVGPathData.HORIZ_LINE_TO: {
|
|
602
|
-
const [tx] = transformPoint(cmd.x, currentY / finalTransform.d, finalTransform);
|
|
603
|
-
const steps = Math.max(1, Math.ceil(Math.abs(tx - currentX) / 2));
|
|
604
|
-
for (let i = 1; i <= steps; i++) {
|
|
605
|
-
const t = i / steps;
|
|
606
|
-
pathPoints.push([currentX + (tx - currentX) * t, currentY]);
|
|
607
|
-
}
|
|
608
|
-
currentX = tx;
|
|
609
|
-
break;
|
|
610
|
-
}
|
|
611
|
-
case SVGPathData.VERT_LINE_TO: {
|
|
612
|
-
const [, ty] = transformPoint(currentX / finalTransform.a, cmd.y, finalTransform);
|
|
613
|
-
const steps = Math.max(1, Math.ceil(Math.abs(ty - currentY) / 2));
|
|
614
|
-
for (let i = 1; i <= steps; i++) {
|
|
615
|
-
const t = i / steps;
|
|
616
|
-
pathPoints.push([currentX, currentY + (ty - currentY) * t]);
|
|
617
|
-
}
|
|
618
|
-
currentY = ty;
|
|
619
|
-
break;
|
|
620
|
-
}
|
|
621
|
-
case SVGPathData.CURVE_TO: {
|
|
622
|
-
const [x1t, y1t] = transformPoint(cmd.x1, cmd.y1, finalTransform);
|
|
623
|
-
const [x2t, y2t] = transformPoint(cmd.x2, cmd.y2, finalTransform);
|
|
624
|
-
const [xt, yt] = transformPoint(cmd.x, cmd.y, finalTransform);
|
|
625
|
-
// Sample bezier curve
|
|
626
|
-
const steps = 20;
|
|
627
|
-
for (let i = 1; i <= steps; i++) {
|
|
628
|
-
const t = i / steps;
|
|
629
|
-
const mt = 1 - t;
|
|
630
|
-
const mt2 = mt * mt;
|
|
631
|
-
const mt3 = mt2 * mt;
|
|
632
|
-
const t2 = t * t;
|
|
633
|
-
const t3 = t2 * t;
|
|
634
|
-
const x = mt3 * currentX + 3 * mt2 * t * x1t + 3 * mt * t2 * x2t + t3 * xt;
|
|
635
|
-
const y = mt3 * currentY + 3 * mt2 * t * y1t + 3 * mt * t2 * y2t + t3 * yt;
|
|
636
|
-
pathPoints.push([x, y]);
|
|
637
|
-
}
|
|
638
|
-
currentX = xt;
|
|
639
|
-
currentY = yt;
|
|
640
|
-
break;
|
|
641
|
-
}
|
|
642
|
-
case SVGPathData.SMOOTH_CURVE_TO: {
|
|
643
|
-
const [x2t, y2t] = transformPoint(cmd.x2, cmd.y2, finalTransform);
|
|
644
|
-
const [xt, yt] = transformPoint(cmd.x, cmd.y, finalTransform);
|
|
645
|
-
const steps = 20;
|
|
646
|
-
for (let i = 1; i <= steps; i++) {
|
|
647
|
-
const t = i / steps;
|
|
648
|
-
const mt = 1 - t;
|
|
649
|
-
const mt2 = mt * mt;
|
|
650
|
-
const mt3 = mt2 * mt;
|
|
651
|
-
const t2 = t * t;
|
|
652
|
-
const t3 = t2 * t;
|
|
653
|
-
const x = mt3 * currentX + 3 * mt2 * t * currentX + 3 * mt * t2 * x2t + t3 * xt;
|
|
654
|
-
const y = mt3 * currentY + 3 * mt2 * t * currentY + 3 * mt * t2 * y2t + t3 * yt;
|
|
655
|
-
pathPoints.push([x, y]);
|
|
656
|
-
}
|
|
657
|
-
currentX = xt;
|
|
658
|
-
currentY = yt;
|
|
659
|
-
break;
|
|
660
|
-
}
|
|
661
|
-
case SVGPathData.QUAD_TO: {
|
|
662
|
-
const [x1t, y1t] = transformPoint(cmd.x1, cmd.y1, finalTransform);
|
|
663
|
-
const [xt, yt] = transformPoint(cmd.x, cmd.y, finalTransform);
|
|
664
|
-
const steps = 20;
|
|
665
|
-
for (let i = 1; i <= steps; i++) {
|
|
666
|
-
const t = i / steps;
|
|
667
|
-
const mt = 1 - t;
|
|
668
|
-
const mt2 = mt * mt;
|
|
669
|
-
const t2 = t * t;
|
|
670
|
-
const x = mt2 * currentX + 2 * mt * t * x1t + t2 * xt;
|
|
671
|
-
const y = mt2 * currentY + 2 * mt * t * y1t + t2 * yt;
|
|
672
|
-
pathPoints.push([x, y]);
|
|
673
|
-
}
|
|
674
|
-
currentX = xt;
|
|
675
|
-
currentY = yt;
|
|
676
|
-
break;
|
|
677
|
-
}
|
|
678
|
-
case SVGPathData.CLOSE_PATH:
|
|
679
|
-
// Add line back to start
|
|
680
|
-
const steps = Math.max(1, Math.ceil(Math.hypot(startX - currentX, startY - currentY) / 2));
|
|
681
|
-
for (let i = 1; i <= steps; i++) {
|
|
682
|
-
const t = i / steps;
|
|
683
|
-
pathPoints.push([
|
|
684
|
-
currentX + (startX - currentX) * t,
|
|
685
|
-
currentY + (startY - currentY) * t,
|
|
686
|
-
]);
|
|
687
|
-
}
|
|
688
|
-
currentX = startX;
|
|
689
|
-
currentY = startY;
|
|
690
|
-
break;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
// Apply clipRect filtering if this path has a mask
|
|
694
|
-
if (parsedPath.clipRect && pathPoints.length > 0) {
|
|
695
|
-
const clip = parsedPath.clipRect;
|
|
696
|
-
// Transform clip bounds to pixel space (once per path, not per point)
|
|
697
|
-
const [clipMinX, clipMinY] = transformPoint(clip.minX, clip.minY, viewportTransform);
|
|
698
|
-
const [clipMaxX, clipMaxY] = transformPoint(clip.maxX, clip.maxY, viewportTransform);
|
|
699
|
-
// Normalize bounds in case transform flipped them
|
|
700
|
-
const pixelClipMinX = Math.min(clipMinX, clipMaxX);
|
|
701
|
-
const pixelClipMaxX = Math.max(clipMinX, clipMaxX);
|
|
702
|
-
const pixelClipMinY = Math.min(clipMinY, clipMaxY);
|
|
703
|
-
const pixelClipMaxY = Math.max(clipMinY, clipMaxY);
|
|
704
|
-
// Filter points to only those within clip rectangle
|
|
705
|
-
const clippedPoints = pathPoints.filter(([x, y]) => {
|
|
706
|
-
return (x >= pixelClipMinX &&
|
|
707
|
-
x <= pixelClipMaxX &&
|
|
708
|
-
y >= pixelClipMinY &&
|
|
709
|
-
y <= pixelClipMaxY);
|
|
710
|
-
});
|
|
711
|
-
if (clippedPoints.length > 0) {
|
|
712
|
-
allPaths.push(clippedPoints);
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
else if (pathPoints.length > 0) {
|
|
716
|
-
// No clipping needed
|
|
717
|
-
allPaths.push(pathPoints);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
catch {
|
|
721
|
-
// Skip invalid paths
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
return allPaths;
|
|
725
|
-
}
|
|
726
|
-
/**
|
|
727
|
-
* Render SVG content to a braille canvas
|
|
728
|
-
*/
|
|
729
|
-
export function renderSVGToCanvas(svgContent, options = {}) {
|
|
730
|
-
const targetWidth = options.width || 20;
|
|
731
|
-
const targetHeight = options.height || 10;
|
|
732
|
-
const canvas = createCanvas(targetWidth, targetHeight);
|
|
733
|
-
const allPaths = extractPathPoints(svgContent, targetWidth, targetHeight, options.rotation || 0, options.autoFit || false, options.explicitBounds);
|
|
734
|
-
// Draw all paths
|
|
735
|
-
for (const pathPoints of allPaths) {
|
|
736
|
-
// Draw edges (stroke) if requested
|
|
737
|
-
if (options.stroke !== false) {
|
|
738
|
-
for (let i = 0; i < pathPoints.length - 1; i++) {
|
|
739
|
-
drawLine(canvas, pathPoints[i][0], pathPoints[i][1], pathPoints[i + 1][0], pathPoints[i + 1][1]);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
// Fill if requested
|
|
743
|
-
if (options.fill !== false && pathPoints.length > 2) {
|
|
744
|
-
fillPath(canvas, pathPoints, options.fillDensity || 1.0);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
// Invert pixels if requested
|
|
748
|
-
if (options.invert) {
|
|
749
|
-
for (let y = 0; y < canvas.pixels.length; y++) {
|
|
750
|
-
for (let x = 0; x < canvas.pixels[y].length; x++) {
|
|
751
|
-
canvas.pixels[y][x] = !canvas.pixels[y][x];
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
return canvas;
|
|
756
|
-
}
|
|
757
|
-
/**
|
|
758
|
-
* Convert braille canvas to string array
|
|
759
|
-
*/
|
|
760
|
-
export function canvasToStrings(canvas) {
|
|
761
|
-
const lines = [];
|
|
762
|
-
for (let charY = 0; charY < canvas.height; charY++) {
|
|
763
|
-
let line = '';
|
|
764
|
-
for (let charX = 0; charX < canvas.width; charX++) {
|
|
765
|
-
let code = BRAILLE_BASE;
|
|
766
|
-
// Each braille character is 2x4 pixels
|
|
767
|
-
for (let dy = 0; dy < 4; dy++) {
|
|
768
|
-
for (let dx = 0; dx < 2; dx++) {
|
|
769
|
-
const px = charX * 2 + dx;
|
|
770
|
-
const py = charY * 4 + dy;
|
|
771
|
-
if (canvas.pixels[py]?.[px]) {
|
|
772
|
-
code += DOT_BITS[dy][dx];
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
line += String.fromCharCode(code);
|
|
777
|
-
}
|
|
778
|
-
lines.push(line);
|
|
779
|
-
}
|
|
780
|
-
return lines;
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Render SVG content string to braille strings
|
|
784
|
-
*/
|
|
785
|
-
export function renderSVGContent(content, options = {}) {
|
|
786
|
-
const canvas = renderSVGToCanvas(content, options);
|
|
787
|
-
return canvasToStrings(canvas);
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Load and render the Guild logo from local assets
|
|
791
|
-
*/
|
|
792
|
-
export function renderGuildLogo(widthChars = 20, heightChars = 10, invert = true, rotation = 0) {
|
|
793
|
-
const content = loadLogoSVG();
|
|
794
|
-
if (!content) {
|
|
795
|
-
// Fallback: return empty lines if logo not found
|
|
796
|
-
return Array(heightChars).fill('⠀'.repeat(widthChars));
|
|
797
|
-
}
|
|
798
|
-
return renderSVGContent(content, {
|
|
799
|
-
width: widthChars,
|
|
800
|
-
height: heightChars,
|
|
801
|
-
fill: true,
|
|
802
|
-
invert,
|
|
803
|
-
rotation,
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* Get raw path points for animation purposes
|
|
808
|
-
* Returns points in pixel coordinate space for the given dimensions
|
|
809
|
-
*/
|
|
810
|
-
export function getLogoPathPoints(widthChars = 20, heightChars = 10) {
|
|
811
|
-
const content = loadLogoSVG();
|
|
812
|
-
if (!content) {
|
|
813
|
-
return [];
|
|
814
|
-
}
|
|
815
|
-
return extractPathPoints(content, widthChars, heightChars);
|
|
816
|
-
}
|
|
817
|
-
/**
|
|
818
|
-
* Get pixel positions from rendered logo (useful for inverted logos)
|
|
819
|
-
* Returns array of [x, y] pixel coordinates that are "on" in the rendered logo
|
|
820
|
-
*/
|
|
821
|
-
export function getLogoPixelPositions(widthChars = 20, heightChars = 10, invert = true) {
|
|
822
|
-
const content = loadLogoSVG();
|
|
823
|
-
if (!content) {
|
|
824
|
-
return [];
|
|
825
|
-
}
|
|
826
|
-
const canvas = renderSVGToCanvas(content, {
|
|
827
|
-
width: widthChars,
|
|
828
|
-
height: heightChars,
|
|
829
|
-
fill: true,
|
|
830
|
-
invert,
|
|
831
|
-
});
|
|
832
|
-
const positions = [];
|
|
833
|
-
for (let y = 0; y < canvas.pixels.length; y++) {
|
|
834
|
-
for (let x = 0; x < canvas.pixels[y].length; x++) {
|
|
835
|
-
if (canvas.pixels[y][x]) {
|
|
836
|
-
positions.push([x, y]);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
return positions;
|
|
841
|
-
}
|
|
842
|
-
/**
|
|
843
|
-
* Load the Guild logo SVG from local assets
|
|
844
|
-
*/
|
|
845
|
-
function loadLogoSVG() {
|
|
846
|
-
// Primary: CLI's own copy in assets directory
|
|
847
|
-
const possiblePaths = [
|
|
848
|
-
path.resolve(__dirname, '../assets/logo.svg'), // From dist/lib/
|
|
849
|
-
path.resolve(__dirname, '../../src/assets/logo.svg'), // Dev fallback
|
|
850
|
-
];
|
|
851
|
-
for (const logoPath of possiblePaths) {
|
|
852
|
-
if (fs.existsSync(logoPath)) {
|
|
853
|
-
return fs.readFileSync(logoPath, 'utf-8');
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
return null;
|
|
857
|
-
}
|
|
858
|
-
//# sourceMappingURL=svg-renderer.js.map
|