@glyphcss/compile 0.0.9
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/LICENSE +21 -0
- package/README.md +125 -0
- package/SKILL.md +60 -0
- package/dist/cli.cjs +473 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +458 -0
- package/dist/index.cjs +315 -0
- package/dist/index.d.cts +28 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +282 -0
- package/dist/vite-zkmogiPw.d.cts +69 -0
- package/dist/vite-zkmogiPw.d.ts +69 -0
- package/dist/vite.cjs +299 -0
- package/dist/vite.d.cts +3 -0
- package/dist/vite.d.ts +3 -0
- package/dist/vite.js +267 -0
- package/package.json +72 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { writeFile, readFile as readFile3 } from "fs/promises";
|
|
5
|
+
|
|
6
|
+
// src/compileFile.ts
|
|
7
|
+
import {
|
|
8
|
+
compileScene,
|
|
9
|
+
createGlyphPerspectiveCamera,
|
|
10
|
+
createGlyphOrthographicCamera,
|
|
11
|
+
cropGlyphInner
|
|
12
|
+
} from "glyphcss";
|
|
13
|
+
|
|
14
|
+
// src/loadMeshFromFile.ts
|
|
15
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
16
|
+
import { loadMesh } from "@glyphcss/core";
|
|
17
|
+
|
|
18
|
+
// src/textureBakeNode.ts
|
|
19
|
+
import { readFile } from "fs/promises";
|
|
20
|
+
import { PNG } from "pngjs";
|
|
21
|
+
import jpeg from "jpeg-js";
|
|
22
|
+
function stripQuery(url) {
|
|
23
|
+
return url.split("?")[0].split("#")[0];
|
|
24
|
+
}
|
|
25
|
+
function toFilePath(url) {
|
|
26
|
+
const clean = stripQuery(url);
|
|
27
|
+
if (/^https?:\/\//i.test(clean)) {
|
|
28
|
+
try {
|
|
29
|
+
return decodeURIComponent(new URL(clean).pathname);
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (clean.startsWith("file://")) {
|
|
34
|
+
try {
|
|
35
|
+
return decodeURIComponent(new URL(clean).pathname);
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return clean;
|
|
40
|
+
}
|
|
41
|
+
async function decode(path) {
|
|
42
|
+
const fp = toFilePath(path);
|
|
43
|
+
try {
|
|
44
|
+
const buf = await readFile(fp);
|
|
45
|
+
if (/\.png$/i.test(fp)) {
|
|
46
|
+
const p = PNG.sync.read(buf);
|
|
47
|
+
return { w: p.width, h: p.height, data: p.data };
|
|
48
|
+
}
|
|
49
|
+
if (/\.jpe?g$/i.test(fp)) {
|
|
50
|
+
const j = jpeg.decode(buf, { useTArray: true });
|
|
51
|
+
return { w: j.width, h: j.height, data: j.data };
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function texelAt(img, u, v) {
|
|
58
|
+
const x = Math.min(img.w - 1, Math.max(0, Math.round(u * (img.w - 1))));
|
|
59
|
+
const y = Math.min(img.h - 1, Math.max(0, Math.round((1 - v) * (img.h - 1))));
|
|
60
|
+
const i = (y * img.w + x) * 4;
|
|
61
|
+
return [img.data[i], img.data[i + 1], img.data[i + 2]];
|
|
62
|
+
}
|
|
63
|
+
function sampleFace(img, uvs) {
|
|
64
|
+
const pts = [uvs[0], uvs[1], uvs[2], [(uvs[0][0] + uvs[1][0] + uvs[2][0]) / 3, (uvs[0][1] + uvs[1][1] + uvs[2][1]) / 3]];
|
|
65
|
+
let r = 0, g = 0, b = 0;
|
|
66
|
+
for (const [u, v] of pts) {
|
|
67
|
+
const c = texelAt(img, u, v);
|
|
68
|
+
r += c[0];
|
|
69
|
+
g += c[1];
|
|
70
|
+
b += c[2];
|
|
71
|
+
}
|
|
72
|
+
const n = pts.length;
|
|
73
|
+
const h = (x) => Math.round(x / n).toString(16).padStart(2, "0");
|
|
74
|
+
return `#${h(r)}${h(g)}${h(b)}`;
|
|
75
|
+
}
|
|
76
|
+
async function bakeTexturesNode(polygons) {
|
|
77
|
+
const cache = /* @__PURE__ */ new Map();
|
|
78
|
+
const get = async (url) => {
|
|
79
|
+
if (!cache.has(url)) cache.set(url, await decode(url));
|
|
80
|
+
return cache.get(url) ?? null;
|
|
81
|
+
};
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const p of polygons) {
|
|
84
|
+
const tt = p.textureTriangles?.[0];
|
|
85
|
+
const tex = p.texture ?? p.material?.texture ?? tt?.texture;
|
|
86
|
+
const uvs = tt?.uvs ?? (p.uvs && p.uvs.length >= 3 ? [p.uvs[0], p.uvs[1], p.uvs[2]] : void 0);
|
|
87
|
+
if (tex && uvs) {
|
|
88
|
+
const img = await get(tex);
|
|
89
|
+
if (img) {
|
|
90
|
+
out.push({ ...p, color: sampleFace(img, uvs), texture: void 0, textureTriangles: void 0, uvs: void 0 });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
out.push(p);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function hasTextures(polygons) {
|
|
99
|
+
return polygons.some((p) => p.texture || p.material?.texture || p.textureTriangles?.[0]?.texture);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/loadMeshFromFile.ts
|
|
103
|
+
function stripQuery2(url) {
|
|
104
|
+
return url.split("?")[0].split("#")[0];
|
|
105
|
+
}
|
|
106
|
+
async function siblingMtl(objPath) {
|
|
107
|
+
const clean = stripQuery2(objPath);
|
|
108
|
+
const dir = clean.replace(/[^/\\]+$/, "");
|
|
109
|
+
let candidate;
|
|
110
|
+
try {
|
|
111
|
+
const m = (await readFile2(clean, "utf8")).match(/^\s*mtllib\s+(.+?)\s*$/im);
|
|
112
|
+
candidate = m ? dir + m[1].trim() : clean.replace(/\.obj$/i, ".mtl");
|
|
113
|
+
} catch {
|
|
114
|
+
candidate = clean.replace(/\.obj$/i, ".mtl");
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
await readFile2(candidate);
|
|
118
|
+
return candidate;
|
|
119
|
+
} catch {
|
|
120
|
+
return void 0;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function fileFetch(url) {
|
|
124
|
+
const path = stripQuery2(url);
|
|
125
|
+
try {
|
|
126
|
+
const buf = await readFile2(path);
|
|
127
|
+
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
128
|
+
return {
|
|
129
|
+
ok: true,
|
|
130
|
+
status: 200,
|
|
131
|
+
text: async () => buf.toString("utf8"),
|
|
132
|
+
arrayBuffer: async () => ab
|
|
133
|
+
};
|
|
134
|
+
} catch {
|
|
135
|
+
return { ok: false, status: 404, text: async () => "", arrayBuffer: async () => new ArrayBuffer(0) };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function loadMeshFromFile(path, options) {
|
|
139
|
+
const g = globalThis;
|
|
140
|
+
const prev = g.fetch;
|
|
141
|
+
g.fetch = fileFetch;
|
|
142
|
+
let result;
|
|
143
|
+
try {
|
|
144
|
+
const mtlUrl = options?.mtlUrl ?? (/\.obj(\?|$)/i.test(path) ? await siblingMtl(path) : void 0);
|
|
145
|
+
result = await loadMesh(path, { solidTextureSamples: false, ...options, mtlUrl });
|
|
146
|
+
} finally {
|
|
147
|
+
g.fetch = prev;
|
|
148
|
+
}
|
|
149
|
+
if (hasTextures(result.polygons)) {
|
|
150
|
+
return { ...result, polygons: await bakeTexturesNode(result.polygons) };
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/compileFile.ts
|
|
156
|
+
function worldMaxDim(polys) {
|
|
157
|
+
let mnx = Infinity, mxx = -Infinity, mny = Infinity, mxy = -Infinity, mnz = Infinity, mxz = -Infinity;
|
|
158
|
+
for (const p of polys) for (const v of p.vertices) {
|
|
159
|
+
if (v[0] < mnx) mnx = v[0];
|
|
160
|
+
if (v[0] > mxx) mxx = v[0];
|
|
161
|
+
if (v[1] < mny) mny = v[1];
|
|
162
|
+
if (v[1] > mxy) mxy = v[1];
|
|
163
|
+
if (v[2] < mnz) mnz = v[2];
|
|
164
|
+
if (v[2] > mxz) mxz = v[2];
|
|
165
|
+
}
|
|
166
|
+
if (!isFinite(mnx)) return 1;
|
|
167
|
+
return Math.max(mxx - mnx, mxy - mny, mxz - mnz, 1e-6);
|
|
168
|
+
}
|
|
169
|
+
function measureContent(inner) {
|
|
170
|
+
const lines = inner.replace(/<[^>]*>/g, "").split("\n");
|
|
171
|
+
let minC = Infinity, maxC = -1, minR = Infinity, maxR = -1;
|
|
172
|
+
lines.forEach((l, r) => {
|
|
173
|
+
for (let c = 0; c < l.length; c++) if (l[c] !== " ") {
|
|
174
|
+
if (c < minC) minC = c;
|
|
175
|
+
if (c > maxC) maxC = c;
|
|
176
|
+
if (r < minR) minR = r;
|
|
177
|
+
if (r > maxR) maxR = r;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return maxC < 0 ? { w: 0, h: 0 } : { w: maxC - minC + 1, h: maxR - minR + 1 };
|
|
181
|
+
}
|
|
182
|
+
function compilePolygons(polygons, options = {}) {
|
|
183
|
+
const buildCam = (zoom) => options.projection === "orthographic" ? createGlyphOrthographicCamera({ rotX: options.rotX, rotY: options.rotY, zoom }) : createGlyphPerspectiveCamera({ rotX: options.rotX, rotY: options.rotY, zoom, distance: options.distance, perspective: options.perspective });
|
|
184
|
+
const shared = {
|
|
185
|
+
autoCenter: options.autoCenter,
|
|
186
|
+
cellAspect: options.cellAspect,
|
|
187
|
+
mode: options.mode,
|
|
188
|
+
glyphPalette: options.glyphPalette,
|
|
189
|
+
useColors: options.useColors,
|
|
190
|
+
smoothShading: options.smoothShading,
|
|
191
|
+
creaseAngle: options.creaseAngle,
|
|
192
|
+
doubleSided: options.doubleSided,
|
|
193
|
+
supersample: options.supersample
|
|
194
|
+
};
|
|
195
|
+
if (options.autoFit && options.autoFit.target > 0) {
|
|
196
|
+
const { target, by } = options.autoFit;
|
|
197
|
+
const probeZoom = 40 / worldMaxDim(polygons);
|
|
198
|
+
const probe = compileScene({ polygons, camera: buildCam(probeZoom), cols: 200, rows: 120, ...shared, autoCenter: true });
|
|
199
|
+
const m = measureContent(probe.inner);
|
|
200
|
+
if (m.w > 0 && m.h > 0) {
|
|
201
|
+
const scale = by === "rows" ? target / m.h : target / m.w;
|
|
202
|
+
const zoom = probeZoom * scale;
|
|
203
|
+
const cols = Math.ceil(m.w * scale * 1.4) + 6;
|
|
204
|
+
const rows = Math.ceil(m.h * scale * 1.4) + 6;
|
|
205
|
+
const full = compileScene({ polygons, camera: buildCam(zoom), cols, rows, ...shared, autoCenter: true });
|
|
206
|
+
const inner = cropGlyphInner(full.inner);
|
|
207
|
+
const lines = inner.split("\n");
|
|
208
|
+
const w = lines.reduce((a, l) => Math.max(a, l.replace(/<[^>]*>/g, "").length), 0);
|
|
209
|
+
return { html: `<pre class="glyph-output">${inner}</pre>`, inner, cols: w, rows: lines.length, cellAspect: options.cellAspect ?? 2 };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return compileScene({
|
|
213
|
+
polygons,
|
|
214
|
+
camera: buildCam(options.zoom),
|
|
215
|
+
cols: options.cols,
|
|
216
|
+
rows: options.rows,
|
|
217
|
+
...shared
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async function compileFile(path, options = {}) {
|
|
221
|
+
const { polygons } = await loadMeshFromFile(path, { meshResolution: options.meshResolution, mtlUrl: options.mtlUrl });
|
|
222
|
+
return compilePolygons(polygons, options);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/compileInteractive.ts
|
|
226
|
+
import {
|
|
227
|
+
buildGlyphInteractiveExport,
|
|
228
|
+
glyphCodepenPrefill
|
|
229
|
+
} from "glyphcss";
|
|
230
|
+
async function compileInteractive(path, options = {}) {
|
|
231
|
+
const { polygons } = await loadMeshFromFile(path, { meshResolution: options.meshResolution, mtlUrl: options.mtlUrl });
|
|
232
|
+
return buildGlyphInteractiveExport(polygons, options);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/cli.ts
|
|
236
|
+
import { encodeGlyphAnsi } from "glyphcss";
|
|
237
|
+
import { resolveGeometry } from "@glyphcss/core";
|
|
238
|
+
var MESH_EXT = /\.(obj|glb|gltf|vox|stl)$/i;
|
|
239
|
+
function normalizePolygons(data) {
|
|
240
|
+
const arr = Array.isArray(data) ? data : data?.polygons;
|
|
241
|
+
if (!Array.isArray(arr)) throw new Error("polygons JSON must be an array or { polygons: [...] }");
|
|
242
|
+
return arr.map((p, i) => {
|
|
243
|
+
const vertices = p.vertices ?? p.v;
|
|
244
|
+
if (!Array.isArray(vertices) || vertices.length < 3) throw new Error(`polygon ${i}: needs vertices [[x,y,z], \u2026] (>= 3)`);
|
|
245
|
+
return { vertices, color: p.color ?? p.c };
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function parseArgs(argv) {
|
|
249
|
+
const opts = {};
|
|
250
|
+
let file;
|
|
251
|
+
let out;
|
|
252
|
+
let format;
|
|
253
|
+
let fit;
|
|
254
|
+
let shape;
|
|
255
|
+
let polygonsFile;
|
|
256
|
+
let polygonsJson;
|
|
257
|
+
let interactive = false;
|
|
258
|
+
let interactions;
|
|
259
|
+
let decimateGrid;
|
|
260
|
+
let cdnVersion;
|
|
261
|
+
const num = (v) => {
|
|
262
|
+
const n = Number(v);
|
|
263
|
+
return v !== void 0 && Number.isFinite(n) ? n : void 0;
|
|
264
|
+
};
|
|
265
|
+
for (let i = 0; i < argv.length; i++) {
|
|
266
|
+
const a = argv[i];
|
|
267
|
+
const next = () => argv[++i];
|
|
268
|
+
switch (a) {
|
|
269
|
+
case "--rot-x":
|
|
270
|
+
opts.rotX = num(next());
|
|
271
|
+
break;
|
|
272
|
+
case "--rot-y":
|
|
273
|
+
opts.rotY = num(next());
|
|
274
|
+
break;
|
|
275
|
+
case "--zoom":
|
|
276
|
+
opts.zoom = num(next());
|
|
277
|
+
break;
|
|
278
|
+
case "--distance":
|
|
279
|
+
opts.distance = num(next());
|
|
280
|
+
break;
|
|
281
|
+
case "--perspective":
|
|
282
|
+
opts.perspective = num(next());
|
|
283
|
+
break;
|
|
284
|
+
case "--cols":
|
|
285
|
+
opts.cols = num(next());
|
|
286
|
+
break;
|
|
287
|
+
case "--rows":
|
|
288
|
+
opts.rows = num(next());
|
|
289
|
+
break;
|
|
290
|
+
case "--cell-aspect":
|
|
291
|
+
opts.cellAspect = num(next());
|
|
292
|
+
break;
|
|
293
|
+
case "--crease-angle":
|
|
294
|
+
opts.creaseAngle = num(next());
|
|
295
|
+
break;
|
|
296
|
+
case "--supersample":
|
|
297
|
+
opts.supersample = num(next());
|
|
298
|
+
break;
|
|
299
|
+
case "--mode":
|
|
300
|
+
opts.mode = next();
|
|
301
|
+
break;
|
|
302
|
+
case "--palette":
|
|
303
|
+
opts.glyphPalette = next();
|
|
304
|
+
break;
|
|
305
|
+
case "--mesh-resolution":
|
|
306
|
+
opts.meshResolution = next();
|
|
307
|
+
break;
|
|
308
|
+
case "--mtl":
|
|
309
|
+
opts.mtlUrl = next();
|
|
310
|
+
break;
|
|
311
|
+
// Geometry input (instead of a mesh file).
|
|
312
|
+
case "--shape":
|
|
313
|
+
shape = next();
|
|
314
|
+
break;
|
|
315
|
+
case "--polygons":
|
|
316
|
+
polygonsFile = next();
|
|
317
|
+
break;
|
|
318
|
+
case "--polygons-json":
|
|
319
|
+
polygonsJson = next();
|
|
320
|
+
break;
|
|
321
|
+
case "--ortho":
|
|
322
|
+
opts.projection = "orthographic";
|
|
323
|
+
break;
|
|
324
|
+
case "--auto-center":
|
|
325
|
+
opts.autoCenter = true;
|
|
326
|
+
break;
|
|
327
|
+
case "--smooth":
|
|
328
|
+
opts.smoothShading = true;
|
|
329
|
+
break;
|
|
330
|
+
case "--double-sided":
|
|
331
|
+
opts.doubleSided = true;
|
|
332
|
+
break;
|
|
333
|
+
case "--fit":
|
|
334
|
+
fit = num(next());
|
|
335
|
+
break;
|
|
336
|
+
// Output format (+ back-compat aliases).
|
|
337
|
+
case "-f":
|
|
338
|
+
case "--format":
|
|
339
|
+
format = next();
|
|
340
|
+
break;
|
|
341
|
+
case "--ansi":
|
|
342
|
+
format = "ansi";
|
|
343
|
+
break;
|
|
344
|
+
case "--no-colors":
|
|
345
|
+
case "--text":
|
|
346
|
+
format = "text";
|
|
347
|
+
break;
|
|
348
|
+
case "--pre":
|
|
349
|
+
case "--html":
|
|
350
|
+
format = "html";
|
|
351
|
+
break;
|
|
352
|
+
case "--full":
|
|
353
|
+
format = "full";
|
|
354
|
+
break;
|
|
355
|
+
case "--interactive":
|
|
356
|
+
interactive = true;
|
|
357
|
+
break;
|
|
358
|
+
case "--interactions":
|
|
359
|
+
interactive = true;
|
|
360
|
+
interactions = (next() ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
361
|
+
break;
|
|
362
|
+
case "--decimate-grid":
|
|
363
|
+
decimateGrid = num(next());
|
|
364
|
+
break;
|
|
365
|
+
case "--cdn-version":
|
|
366
|
+
cdnVersion = next();
|
|
367
|
+
break;
|
|
368
|
+
case "-o":
|
|
369
|
+
case "--out":
|
|
370
|
+
out = next();
|
|
371
|
+
break;
|
|
372
|
+
case "-h":
|
|
373
|
+
case "--help":
|
|
374
|
+
file = void 0;
|
|
375
|
+
return { file, out, format, fit, shape, polygonsFile, polygonsJson, interactive, interactions, decimateGrid, cdnVersion, opts };
|
|
376
|
+
default:
|
|
377
|
+
if (!a.startsWith("-") && !file) file = a;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return { file, out, format, fit, shape, polygonsFile, polygonsJson, interactive, interactions, decimateGrid, cdnVersion, opts };
|
|
381
|
+
}
|
|
382
|
+
var HELP = `glyphcss <mesh-file | shape> [options]
|
|
383
|
+
|
|
384
|
+
Input a mesh file (.obj/.glb/.gltf/.vox/.stl), a primitive shape name
|
|
385
|
+
(e.g. cube, sphere, icosahedron, torus, cone), or:
|
|
386
|
+
--shape NAME --polygons FILE.json --polygons-json '[{"vertices":[...],"color":"#f00"}]'
|
|
387
|
+
Camera --rot-x N --rot-y N --zoom N --distance N --perspective N --ortho
|
|
388
|
+
Grid --cols N --rows N --cell-aspect N (omit cols/rows \u2192 auto-fit to content)
|
|
389
|
+
Fit --fit N target width in columns for auto-fit (default: terminal width or 80)
|
|
390
|
+
Render --mode solid|wireframe|voxel --palette NAME --no-colors --smooth --double-sided
|
|
391
|
+
Mesh --auto-center --mtl FILE (OBJ material override) --mesh-resolution lossy|lossless --crease-angle N --supersample N
|
|
392
|
+
Interact --interactive --interactions orbit,zoom,pan,fpv --decimate-grid N --cdn-version V
|
|
393
|
+
Output -f, --format text|ansi|html|full (default: terminal \u2192 ansi, file \u2192 html, pipe \u2192 text)
|
|
394
|
+
-o, --out FILE
|
|
395
|
+
`;
|
|
396
|
+
function wrapHtml(inner) {
|
|
397
|
+
return `<!doctype html>
|
|
398
|
+
<html><head><meta charset="utf-8"><title>glyphcss</title>
|
|
399
|
+
<style>
|
|
400
|
+
html,body{margin:0;background:#0b0d10;color:#e2e8f0}
|
|
401
|
+
pre.glyph-output{margin:0;font:13px/1 ui-monospace,"SF Mono",Menlo,monospace;white-space:pre}
|
|
402
|
+
</style></head>
|
|
403
|
+
<body>${inner}</body></html>`;
|
|
404
|
+
}
|
|
405
|
+
async function main() {
|
|
406
|
+
const { file, out, format: fmtArg, fit, shape, polygonsFile, polygonsJson, interactive, interactions, decimateGrid, cdnVersion, opts } = parseArgs(process.argv.slice(2));
|
|
407
|
+
const positionalShape = file && !MESH_EXT.test(file) ? file : void 0;
|
|
408
|
+
const meshFile = file && MESH_EXT.test(file) ? file : void 0;
|
|
409
|
+
const shapeName = shape ?? positionalShape;
|
|
410
|
+
if (!meshFile && !shapeName && !polygonsFile && !polygonsJson) {
|
|
411
|
+
process.stderr.write(HELP);
|
|
412
|
+
process.exit(process.argv.length <= 2 ? 1 : 0);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const format = fmtArg ?? (out ? "html" : process.stdout.isTTY ? "ansi" : "text");
|
|
416
|
+
if (interactive) {
|
|
417
|
+
if (!meshFile) throw new Error("--interactive needs a mesh file (shapes/polygons are static-only for now)");
|
|
418
|
+
const r = await compileInteractive(meshFile, { ...opts, interactions, decimateGrid, cdnVersion });
|
|
419
|
+
const output2 = format === "full" ? wrapHtml(r.html) : r.html;
|
|
420
|
+
if (out) {
|
|
421
|
+
await writeFile(out, output2, "utf8");
|
|
422
|
+
process.stderr.write(`glyphcss: wrote ${out} \u2014 interactive [${r.interactions.join(", ")}], ${r.polygonCount}/${r.sourcePolygonCount} tris
|
|
423
|
+
`);
|
|
424
|
+
} else {
|
|
425
|
+
process.stdout.write(output2 + "\n");
|
|
426
|
+
}
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
opts.useColors = format !== "text";
|
|
430
|
+
const hasCols = opts.cols !== void 0, hasRows = opts.rows !== void 0;
|
|
431
|
+
const termW = process.stdout.columns ? process.stdout.columns - 1 : 80;
|
|
432
|
+
if (fit !== void 0) opts.autoFit = { target: fit, by: "cols" };
|
|
433
|
+
else if (hasCols && !hasRows) opts.autoFit = { target: opts.cols, by: "cols" };
|
|
434
|
+
else if (hasRows && !hasCols) opts.autoFit = { target: opts.rows, by: "rows" };
|
|
435
|
+
else if (!hasCols && !hasRows) opts.autoFit = { target: termW, by: "cols" };
|
|
436
|
+
if (opts.autoFit) {
|
|
437
|
+
opts.cols = void 0;
|
|
438
|
+
opts.rows = void 0;
|
|
439
|
+
}
|
|
440
|
+
let result;
|
|
441
|
+
if (polygonsJson) result = compilePolygons(normalizePolygons(JSON.parse(polygonsJson)), opts);
|
|
442
|
+
else if (polygonsFile) result = compilePolygons(normalizePolygons(JSON.parse(await readFile3(polygonsFile, "utf8"))), opts);
|
|
443
|
+
else if (shapeName) result = compilePolygons(resolveGeometry(shapeName, { size: 1 }), { ...opts, autoCenter: true });
|
|
444
|
+
else result = await compileFile(meshFile, opts);
|
|
445
|
+
const output = format === "text" ? result.inner : format === "ansi" ? encodeGlyphAnsi(result.inner) : format === "full" ? wrapHtml(result.html) : result.html;
|
|
446
|
+
if (out) {
|
|
447
|
+
await writeFile(out, output, "utf8");
|
|
448
|
+
process.stderr.write(`glyphcss: wrote ${out} (${result.cols}\xD7${result.rows}, ${format})
|
|
449
|
+
`);
|
|
450
|
+
} else {
|
|
451
|
+
process.stdout.write(output + "\n");
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
main().catch((err) => {
|
|
455
|
+
process.stderr.write(`glyphcss: ${err instanceof Error ? err.message : String(err)}
|
|
456
|
+
`);
|
|
457
|
+
process.exit(1);
|
|
458
|
+
});
|