locusing 0.1.2

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/export.js ADDED
@@ -0,0 +1,528 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __spreadValues = (a, b) => {
14
+ for (var prop in b || (b = {}))
15
+ if (__hasOwnProp.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ if (__getOwnPropSymbols)
18
+ for (var prop of __getOwnPropSymbols(b)) {
19
+ if (__propIsEnum.call(b, prop))
20
+ __defNormalProp(a, prop, b[prop]);
21
+ }
22
+ return a;
23
+ };
24
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ var __objRest = (source, exclude) => {
26
+ var target = {};
27
+ for (var prop in source)
28
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
29
+ target[prop] = source[prop];
30
+ if (source != null && __getOwnPropSymbols)
31
+ for (var prop of __getOwnPropSymbols(source)) {
32
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
33
+ target[prop] = source[prop];
34
+ }
35
+ return target;
36
+ };
37
+ var __export = (target, all) => {
38
+ for (var name in all)
39
+ __defProp(target, name, { get: all[name], enumerable: true });
40
+ };
41
+ var __copyProps = (to, from, except, desc) => {
42
+ if (from && typeof from === "object" || typeof from === "function") {
43
+ for (let key of __getOwnPropNames(from))
44
+ if (!__hasOwnProp.call(to, key) && key !== except)
45
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
46
+ }
47
+ return to;
48
+ };
49
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
50
+ // If the importer is in node compatibility mode or this is not an ESM
51
+ // file that has been converted to a CommonJS file using a Babel-
52
+ // compatible transform (i.e. "__esModule" has not been set), then set
53
+ // "default" to the CommonJS "module.exports" for node compatibility.
54
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
55
+ mod
56
+ ));
57
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
58
+
59
+ // src/export.ts
60
+ var export_exports = {};
61
+ __export(export_exports, {
62
+ PlaywrightExporter: () => PlaywrightExporter,
63
+ exportSVGToPNG: () => exportSVGToPNG,
64
+ exportToPNG: () => exportToPNG,
65
+ generateExportHTML: () => generateExportHTML,
66
+ generateExportHTMLWithInlineCSS: () => generateExportHTMLWithInlineCSS
67
+ });
68
+ module.exports = __toCommonJS(export_exports);
69
+
70
+ // src/core/render-svg.ts
71
+ var DEFAULT_FRAME = {
72
+ width: 16,
73
+ height: 9
74
+ };
75
+ var XHTML_NS = "http://www.w3.org/1999/xhtml";
76
+ var SVG_NS = "http://www.w3.org/2000/svg";
77
+ function toSVGString(diagram, options = {}) {
78
+ const { width = 800, height = 600, background, frame } = options;
79
+ const frameConfig = frame ? typeof frame === "boolean" ? DEFAULT_FRAME : frame : null;
80
+ if (frameConfig) {
81
+ const { width: fw, height: fh } = frameConfig;
82
+ const viewBox = `${-fw / 2} ${-fh / 2} ${fw} ${fh}`;
83
+ let content2 = "";
84
+ if (background) {
85
+ content2 += `<rect x="${-fw / 2}" y="${-fh / 2}" width="${fw}" height="${fh}" fill="${background}"/>`;
86
+ }
87
+ content2 += `<g transform="scale(1,-1)">${shapeToSVGString(diagram.shape)}</g>`;
88
+ return `<svg xmlns="${SVG_NS}" width="${width}" height="${height}" viewBox="${viewBox}">${content2}</svg>`;
89
+ }
90
+ let content = "";
91
+ if (background) {
92
+ content += `<rect width="100%" height="100%" fill="${background}"/>`;
93
+ }
94
+ content += shapeToSVGString(diagram.shape);
95
+ return `<svg xmlns="${SVG_NS}" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">${content}</svg>`;
96
+ }
97
+ function shapeToSVGString(shape) {
98
+ const attrs = [`id="${shape.id}"`];
99
+ const style = styleToString(shape.style);
100
+ const transform = transformToString(shape.transform);
101
+ if (style) attrs.push(style);
102
+ if (transform) attrs.push(`transform="${transform}"`);
103
+ switch (shape.type) {
104
+ case "rect":
105
+ attrs.push(`x="${shape.x}" y="${shape.y}" width="${shape.width}" height="${shape.height}"`);
106
+ if (shape.rx) attrs.push(`rx="${shape.rx}"`);
107
+ if (shape.ry) attrs.push(`ry="${shape.ry}"`);
108
+ return `<rect ${attrs.join(" ")}/>`;
109
+ case "circle":
110
+ attrs.push(`cx="${shape.cx}" cy="${shape.cy}" r="${shape.r}"`);
111
+ return `<circle ${attrs.join(" ")}/>`;
112
+ case "ellipse":
113
+ attrs.push(`cx="${shape.cx}" cy="${shape.cy}" rx="${shape.rx}" ry="${shape.ry}"`);
114
+ return `<ellipse ${attrs.join(" ")}/>`;
115
+ case "line":
116
+ attrs.push(`x1="${shape.x1}" y1="${shape.y1}" x2="${shape.x2}" y2="${shape.y2}"`);
117
+ return `<line ${attrs.join(" ")}/>`;
118
+ case "polyline":
119
+ attrs.push(`points="${shape.points.map((p) => `${p[0]},${p[1]}`).join(" ")}"`);
120
+ return `<polyline ${attrs.join(" ")}/>`;
121
+ case "polygon":
122
+ attrs.push(`points="${shape.points.map((p) => `${p[0]},${p[1]}`).join(" ")}"`);
123
+ return `<polygon ${attrs.join(" ")}/>`;
124
+ case "path":
125
+ attrs.push(`d="${commandsToPath(shape.commands)}"`);
126
+ return `<path ${attrs.join(" ")}/>`;
127
+ case "arc":
128
+ attrs.push(`d="${arcToPath(shape.cx, shape.cy, shape.r, shape.startAngle, shape.endAngle)}"`);
129
+ return `<path ${attrs.join(" ")}/>`;
130
+ case "text":
131
+ const textStyle = textStyleToString(shape.style);
132
+ if (textStyle) attrs.push(textStyle);
133
+ attrs.push(`x="${shape.x}" y="${shape.y}"`);
134
+ return `<text ${attrs.join(" ")}>${escapeXML(shape.content)}</text>`;
135
+ case "tex":
136
+ const texShape = shape;
137
+ return `<foreignObject x="${texShape.x}" y="${texShape.y}" width="1000" height="200" overflow="visible"><div xmlns="${XHTML_NS}" style="font-size:${texShape.fontSize}px;color:${texShape.color};display:inline-block;white-space:nowrap">${texShape.html}</div></foreignObject>`;
138
+ case "group":
139
+ const children = shape.children.map(shapeToSVGString).join("");
140
+ return `<g ${attrs.join(" ")}>${children}</g>`;
141
+ case "svg":
142
+ const svgShapeStr = shape;
143
+ const scaleXStr = shape.width / svgShapeStr.originalWidth;
144
+ const scaleYStr = shape.height / svgShapeStr.originalHeight;
145
+ return `<g ${attrs.join(" ")} transform="scale(${scaleXStr}, ${scaleYStr})">${svgShapeStr.content}</g>`;
146
+ case "image":
147
+ const imgShapeStr = shape;
148
+ attrs.push(`href="${imgShapeStr.src}"`);
149
+ attrs.push(`x="${-shape.width / 2}" y="${-shape.height / 2}"`);
150
+ attrs.push(`width="${shape.width}" height="${shape.height}"`);
151
+ attrs.push('preserveAspectRatio="xMidYMid meet"');
152
+ return `<image ${attrs.join(" ")}/>`;
153
+ default:
154
+ return "";
155
+ }
156
+ }
157
+ function styleToString(style) {
158
+ const parts = [];
159
+ if (style.fill) parts.push(`fill="${style.fill}"`);
160
+ if (style.fillOpacity !== void 0) parts.push(`fill-opacity="${style.fillOpacity}"`);
161
+ if (style.stroke) parts.push(`stroke="${style.stroke}"`);
162
+ if (style.strokeWidth !== void 0) parts.push(`stroke-width="${style.strokeWidth}"`);
163
+ if (style.strokeOpacity !== void 0) parts.push(`stroke-opacity="${style.strokeOpacity}"`);
164
+ if (style.strokeDasharray) parts.push(`stroke-dasharray="${style.strokeDasharray}"`);
165
+ if (style.strokeLinecap) parts.push(`stroke-linecap="${style.strokeLinecap}"`);
166
+ if (style.strokeLinejoin) parts.push(`stroke-linejoin="${style.strokeLinejoin}"`);
167
+ if (style.opacity !== void 0) parts.push(`opacity="${style.opacity}"`);
168
+ return parts.join(" ");
169
+ }
170
+ function textStyleToString(style) {
171
+ const parts = [];
172
+ if (style.fontSize) parts.push(`font-size="${style.fontSize}"`);
173
+ if (style.fontFamily) parts.push(`font-family="${style.fontFamily}"`);
174
+ if (style.fontWeight) parts.push(`font-weight="${style.fontWeight}"`);
175
+ if (style.textAnchor) parts.push(`text-anchor="${style.textAnchor}"`);
176
+ if (style.dominantBaseline) parts.push(`dominant-baseline="${style.dominantBaseline}"`);
177
+ return parts.join(" ");
178
+ }
179
+ function transformToString(matrix) {
180
+ const [a, b, c, d, e, f] = [
181
+ matrix[0],
182
+ matrix[3],
183
+ matrix[1],
184
+ matrix[4],
185
+ matrix[2],
186
+ matrix[5]
187
+ ];
188
+ if (a === 1 && b === 0 && c === 0 && d === 1 && e === 0 && f === 0) {
189
+ return "";
190
+ }
191
+ return `matrix(${a},${b},${c},${d},${e},${f})`;
192
+ }
193
+ function commandsToPath(commands, _frameMode = false) {
194
+ return commands.map((cmd) => {
195
+ switch (cmd.type) {
196
+ case "M":
197
+ return `M${cmd.x},${cmd.y}`;
198
+ case "L":
199
+ return `L${cmd.x},${cmd.y}`;
200
+ case "H":
201
+ return `H${cmd.x}`;
202
+ case "V":
203
+ return `V${cmd.y}`;
204
+ case "C":
205
+ return `C${cmd.x1},${cmd.y1} ${cmd.x2},${cmd.y2} ${cmd.x},${cmd.y}`;
206
+ case "S":
207
+ return `S${cmd.x2},${cmd.y2} ${cmd.x},${cmd.y}`;
208
+ case "Q":
209
+ return `Q${cmd.x1},${cmd.y1} ${cmd.x},${cmd.y}`;
210
+ case "T":
211
+ return `T${cmd.x},${cmd.y}`;
212
+ case "A": {
213
+ return `A${cmd.rx},${cmd.ry} ${cmd.angle} ${cmd.largeArc ? 1 : 0},${cmd.sweep ? 1 : 0} ${cmd.x},${cmd.y}`;
214
+ }
215
+ case "Z":
216
+ return "Z";
217
+ }
218
+ }).join(" ");
219
+ }
220
+ function arcToPath(cx, cy, r, startAngle, endAngle) {
221
+ const startX = cx + r * Math.cos(startAngle);
222
+ const startY = cy + r * Math.sin(startAngle);
223
+ const endX = cx + r * Math.cos(endAngle);
224
+ const endY = cy + r * Math.sin(endAngle);
225
+ const largeArc = Math.abs(endAngle - startAngle) > Math.PI ? 1 : 0;
226
+ const sweep = endAngle > startAngle ? 1 : 0;
227
+ return `M${startX},${startY} A${r},${r} 0 ${largeArc},${sweep} ${endX},${endY}`;
228
+ }
229
+ function escapeXML(str) {
230
+ if (typeof str !== "string") {
231
+ str = String(str != null ? str : "");
232
+ }
233
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
234
+ }
235
+
236
+ // src/export/html-template.ts
237
+ var KATEX_CSS_URL = "https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/katex.min.css";
238
+ function generateExportHTML(svgContent, options) {
239
+ const {
240
+ width = 1920,
241
+ height = 1080,
242
+ background = "transparent"
243
+ } = options;
244
+ return `<!DOCTYPE html>
245
+ <html>
246
+ <head>
247
+ <meta charset="UTF-8">
248
+ <meta name="viewport" content="width=${width}, height=${height}">
249
+ <link rel="stylesheet" href="${KATEX_CSS_URL}" crossorigin="anonymous">
250
+ <style>
251
+ * {
252
+ margin: 0;
253
+ padding: 0;
254
+ box-sizing: border-box;
255
+ }
256
+ html, body {
257
+ width: ${width}px;
258
+ height: ${height}px;
259
+ overflow: hidden;
260
+ }
261
+ body {
262
+ background: ${background};
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: center;
266
+ }
267
+ svg {
268
+ width: 100%;
269
+ height: 100%;
270
+ display: block;
271
+ }
272
+ /* KaTeX \u5728 SVG foreignObject \u4E2D\u7684\u6837\u5F0F\u4FEE\u6B63 */
273
+ .katex {
274
+ font-size: inherit;
275
+ line-height: 1;
276
+ }
277
+ .katex-html {
278
+ white-space: nowrap;
279
+ }
280
+ </style>
281
+ </head>
282
+ <body>
283
+ ${svgContent}
284
+ </body>
285
+ </html>`;
286
+ }
287
+ function generateExportHTMLWithInlineCSS(katexCSS, svgContent, options) {
288
+ const {
289
+ width = 1920,
290
+ height = 1080,
291
+ background = "transparent"
292
+ } = options;
293
+ return `<!DOCTYPE html>
294
+ <html>
295
+ <head>
296
+ <meta charset="UTF-8">
297
+ <meta name="viewport" content="width=${width}, height=${height}">
298
+ <style>
299
+ ${katexCSS}
300
+ </style>
301
+ <style>
302
+ * {
303
+ margin: 0;
304
+ padding: 0;
305
+ box-sizing: border-box;
306
+ }
307
+ html, body {
308
+ width: ${width}px;
309
+ height: ${height}px;
310
+ overflow: hidden;
311
+ }
312
+ body {
313
+ background: ${background};
314
+ display: flex;
315
+ align-items: center;
316
+ justify-content: center;
317
+ }
318
+ svg {
319
+ width: 100%;
320
+ height: 100%;
321
+ display: block;
322
+ }
323
+ .katex {
324
+ font-size: inherit;
325
+ line-height: 1;
326
+ }
327
+ .katex-html {
328
+ white-space: nowrap;
329
+ }
330
+ </style>
331
+ </head>
332
+ <body>
333
+ ${svgContent}
334
+ </body>
335
+ </html>`;
336
+ }
337
+
338
+ // src/export/playwright-exporter.ts
339
+ var PlaywrightExporter = class {
340
+ constructor(options = {}) {
341
+ this.browser = null;
342
+ this.page = null;
343
+ var _a, _b;
344
+ this.options = {
345
+ headless: (_a = options.headless) != null ? _a : true,
346
+ browser: (_b = options.browser) != null ? _b : "chromium"
347
+ };
348
+ }
349
+ /**
350
+ * 启动浏览器
351
+ *
352
+ * @throws 如果 Playwright 未安装
353
+ */
354
+ async launch() {
355
+ if (this.browser) {
356
+ return;
357
+ }
358
+ let playwright;
359
+ try {
360
+ playwright = await import("playwright");
361
+ } catch (e) {
362
+ throw new Error(
363
+ "Playwright is not installed. Please install it with: pnpm add -D playwright"
364
+ );
365
+ }
366
+ const browserType = playwright[this.options.browser];
367
+ if (!browserType) {
368
+ throw new Error(`Unknown browser type: ${this.options.browser}`);
369
+ }
370
+ this.browser = await browserType.launch({
371
+ headless: this.options.headless
372
+ });
373
+ this.page = await this.browser.newPage();
374
+ }
375
+ /**
376
+ * 关闭浏览器
377
+ */
378
+ async close() {
379
+ if (this.page) {
380
+ await this.page.close();
381
+ this.page = null;
382
+ }
383
+ if (this.browser) {
384
+ await this.browser.close();
385
+ this.browser = null;
386
+ }
387
+ }
388
+ /**
389
+ * 获取页面实例,确保已启动
390
+ */
391
+ getPage() {
392
+ if (!this.browser || !this.page) {
393
+ throw new Error("Browser not launched. Call launch() first.");
394
+ }
395
+ return this.page;
396
+ }
397
+ /**
398
+ * 导出 Diagram 为图片
399
+ *
400
+ * @param diagram - 要导出的 Diagram
401
+ * @param options - 导出选项
402
+ * @returns 导出结果
403
+ */
404
+ async exportDiagram(diagram, options = {}) {
405
+ const {
406
+ width = 1920,
407
+ height = 1080,
408
+ frame = true
409
+ } = options;
410
+ const svgString = toSVGString(diagram, {
411
+ width,
412
+ height,
413
+ frame,
414
+ background: "transparent"
415
+ // HTML 背景会处理
416
+ });
417
+ return this.exportSVGString(svgString, options);
418
+ }
419
+ /**
420
+ * 导出 Scene 为图片(静态帧)
421
+ *
422
+ * @param SceneClass - Scene 类
423
+ * @param options - 导出选项
424
+ * @returns 导出结果
425
+ */
426
+ async exportScene(SceneClass, options = {}) {
427
+ const _a = options, { frameTime: _frameTime = 0 } = _a, exportOptions = __objRest(_a, ["frameTime"]);
428
+ const scene = new SceneClass();
429
+ if (typeof scene.construct === "function") {
430
+ if (typeof scene.toSVGString === "function") {
431
+ const svgString = scene.toSVGString();
432
+ return this.exportSVGString(svgString, exportOptions);
433
+ }
434
+ }
435
+ throw new Error(
436
+ "Scene export requires the Scene to have a toSVGString() method. For animated scenes, use browser-based export."
437
+ );
438
+ }
439
+ /**
440
+ * 导出 SVG 字符串为图片
441
+ *
442
+ * @param svgString - SVG 字符串
443
+ * @param options - 导出选项
444
+ * @returns 导出结果
445
+ */
446
+ async exportSVGString(svgString, options = {}) {
447
+ const page = this.getPage();
448
+ const {
449
+ format = "png",
450
+ quality = 90,
451
+ deviceScaleFactor = 2,
452
+ width = 1920,
453
+ height = 1080,
454
+ background = "transparent",
455
+ waitForFonts = true,
456
+ renderDelay = 100
457
+ } = options;
458
+ await page.setViewportSize({
459
+ width,
460
+ height
461
+ });
462
+ await page.emulateMedia({ colorScheme: "light" });
463
+ const html = generateExportHTML(svgString, { width, height, background });
464
+ await page.setContent(html, {
465
+ waitUntil: "networkidle"
466
+ });
467
+ if (waitForFonts) {
468
+ await page.waitForFunction(
469
+ () => document.fonts.ready.then(() => true),
470
+ { timeout: 1e4 }
471
+ ).catch(() => {
472
+ console.warn("Font loading timeout, continuing with available fonts");
473
+ });
474
+ }
475
+ if (renderDelay > 0) {
476
+ await page.waitForTimeout(renderDelay);
477
+ }
478
+ const buffer = await page.screenshot({
479
+ type: format,
480
+ quality: format !== "png" ? quality : void 0,
481
+ fullPage: false,
482
+ omitBackground: background === "transparent",
483
+ scale: "device"
484
+ });
485
+ const actualWidth = width * deviceScaleFactor;
486
+ const actualHeight = height * deviceScaleFactor;
487
+ return {
488
+ buffer: Buffer.from(buffer),
489
+ mimeType: `image/${format}`,
490
+ width: actualWidth,
491
+ height: actualHeight
492
+ };
493
+ }
494
+ };
495
+ async function exportToPNG(diagram, outputPath, options = {}) {
496
+ const fs = await import("fs/promises");
497
+ const exporter = new PlaywrightExporter();
498
+ try {
499
+ await exporter.launch();
500
+ const result = await exporter.exportDiagram(diagram, __spreadProps(__spreadValues({}, options), {
501
+ format: "png"
502
+ }));
503
+ await fs.writeFile(outputPath, result.buffer);
504
+ } finally {
505
+ await exporter.close();
506
+ }
507
+ }
508
+ async function exportSVGToPNG(svgString, outputPath, options = {}) {
509
+ const fs = await import("fs/promises");
510
+ const exporter = new PlaywrightExporter();
511
+ try {
512
+ await exporter.launch();
513
+ const result = await exporter.exportSVGString(svgString, __spreadProps(__spreadValues({}, options), {
514
+ format: "png"
515
+ }));
516
+ await fs.writeFile(outputPath, result.buffer);
517
+ } finally {
518
+ await exporter.close();
519
+ }
520
+ }
521
+ // Annotate the CommonJS export names for ESM import in node:
522
+ 0 && (module.exports = {
523
+ PlaywrightExporter,
524
+ exportSVGToPNG,
525
+ exportToPNG,
526
+ generateExportHTML,
527
+ generateExportHTMLWithInlineCSS
528
+ });