@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.
@@ -0,0 +1,69 @@
1
+ import { CompileSceneResult } from 'glyphcss';
2
+ import { RenderMode, MeshResolution, Polygon } from '@glyphcss/core';
3
+
4
+ interface CompileFileOptions {
5
+ /** Camera projection. Default: "perspective" (the library default). */
6
+ projection?: "perspective" | "orthographic";
7
+ rotX?: number;
8
+ rotY?: number;
9
+ zoom?: number;
10
+ /** Perspective only. */
11
+ distance?: number;
12
+ perspective?: number;
13
+ cols?: number;
14
+ rows?: number;
15
+ cellAspect?: number;
16
+ mode?: RenderMode;
17
+ glyphPalette?: string;
18
+ useColors?: boolean;
19
+ smoothShading?: boolean;
20
+ creaseAngle?: number;
21
+ doubleSided?: boolean;
22
+ supersample?: number;
23
+ /** Recenter the mesh bbox to the origin (matches `<glyph-mesh auto-center>`). Default false. */
24
+ autoCenter?: boolean;
25
+ /**
26
+ * Auto-fit: size the grid + zoom so the whole model shows, cropped tight.
27
+ * `by` is the axis to fit to `target` (the other axis adapts to the content):
28
+ * fit `cols` (width) when you give a column budget, `rows` (height) for a row
29
+ * budget. Implies `autoCenter`.
30
+ */
31
+ autoFit?: {
32
+ target: number;
33
+ by: "cols" | "rows";
34
+ };
35
+ /** Mesh-optimization quality passed to `loadMesh`. Default: the loadMesh default ("lossy"). */
36
+ meshResolution?: MeshResolution;
37
+ /** Explicit companion `.mtl` path for OBJ (overrides sibling auto-detection). */
38
+ mtlUrl?: string;
39
+ }
40
+ /**
41
+ * compilePolygons — render polygons you already have (a primitive, custom JSON,
42
+ * etc.) to a `<pre>`, with the same camera / auto-fit logic as `compileFile`.
43
+ * Pure + synchronous (no file I/O).
44
+ */
45
+ declare function compilePolygons(polygons: Polygon[], options?: CompileFileOptions): CompileSceneResult;
46
+ declare function compileFile(path: string, options?: CompileFileOptions): Promise<CompileSceneResult>;
47
+
48
+ /**
49
+ * glyphcssCompile — a Vite plugin that compiles a mesh import to its static
50
+ * `<pre>` ASCII **at build time**:
51
+ *
52
+ * import duck from "./duck.glb?glyph&rotX=60&rotY=45&autoCenter=1";
53
+ * el.innerHTML = duck; // the <pre>…ascii…</pre> string, zero runtime
54
+ *
55
+ * Query params map to `CompileFileOptions` (rotX, rotY, zoom, distance,
56
+ * perspective, cols, rows, cellAspect, mode, palette, colors=0|1, autoCenter,
57
+ * projection, meshResolution). Works in any Vite-based pipeline (Astro, vanilla
58
+ * Vite, Vite-React). Defaults match the glyphcss library.
59
+ */
60
+
61
+ interface VitePluginLike {
62
+ name: string;
63
+ enforce?: "pre" | "post";
64
+ load(this: unknown, id: string): Promise<string | null> | string | null;
65
+ }
66
+ type GlyphCompileOptions = Partial<CompileFileOptions>;
67
+ declare function glyphcssCompile(globalOptions?: GlyphCompileOptions): VitePluginLike;
68
+
69
+ export { type CompileFileOptions as C, type GlyphCompileOptions as G, type VitePluginLike as V, compilePolygons as a, compileFile as c, glyphcssCompile as g };
@@ -0,0 +1,69 @@
1
+ import { CompileSceneResult } from 'glyphcss';
2
+ import { RenderMode, MeshResolution, Polygon } from '@glyphcss/core';
3
+
4
+ interface CompileFileOptions {
5
+ /** Camera projection. Default: "perspective" (the library default). */
6
+ projection?: "perspective" | "orthographic";
7
+ rotX?: number;
8
+ rotY?: number;
9
+ zoom?: number;
10
+ /** Perspective only. */
11
+ distance?: number;
12
+ perspective?: number;
13
+ cols?: number;
14
+ rows?: number;
15
+ cellAspect?: number;
16
+ mode?: RenderMode;
17
+ glyphPalette?: string;
18
+ useColors?: boolean;
19
+ smoothShading?: boolean;
20
+ creaseAngle?: number;
21
+ doubleSided?: boolean;
22
+ supersample?: number;
23
+ /** Recenter the mesh bbox to the origin (matches `<glyph-mesh auto-center>`). Default false. */
24
+ autoCenter?: boolean;
25
+ /**
26
+ * Auto-fit: size the grid + zoom so the whole model shows, cropped tight.
27
+ * `by` is the axis to fit to `target` (the other axis adapts to the content):
28
+ * fit `cols` (width) when you give a column budget, `rows` (height) for a row
29
+ * budget. Implies `autoCenter`.
30
+ */
31
+ autoFit?: {
32
+ target: number;
33
+ by: "cols" | "rows";
34
+ };
35
+ /** Mesh-optimization quality passed to `loadMesh`. Default: the loadMesh default ("lossy"). */
36
+ meshResolution?: MeshResolution;
37
+ /** Explicit companion `.mtl` path for OBJ (overrides sibling auto-detection). */
38
+ mtlUrl?: string;
39
+ }
40
+ /**
41
+ * compilePolygons — render polygons you already have (a primitive, custom JSON,
42
+ * etc.) to a `<pre>`, with the same camera / auto-fit logic as `compileFile`.
43
+ * Pure + synchronous (no file I/O).
44
+ */
45
+ declare function compilePolygons(polygons: Polygon[], options?: CompileFileOptions): CompileSceneResult;
46
+ declare function compileFile(path: string, options?: CompileFileOptions): Promise<CompileSceneResult>;
47
+
48
+ /**
49
+ * glyphcssCompile — a Vite plugin that compiles a mesh import to its static
50
+ * `<pre>` ASCII **at build time**:
51
+ *
52
+ * import duck from "./duck.glb?glyph&rotX=60&rotY=45&autoCenter=1";
53
+ * el.innerHTML = duck; // the <pre>…ascii…</pre> string, zero runtime
54
+ *
55
+ * Query params map to `CompileFileOptions` (rotX, rotY, zoom, distance,
56
+ * perspective, cols, rows, cellAspect, mode, palette, colors=0|1, autoCenter,
57
+ * projection, meshResolution). Works in any Vite-based pipeline (Astro, vanilla
58
+ * Vite, Vite-React). Defaults match the glyphcss library.
59
+ */
60
+
61
+ interface VitePluginLike {
62
+ name: string;
63
+ enforce?: "pre" | "post";
64
+ load(this: unknown, id: string): Promise<string | null> | string | null;
65
+ }
66
+ type GlyphCompileOptions = Partial<CompileFileOptions>;
67
+ declare function glyphcssCompile(globalOptions?: GlyphCompileOptions): VitePluginLike;
68
+
69
+ export { type CompileFileOptions as C, type GlyphCompileOptions as G, type VitePluginLike as V, compilePolygons as a, compileFile as c, glyphcssCompile as g };
package/dist/vite.cjs ADDED
@@ -0,0 +1,299 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/vite.ts
31
+ var vite_exports = {};
32
+ __export(vite_exports, {
33
+ default: () => vite_default,
34
+ glyphcssCompile: () => glyphcssCompile
35
+ });
36
+ module.exports = __toCommonJS(vite_exports);
37
+
38
+ // src/compileFile.ts
39
+ var import_glyphcss = require("glyphcss");
40
+
41
+ // src/loadMeshFromFile.ts
42
+ var import_promises2 = require("fs/promises");
43
+ var import_core = require("@glyphcss/core");
44
+
45
+ // src/textureBakeNode.ts
46
+ var import_promises = require("fs/promises");
47
+ var import_pngjs = require("pngjs");
48
+ var import_jpeg_js = __toESM(require("jpeg-js"), 1);
49
+ function stripQuery(url) {
50
+ return url.split("?")[0].split("#")[0];
51
+ }
52
+ function toFilePath(url) {
53
+ const clean = stripQuery(url);
54
+ if (/^https?:\/\//i.test(clean)) {
55
+ try {
56
+ return decodeURIComponent(new URL(clean).pathname);
57
+ } catch {
58
+ }
59
+ }
60
+ if (clean.startsWith("file://")) {
61
+ try {
62
+ return decodeURIComponent(new URL(clean).pathname);
63
+ } catch {
64
+ }
65
+ }
66
+ return clean;
67
+ }
68
+ async function decode(path) {
69
+ const fp = toFilePath(path);
70
+ try {
71
+ const buf = await (0, import_promises.readFile)(fp);
72
+ if (/\.png$/i.test(fp)) {
73
+ const p = import_pngjs.PNG.sync.read(buf);
74
+ return { w: p.width, h: p.height, data: p.data };
75
+ }
76
+ if (/\.jpe?g$/i.test(fp)) {
77
+ const j = import_jpeg_js.default.decode(buf, { useTArray: true });
78
+ return { w: j.width, h: j.height, data: j.data };
79
+ }
80
+ } catch {
81
+ }
82
+ return null;
83
+ }
84
+ function texelAt(img, u, v) {
85
+ const x = Math.min(img.w - 1, Math.max(0, Math.round(u * (img.w - 1))));
86
+ const y = Math.min(img.h - 1, Math.max(0, Math.round((1 - v) * (img.h - 1))));
87
+ const i = (y * img.w + x) * 4;
88
+ return [img.data[i], img.data[i + 1], img.data[i + 2]];
89
+ }
90
+ function sampleFace(img, uvs) {
91
+ 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]];
92
+ let r = 0, g = 0, b = 0;
93
+ for (const [u, v] of pts) {
94
+ const c = texelAt(img, u, v);
95
+ r += c[0];
96
+ g += c[1];
97
+ b += c[2];
98
+ }
99
+ const n = pts.length;
100
+ const h = (x) => Math.round(x / n).toString(16).padStart(2, "0");
101
+ return `#${h(r)}${h(g)}${h(b)}`;
102
+ }
103
+ async function bakeTexturesNode(polygons) {
104
+ const cache = /* @__PURE__ */ new Map();
105
+ const get = async (url) => {
106
+ if (!cache.has(url)) cache.set(url, await decode(url));
107
+ return cache.get(url) ?? null;
108
+ };
109
+ const out = [];
110
+ for (const p of polygons) {
111
+ const tt = p.textureTriangles?.[0];
112
+ const tex = p.texture ?? p.material?.texture ?? tt?.texture;
113
+ const uvs = tt?.uvs ?? (p.uvs && p.uvs.length >= 3 ? [p.uvs[0], p.uvs[1], p.uvs[2]] : void 0);
114
+ if (tex && uvs) {
115
+ const img = await get(tex);
116
+ if (img) {
117
+ out.push({ ...p, color: sampleFace(img, uvs), texture: void 0, textureTriangles: void 0, uvs: void 0 });
118
+ continue;
119
+ }
120
+ }
121
+ out.push(p);
122
+ }
123
+ return out;
124
+ }
125
+ function hasTextures(polygons) {
126
+ return polygons.some((p) => p.texture || p.material?.texture || p.textureTriangles?.[0]?.texture);
127
+ }
128
+
129
+ // src/loadMeshFromFile.ts
130
+ function stripQuery2(url) {
131
+ return url.split("?")[0].split("#")[0];
132
+ }
133
+ async function siblingMtl(objPath) {
134
+ const clean = stripQuery2(objPath);
135
+ const dir = clean.replace(/[^/\\]+$/, "");
136
+ let candidate;
137
+ try {
138
+ const m = (await (0, import_promises2.readFile)(clean, "utf8")).match(/^\s*mtllib\s+(.+?)\s*$/im);
139
+ candidate = m ? dir + m[1].trim() : clean.replace(/\.obj$/i, ".mtl");
140
+ } catch {
141
+ candidate = clean.replace(/\.obj$/i, ".mtl");
142
+ }
143
+ try {
144
+ await (0, import_promises2.readFile)(candidate);
145
+ return candidate;
146
+ } catch {
147
+ return void 0;
148
+ }
149
+ }
150
+ async function fileFetch(url) {
151
+ const path = stripQuery2(url);
152
+ try {
153
+ const buf = await (0, import_promises2.readFile)(path);
154
+ const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
155
+ return {
156
+ ok: true,
157
+ status: 200,
158
+ text: async () => buf.toString("utf8"),
159
+ arrayBuffer: async () => ab
160
+ };
161
+ } catch {
162
+ return { ok: false, status: 404, text: async () => "", arrayBuffer: async () => new ArrayBuffer(0) };
163
+ }
164
+ }
165
+ async function loadMeshFromFile(path, options) {
166
+ const g = globalThis;
167
+ const prev = g.fetch;
168
+ g.fetch = fileFetch;
169
+ let result;
170
+ try {
171
+ const mtlUrl = options?.mtlUrl ?? (/\.obj(\?|$)/i.test(path) ? await siblingMtl(path) : void 0);
172
+ result = await (0, import_core.loadMesh)(path, { solidTextureSamples: false, ...options, mtlUrl });
173
+ } finally {
174
+ g.fetch = prev;
175
+ }
176
+ if (hasTextures(result.polygons)) {
177
+ return { ...result, polygons: await bakeTexturesNode(result.polygons) };
178
+ }
179
+ return result;
180
+ }
181
+
182
+ // src/compileFile.ts
183
+ function worldMaxDim(polys) {
184
+ let mnx = Infinity, mxx = -Infinity, mny = Infinity, mxy = -Infinity, mnz = Infinity, mxz = -Infinity;
185
+ for (const p of polys) for (const v of p.vertices) {
186
+ if (v[0] < mnx) mnx = v[0];
187
+ if (v[0] > mxx) mxx = v[0];
188
+ if (v[1] < mny) mny = v[1];
189
+ if (v[1] > mxy) mxy = v[1];
190
+ if (v[2] < mnz) mnz = v[2];
191
+ if (v[2] > mxz) mxz = v[2];
192
+ }
193
+ if (!isFinite(mnx)) return 1;
194
+ return Math.max(mxx - mnx, mxy - mny, mxz - mnz, 1e-6);
195
+ }
196
+ function measureContent(inner) {
197
+ const lines = inner.replace(/<[^>]*>/g, "").split("\n");
198
+ let minC = Infinity, maxC = -1, minR = Infinity, maxR = -1;
199
+ lines.forEach((l, r) => {
200
+ for (let c = 0; c < l.length; c++) if (l[c] !== " ") {
201
+ if (c < minC) minC = c;
202
+ if (c > maxC) maxC = c;
203
+ if (r < minR) minR = r;
204
+ if (r > maxR) maxR = r;
205
+ }
206
+ });
207
+ return maxC < 0 ? { w: 0, h: 0 } : { w: maxC - minC + 1, h: maxR - minR + 1 };
208
+ }
209
+ function compilePolygons(polygons, options = {}) {
210
+ const buildCam = (zoom) => options.projection === "orthographic" ? (0, import_glyphcss.createGlyphOrthographicCamera)({ rotX: options.rotX, rotY: options.rotY, zoom }) : (0, import_glyphcss.createGlyphPerspectiveCamera)({ rotX: options.rotX, rotY: options.rotY, zoom, distance: options.distance, perspective: options.perspective });
211
+ const shared = {
212
+ autoCenter: options.autoCenter,
213
+ cellAspect: options.cellAspect,
214
+ mode: options.mode,
215
+ glyphPalette: options.glyphPalette,
216
+ useColors: options.useColors,
217
+ smoothShading: options.smoothShading,
218
+ creaseAngle: options.creaseAngle,
219
+ doubleSided: options.doubleSided,
220
+ supersample: options.supersample
221
+ };
222
+ if (options.autoFit && options.autoFit.target > 0) {
223
+ const { target, by } = options.autoFit;
224
+ const probeZoom = 40 / worldMaxDim(polygons);
225
+ const probe = (0, import_glyphcss.compileScene)({ polygons, camera: buildCam(probeZoom), cols: 200, rows: 120, ...shared, autoCenter: true });
226
+ const m = measureContent(probe.inner);
227
+ if (m.w > 0 && m.h > 0) {
228
+ const scale = by === "rows" ? target / m.h : target / m.w;
229
+ const zoom = probeZoom * scale;
230
+ const cols = Math.ceil(m.w * scale * 1.4) + 6;
231
+ const rows = Math.ceil(m.h * scale * 1.4) + 6;
232
+ const full = (0, import_glyphcss.compileScene)({ polygons, camera: buildCam(zoom), cols, rows, ...shared, autoCenter: true });
233
+ const inner = (0, import_glyphcss.cropGlyphInner)(full.inner);
234
+ const lines = inner.split("\n");
235
+ const w = lines.reduce((a, l) => Math.max(a, l.replace(/<[^>]*>/g, "").length), 0);
236
+ return { html: `<pre class="glyph-output">${inner}</pre>`, inner, cols: w, rows: lines.length, cellAspect: options.cellAspect ?? 2 };
237
+ }
238
+ }
239
+ return (0, import_glyphcss.compileScene)({
240
+ polygons,
241
+ camera: buildCam(options.zoom),
242
+ cols: options.cols,
243
+ rows: options.rows,
244
+ ...shared
245
+ });
246
+ }
247
+ async function compileFile(path, options = {}) {
248
+ const { polygons } = await loadMeshFromFile(path, { meshResolution: options.meshResolution, mtlUrl: options.mtlUrl });
249
+ return compilePolygons(polygons, options);
250
+ }
251
+
252
+ // src/vite.ts
253
+ var MESH_RE = /\.(glb|gltf|obj|vox|stl)(\?|$)/i;
254
+ function hasGlyphFlag(id) {
255
+ const q = id.split("?")[1];
256
+ return q != null && new URLSearchParams(q).has("glyph");
257
+ }
258
+ function parseQuery(id) {
259
+ const q = id.split("?")[1] ?? "";
260
+ const p = new URLSearchParams(q);
261
+ const num = (k) => p.has(k) ? Number(p.get(k)) : void 0;
262
+ const out = {};
263
+ for (const k of ["rotX", "rotY", "zoom", "distance", "perspective", "cols", "rows", "cellAspect", "creaseAngle", "supersample"]) {
264
+ const v = num(k);
265
+ if (v !== void 0 && Number.isFinite(v)) out[k] = v;
266
+ }
267
+ if (p.has("mode")) out.mode = p.get("mode");
268
+ if (p.has("palette")) out.glyphPalette = p.get("palette") ?? void 0;
269
+ if (p.has("projection")) out.projection = p.get("projection") === "orthographic" ? "orthographic" : "perspective";
270
+ if (p.has("meshResolution")) out.meshResolution = p.get("meshResolution");
271
+ if (p.has("colors")) out.useColors = p.get("colors") !== "0" && p.get("colors") !== "false";
272
+ if (p.has("autoCenter")) out.autoCenter = p.get("autoCenter") !== "0" && p.get("autoCenter") !== "false";
273
+ if (p.has("smoothShading")) out.smoothShading = p.get("smoothShading") !== "0" && p.get("smoothShading") !== "false";
274
+ if (p.has("doubleSided")) out.doubleSided = p.get("doubleSided") !== "0" && p.get("doubleSided") !== "false";
275
+ return out;
276
+ }
277
+ function glyphcssCompile(globalOptions = {}) {
278
+ return {
279
+ name: "glyphcss-compile",
280
+ enforce: "pre",
281
+ async load(id) {
282
+ if (!MESH_RE.test(id) || !hasGlyphFlag(id)) return null;
283
+ const path = id.split("?")[0];
284
+ const opts = { ...globalOptions, ...parseQuery(id) };
285
+ const result = await compileFile(path, opts);
286
+ const meta = { cols: result.cols, rows: result.rows, cellAspect: result.cellAspect };
287
+ return [
288
+ `export default ${JSON.stringify(result.html)};`,
289
+ `export const inner = ${JSON.stringify(result.inner)};`,
290
+ `export const meta = ${JSON.stringify(meta)};`
291
+ ].join("\n");
292
+ }
293
+ };
294
+ }
295
+ var vite_default = glyphcssCompile;
296
+ // Annotate the CommonJS export names for ESM import in node:
297
+ 0 && (module.exports = {
298
+ glyphcssCompile
299
+ });
@@ -0,0 +1,3 @@
1
+ export { G as GlyphCompileOptions, V as VitePluginLike, g as default, g as glyphcssCompile } from './vite-zkmogiPw.cjs';
2
+ import 'glyphcss';
3
+ import '@glyphcss/core';
package/dist/vite.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { G as GlyphCompileOptions, V as VitePluginLike, g as default, g as glyphcssCompile } from './vite-zkmogiPw.js';
2
+ import 'glyphcss';
3
+ import '@glyphcss/core';