@lofcz/tikz 0.0.0-stage → 0.2.0

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.
Files changed (118) hide show
  1. package/LICENSE +674 -0
  2. package/NOTICE.md +30 -0
  3. package/README.md +127 -2
  4. package/package.json +56 -4
  5. package/runtime/LICENSE +165 -0
  6. package/runtime/NOTICE.md +32 -0
  7. package/runtime/dist/auto.d.ts +68 -0
  8. package/runtime/dist/auto.js +564 -0
  9. package/runtime/dist/bibtex.mjs +2 -0
  10. package/runtime/dist/bibtex.wasm +0 -0
  11. package/runtime/dist/bundles/cm-tfm/files.br +0 -0
  12. package/runtime/dist/bundles/cm-tfm/files.index.json +1 -0
  13. package/runtime/dist/bundles/cm-tfm/manifest.json +1 -0
  14. package/runtime/dist/bundles/cm-type1/files.br +0 -0
  15. package/runtime/dist/bundles/cm-type1/files.index.json +1 -0
  16. package/runtime/dist/bundles/cm-type1/manifest.json +1 -0
  17. package/runtime/dist/bundles/core/files.br +0 -0
  18. package/runtime/dist/bundles/core/files.index.json +1 -0
  19. package/runtime/dist/bundles/core/manifest.json +1 -0
  20. package/runtime/dist/bundles/hot.json +1 -0
  21. package/runtime/dist/bundles/index.json +80 -0
  22. package/runtime/dist/bundles/latex-core/files.br +0 -0
  23. package/runtime/dist/bundles/latex-core/files.index.json +1 -0
  24. package/runtime/dist/bundles/latex-core/manifest.json +1 -0
  25. package/runtime/dist/bundles/latex-extra/files.br +0 -0
  26. package/runtime/dist/bundles/latex-extra/files.index.json +1 -0
  27. package/runtime/dist/bundles/latex-extra/manifest.json +1 -0
  28. package/runtime/dist/bundles/lm-fonts/files.br +0 -0
  29. package/runtime/dist/bundles/lm-fonts/files.index.json +1 -0
  30. package/runtime/dist/bundles/lm-fonts/manifest.json +1 -0
  31. package/runtime/dist/bundles/luatex/files.br +0 -0
  32. package/runtime/dist/bundles/luatex/files.index.json +1 -0
  33. package/runtime/dist/bundles/luatex/manifest.json +1 -0
  34. package/runtime/dist/bundles/ps-fonts/files.br +0 -0
  35. package/runtime/dist/bundles/ps-fonts/files.index.json +1 -0
  36. package/runtime/dist/bundles/ps-fonts/manifest.json +1 -0
  37. package/runtime/dist/bundles/tex-plain/files.br +0 -0
  38. package/runtime/dist/bundles/tex-plain/files.index.json +1 -0
  39. package/runtime/dist/bundles/tex-plain/manifest.json +1 -0
  40. package/runtime/dist/bundles/tikz-snapshot/files.br +0 -0
  41. package/runtime/dist/bundles/tikz-snapshot/files.index.json +1 -0
  42. package/runtime/dist/bundles/tikz-snapshot/manifest.json +1 -0
  43. package/runtime/dist/bundles-config.d.ts +3 -0
  44. package/runtime/dist/bundles-config.js +16 -0
  45. package/runtime/dist/cli.d.ts +2 -0
  46. package/runtime/dist/cli.js +372 -0
  47. package/runtime/dist/core.d.ts +124 -0
  48. package/runtime/dist/core.js +927 -0
  49. package/runtime/dist/diagnostics.d.ts +53 -0
  50. package/runtime/dist/diagnostics.js +184 -0
  51. package/runtime/dist/dvisvgm.d.ts +20 -0
  52. package/runtime/dist/dvisvgm.js +46 -0
  53. package/runtime/dist/dvisvgm.mjs +2 -0
  54. package/runtime/dist/dvisvgm.wasm +0 -0
  55. package/runtime/dist/figures.d.ts +92 -0
  56. package/runtime/dist/figures.js +250 -0
  57. package/runtime/dist/ghostscript.d.ts +92 -0
  58. package/runtime/dist/ghostscript.js +218 -0
  59. package/runtime/dist/index.d.ts +87 -0
  60. package/runtime/dist/index.js +364 -0
  61. package/runtime/dist/logger.d.ts +48 -0
  62. package/runtime/dist/logger.js +55 -0
  63. package/runtime/dist/luatex.mjs +2 -0
  64. package/runtime/dist/luatex.wasm +0 -0
  65. package/runtime/dist/makeindex.mjs +2 -0
  66. package/runtime/dist/makeindex.wasm +0 -0
  67. package/runtime/dist/mplib.d.ts +106 -0
  68. package/runtime/dist/mplib.js +85 -0
  69. package/runtime/dist/mplib.mjs +2 -0
  70. package/runtime/dist/mplib.wasm +0 -0
  71. package/runtime/dist/node.d.ts +4 -0
  72. package/runtime/dist/node.js +49 -0
  73. package/runtime/dist/postscript.d.ts +21 -0
  74. package/runtime/dist/postscript.js +130 -0
  75. package/runtime/dist/prerender.d.ts +55 -0
  76. package/runtime/dist/prerender.js +106 -0
  77. package/runtime/dist/render/svg.d.ts +20 -0
  78. package/runtime/dist/render/svg.js +96 -0
  79. package/runtime/dist/tex/bridge.d.ts +93 -0
  80. package/runtime/dist/tex/bridge.js +223 -0
  81. package/runtime/dist/tex/cache-key.d.ts +33 -0
  82. package/runtime/dist/tex/cache-key.js +150 -0
  83. package/runtime/dist/tex/mpto.d.ts +60 -0
  84. package/runtime/dist/tex/mpto.js +112 -0
  85. package/runtime/dist/tex/mpx.d.ts +25 -0
  86. package/runtime/dist/tex/mpx.js +54 -0
  87. package/runtime/dist/tex/scanner.d.ts +78 -0
  88. package/runtime/dist/tex/scanner.js +326 -0
  89. package/runtime/dist/tex.mjs +2 -0
  90. package/runtime/dist/tex.wasm +0 -0
  91. package/runtime/dist/texengine.d.ts +35 -0
  92. package/runtime/dist/texengine.js +57 -0
  93. package/runtime/dist/texutils.d.ts +19 -0
  94. package/runtime/dist/texutils.js +60 -0
  95. package/runtime/dist/types.d.ts +376 -0
  96. package/runtime/dist/types.js +6 -0
  97. package/runtime/dist/vfs/bundle.d.ts +88 -0
  98. package/runtime/dist/vfs/bundle.js +315 -0
  99. package/runtime/dist/vfs/lazyfs.d.ts +62 -0
  100. package/runtime/dist/vfs/lazyfs.js +133 -0
  101. package/runtime/dist/worker.d.ts +15 -0
  102. package/runtime/dist/worker.js +143 -0
  103. package/runtime/licenses/COPYING.AFPL +230 -0
  104. package/runtime/licenses/COPYING.makeindex +42 -0
  105. package/runtime/licenses/GPL-3.0.txt +674 -0
  106. package/runtime/licenses/LGPL-3.0.txt +165 -0
  107. package/runtime/licenses/OFL-1.1.txt +93 -0
  108. package/runtime/package.json +1 -0
  109. package/runtime/upstream.json +27 -0
  110. package/src/browser-worker.js +26 -0
  111. package/src/browser.d.ts +18 -0
  112. package/src/browser.js +68 -0
  113. package/src/deploy.d.ts +5 -0
  114. package/src/deploy.js +48 -0
  115. package/src/index.d.ts +28 -0
  116. package/src/index.js +103 -0
  117. package/src/worker.js +15 -0
  118. package/upstream.json +27 -0
@@ -0,0 +1,364 @@
1
+ import { MetaPostCore } from './core.js';
2
+ import { Logger, DEFAULT_LOG_LEVEL, LOG_LEVELS, isLogLevel, consoleSink } from './logger.js';
3
+ import { BundleSet, browserIO } from './vfs/bundle.js';
4
+ import { resolveBundleSpecs, DEFAULT_BUNDLES } from './bundles-config.js';
5
+ import { isNode, nodeIO } from './node.js';
6
+ import { ghostscriptLoader, ghostscriptWanted } from './ghostscript.js';
7
+ export * from './types.js';
8
+ export { sanitizeSvg, postProcessSvg } from './render/svg.js';
9
+ export { scanTexBlocks, scanInputs } from './tex/scanner.js';
10
+ export { parseMetaPostLog } from './diagnostics.js';
11
+ export { splitMpx } from './tex/mpx.js';
12
+ export { LOG_LEVELS, formatRecord, consoleSink } from './logger.js';
13
+ // the opt-in bundles are added to this list, so callers need it: see README, "OpenType fonts"
14
+ export { DEFAULT_BUNDLES } from './bundles-config.js';
15
+ export { figureHash, figureName, figureDocument, extractFigures, renderFigure, wrapTikz, wrapMetaPost, makeZip, FIGURE_FILE } from './figures.js';
16
+ class InProcessBackend {
17
+ options;
18
+ emit;
19
+ core;
20
+ bundles;
21
+ logger;
22
+ constructor(options, emit) {
23
+ this.options = options;
24
+ this.emit = emit;
25
+ }
26
+ async init() {
27
+ const o = this.options;
28
+ const here = import.meta.url;
29
+ this.logger = new Logger(o.logLevel ?? DEFAULT_LOG_LEVEL, (r) => this.emit('record', r));
30
+ const io = o.bundleIO ?? (isNode ? await nodeIO() : browserIO());
31
+ let texmfDir = o.texmfDir;
32
+ if (!texmfDir) {
33
+ this.bundles = new BundleSet(io);
34
+ this.bundles.logger = this.logger;
35
+ const base = o.bundleBaseUrl ?? new URL('./bundles/', here).href;
36
+ await this.bundles.addAll(resolveBundleSpecs(o.bundles ?? DEFAULT_BUNDLES, base));
37
+ await this.bundles.prefetchEager();
38
+ await this.bundles.loadHot((base.endsWith('/') ? base : base + '/') + 'hot.json');
39
+ if (!this.bundles.canFetchSync)
40
+ await this.bundles.prefetchAll();
41
+ }
42
+ const mplibFactory = o.modules?.mplib ?? (await import(/* @vite-ignore */ new URL('./mplib.mjs', here).href)).default;
43
+ let texFactory = o.modules?.tex;
44
+ if (!texFactory && (o.tex ?? 'auto') !== 'none') {
45
+ try {
46
+ texFactory = (await import(/* @vite-ignore */ new URL('./tex.mjs', here).href)).default;
47
+ }
48
+ catch {
49
+ texFactory = undefined;
50
+ }
51
+ }
52
+ let dvisvgmFactory = o.modules?.dvisvgm;
53
+ if (!dvisvgmFactory && texFactory) {
54
+ try {
55
+ dvisvgmFactory = (await import(/* @vite-ignore */ new URL('./dvisvgm.mjs', here).href)).default;
56
+ }
57
+ catch {
58
+ dvisvgmFactory = undefined;
59
+ }
60
+ }
61
+ // luatex.wasm: the factory is cheap to import; the 4 MB module is only fetched on the first lualatex run
62
+ let luatexFactory = o.modules?.luatex;
63
+ if (!luatexFactory && texFactory) {
64
+ try {
65
+ luatexFactory = (await import(/* @vite-ignore */ new URL('./luatex.mjs', here).href)).default;
66
+ }
67
+ catch {
68
+ luatexFactory = undefined;
69
+ }
70
+ }
71
+ const gsBase = o.ghostscriptBaseUrl ?? new URL('./ghostscript/', here).href;
72
+ const ghostscript = dvisvgmFactory && ghostscriptWanted(o.ghostscript, o.bundles ?? DEFAULT_BUNDLES)
73
+ ? ghostscriptLoader({ glueUrl: new URL('gs.mjs', gsBase).href, wasmUrl: new URL('gs.wasm', gsBase).href, fetch: (u) => io.fetch(u), factory: o.modules?.ghostscript, log: (m) => this.logger.info('host', m) })
74
+ : undefined;
75
+ // bibtex.wasm and makeindex.wasm: imported on first use, so a host that never asks pays nothing
76
+ const toolFactories = new Map();
77
+ const tools = (name) => {
78
+ let f = toolFactories.get(name);
79
+ if (!f)
80
+ toolFactories.set(name, f = o.modules?.[name] ? Promise.resolve(o.modules[name]) : import(/* @vite-ignore */ new URL(`./${name}.mjs`, here).href).then((m) => m.default, () => undefined));
81
+ return f;
82
+ };
83
+ this.core = new MetaPostCore({
84
+ mplibFactory, texFactory, luatexFactory, dvisvgmFactory, ghostscript, tools, bundles: this.bundles, texmfDir, options: o, logger: this.logger,
85
+ onProgress: (e) => this.emit('progress', e),
86
+ onLog: (l) => this.emit('log', l),
87
+ });
88
+ await this.core.init();
89
+ return { version: this.core.version };
90
+ }
91
+ run(source, options) { return this.core.run(source, options); }
92
+ latex(source, options) { return this.core.latex(source, options); }
93
+ bibtex(options) { return this.core.bibtex(options); }
94
+ makeindex(options) { return this.core.makeindex(options); }
95
+ async addFiles(files) { this.core.addFiles(files); }
96
+ async removeFiles(names) { this.core.removeFiles(names); }
97
+ async clearCache() { this.core.clearCache(); }
98
+ async preload(bundles) {
99
+ if (!this.bundles)
100
+ return;
101
+ const base = this.options.bundleBaseUrl ?? new URL('./bundles/', import.meta.url).href;
102
+ for (const spec of resolveBundleSpecs(bundles, base))
103
+ if (!this.bundles.manifests.some((m) => m.spec.name === spec.name))
104
+ await this.bundles.add(spec);
105
+ await this.bundles.prefetchAll((f) => bundles.includes(f.bundle));
106
+ }
107
+ async prefetch(kinds) { return this.bundles ? this.bundles.prefetchHot(kinds) : 0; }
108
+ setLogLevel(level) { this.logger.level = level; }
109
+ dispose() { }
110
+ }
111
+ class WorkerBackend {
112
+ options;
113
+ emit;
114
+ worker;
115
+ pending = new Map();
116
+ nextId = 1;
117
+ constructor(options, emit) {
118
+ this.options = options;
119
+ this.emit = emit;
120
+ }
121
+ onMessage(m) {
122
+ if ('event' in m) {
123
+ this.emit(m.event, m.data);
124
+ return;
125
+ }
126
+ const p = this.pending.get(m.id);
127
+ if (!p)
128
+ return;
129
+ this.pending.delete(m.id);
130
+ if (!this.pending.size)
131
+ this.worker.busy(false);
132
+ if (m.ok)
133
+ p.resolve(m.result);
134
+ else
135
+ p.reject(new Error(m.error));
136
+ }
137
+ onError(message) {
138
+ for (const p of this.pending.values())
139
+ p.reject(new Error(message));
140
+ this.pending.clear();
141
+ this.worker.busy(false);
142
+ }
143
+ /** Start the worker: a Web Worker in a browser, a worker_threads Worker in Node. */
144
+ async start() {
145
+ const url = new URL('./worker.js', import.meta.url);
146
+ if (isNode) {
147
+ const { Worker: NodeWorker } = await import('node:worker_threads');
148
+ const w = new NodeWorker(url);
149
+ w.on('message', (m) => this.onMessage(m));
150
+ w.on('error', (e) => this.onError(String(e?.message ?? e)));
151
+ w.on('exit', (code) => { if (this.pending.size)
152
+ this.onError(`mp-tikz-wasm: worker exited (${code})`); });
153
+ w.unref();
154
+ this.worker = { postMessage: (m) => w.postMessage(m), terminate: () => { void w.terminate(); }, busy: (on) => (on ? w.ref() : w.unref()) };
155
+ }
156
+ else {
157
+ const w = new Worker(url, { type: 'module' });
158
+ w.onmessage = (ev) => this.onMessage(ev.data);
159
+ w.onerror = (e) => this.onError(String(e.message ?? e));
160
+ this.worker = { postMessage: (m) => w.postMessage(m), terminate: () => w.terminate(), busy: () => { } };
161
+ }
162
+ }
163
+ call(op, payload = {}) {
164
+ const id = this.nextId++;
165
+ return new Promise((resolve, reject) => {
166
+ if (!this.pending.size)
167
+ this.worker.busy(true);
168
+ this.pending.set(id, { resolve, reject });
169
+ this.worker.postMessage({ id, op, ...payload });
170
+ });
171
+ }
172
+ async init() {
173
+ await this.start();
174
+ const { runScript, makeText, onFindFile, log, logger, ...cloneable } = this.options;
175
+ return this.call('init', { options: cloneable, baseUrl: import.meta.url });
176
+ }
177
+ run(source, options) {
178
+ const { signal, ...rest } = options;
179
+ return this.call('run', { source, options: rest });
180
+ }
181
+ latex(source, options) {
182
+ const { signal, ...rest } = options;
183
+ return this.call('latex', { source, options: rest });
184
+ }
185
+ bibtex(options) { return this.call('bibtex', { options }); }
186
+ makeindex(options) { return this.call('makeindex', { options }); }
187
+ addFiles(files) { return this.call('addFiles', { files }); }
188
+ removeFiles(names) { return this.call('removeFiles', { names }); }
189
+ clearCache() { return this.call('clearCache'); }
190
+ preload(bundles) { return this.call('preload', { bundles }); }
191
+ prefetch(kinds) { return this.call('prefetch', { kinds }); }
192
+ setLogLevel(level) { void this.call('setLogLevel', { level }); }
193
+ dispose() { this.worker.terminate(); }
194
+ }
195
+ export class MetaPost {
196
+ backend;
197
+ options;
198
+ listeners = new Map();
199
+ queue = Promise.resolve();
200
+ t0 = now();
201
+ version = { metapost: '', tex: '', build: '' };
202
+ constructor(backend, options) {
203
+ this.backend = backend;
204
+ this.options = options;
205
+ }
206
+ static async create(options = {}) {
207
+ options = { ...options };
208
+ if (options.snapshot === undefined)
209
+ options.snapshot = isNode ? 'auto' : 'none';
210
+ if (options.logLevel !== undefined && !isLogLevel(options.logLevel))
211
+ throw new Error(`mp-tikz-wasm: unknown logLevel "${options.logLevel}" (silent, error, warn, info, debug, trace)`);
212
+ const hasCallbacks = !!(options.runScript || options.makeText || options.onFindFile || options.modules || options.bundleIO);
213
+ // In Node the default is in-process; `worker: true` runs the engines in a worker_threads
214
+ // thread, which is what lets the stall watchdog (timeoutMs) stop a runaway document.
215
+ const useWorker = options.worker ?? (!isNode && typeof Worker !== 'undefined' && !hasCallbacks);
216
+ if (!isNode && (options.runScript || options.makeText || options.onFindFile) && options.worker === undefined && typeof console !== 'undefined') {
217
+ console.warn('mp-tikz-wasm: runScript/makeText/onFindFile callbacks require in-process mode; running on the main thread');
218
+ }
219
+ let mp;
220
+ const emit = (ev, data) => mp.handleEvent(ev, data);
221
+ const backend = useWorker ? new WorkerBackend(options, emit) : new InProcessBackend(options, emit);
222
+ mp = new MetaPost(backend, options);
223
+ const { version } = await backend.init();
224
+ Object.assign(mp.version, version);
225
+ if (options.prefetch?.length)
226
+ await backend.prefetch(options.prefetch);
227
+ return mp;
228
+ }
229
+ /**
230
+ * The stall watchdog (worker only): the run is killed when `timeoutMs` pass without a progress
231
+ * event or a line of engine output. Every engine phase and every on-demand file fetch is progress,
232
+ * so a first run on a slow host that spends a minute fetching files one after another is left
233
+ * alone, and so is a long TeX run that is still printing; a silent, hung engine is not.
234
+ */
235
+ watchdog(p, what, signal) {
236
+ const timeout = this.options.timeoutMs ?? 20_000;
237
+ if (!(this.backend instanceof WorkerBackend) || !timeout)
238
+ return p;
239
+ return new Promise((resolve, reject) => {
240
+ let t;
241
+ const off = this.on('progress', () => arm());
242
+ const offLog = this.on('log', () => arm());
243
+ const done = () => { if (t)
244
+ clearTimeout(t); off(); offLog(); };
245
+ const kill = (why) => { done(); this.backend.dispose(); reject(new Error(why)); };
246
+ const arm = () => { if (t)
247
+ clearTimeout(t); t = setTimeout(() => kill(`mp-tikz-wasm: ${what} made no progress for ${timeout} ms; worker terminated`), timeout); };
248
+ arm();
249
+ p.then((r) => { done(); resolve(r); }, (e) => { done(); reject(e); });
250
+ signal?.addEventListener('abort', () => kill('aborted'));
251
+ });
252
+ }
253
+ /** Events from either backend: log records reach the console (or the `logger` option), raw lines the `log` option, then any listeners. */
254
+ handleEvent(event, data) {
255
+ if (event === 'record')
256
+ (this.options.logger ?? consoleSink)(data);
257
+ else if (event === 'log')
258
+ this.options.log?.(data);
259
+ this.emit(event, data);
260
+ }
261
+ /** A record from this side of the worker boundary (a failed call, the watchdog), subject to the same level. */
262
+ hostRecord(level, message) {
263
+ if (LOG_LEVELS.indexOf(level) > LOG_LEVELS.indexOf(this.logLevel))
264
+ return;
265
+ this.handleEvent('record', { level, source: 'host', message, time: Math.round((now() - this.t0) * 10) / 10 });
266
+ }
267
+ failing(p, what) {
268
+ return p.catch((e) => { this.hostRecord('error', `${what} failed: ${e?.message ?? e}`); throw e; });
269
+ }
270
+ /** How much reaches the console (see `MetaPostOptions.logLevel`); can be changed at any time, also mid-run. */
271
+ get logLevel() { return this.options.logLevel ?? DEFAULT_LOG_LEVEL; }
272
+ set logLevel(level) {
273
+ if (!isLogLevel(level))
274
+ throw new Error(`mp-tikz-wasm: unknown logLevel "${level}" (silent, error, warn, info, debug, trace)`);
275
+ this.options.logLevel = level;
276
+ this.backend.setLogLevel(level);
277
+ }
278
+ /** Compile MetaPost source. Calls are serialised on this instance. */
279
+ run(source, options = {}) {
280
+ const task = () => this.failing(this.watchdog(this.backend.run(source, options), 'run', options.signal), 'run');
281
+ const next = this.queue.then(task, task);
282
+ this.queue = next.catch(() => undefined);
283
+ return next;
284
+ }
285
+ /** Typeset a whole LaTeX/TikZ (or plain TeX) document to one SVG per page. */
286
+ latex(source, options = {}) {
287
+ const task = () => this.failing(this.watchdog(this.backend.latex(source, options), 'latex'), 'latex');
288
+ const next = this.queue.then(task, task);
289
+ this.queue = next.catch(() => undefined);
290
+ return next;
291
+ }
292
+ /**
293
+ * BibTeX on the .aux a latex() run wrote (`files: result.artifacts`, plus the .bib databases):
294
+ * doc.bbl and doc.blg come back in `artifacts`, for the next latex() run. TeX Live's styles
295
+ * are in the `latex-doc` bundle. Serialised with run() and latex() on this instance.
296
+ */
297
+ bibtex(options = {}) { return this.serialise(() => this.failing(this.watchdog(this.backend.bibtex(options), 'bibtex'), 'bibtex')); }
298
+ /** makeindex on the .idx a latex() run wrote: doc.ind and doc.ilg come back in `artifacts`. */
299
+ makeindex(options = {}) { return this.serialise(() => this.failing(this.watchdog(this.backend.makeindex(options), 'makeindex'), 'makeindex')); }
300
+ serialise(task) {
301
+ const next = this.queue.then(task, task);
302
+ this.queue = next.catch(() => undefined);
303
+ return next;
304
+ }
305
+ addFiles(files) { return this.backend.addFiles(files); }
306
+ /**
307
+ * Take off this instance files that addFiles or a run's `files` put there, named as they were
308
+ * given (all of them with no names). Passed files otherwise stay for the instance's lifetime:
309
+ * a host typesetting several documents should remove one document's .aux, .toc, .bbl before
310
+ * the next, or the next one reads them.
311
+ */
312
+ removeFiles(names) { return this.backend.removeFiles(names); }
313
+ preload(bundles) { return this.backend.preload(bundles); }
314
+ /** Fetch in parallel the files a first run of these kinds needs (see `MetaPostOptions.prefetch`). Resolves to the number fetched. */
315
+ prefetch(kinds) { return this.backend.prefetch(kinds); }
316
+ clearCache() { return this.backend.clearCache(); }
317
+ on(event, fn) {
318
+ let s = this.listeners.get(event);
319
+ if (!s) {
320
+ s = new Set();
321
+ this.listeners.set(event, s);
322
+ }
323
+ s.add(fn);
324
+ return () => s.delete(fn);
325
+ }
326
+ emit(event, data) { this.listeners.get(event)?.forEach((f) => f(data)); }
327
+ dispose() { this.backend.dispose(); }
328
+ }
329
+ function now() { return typeof performance !== 'undefined' ? performance.now() : Date.now(); }
330
+ /** A pool of independent instances for batch work (docs/10 §2.3). */
331
+ export class MetaPostPool {
332
+ all;
333
+ idle = [];
334
+ waiters = [];
335
+ constructor(all) {
336
+ this.all = all;
337
+ this.idle = [...all];
338
+ }
339
+ static async create(options = {}) {
340
+ const size = options.size ?? (typeof navigator !== 'undefined' ? (navigator.hardwareConcurrency ?? 2) : 2);
341
+ const all = await Promise.all(Array.from({ length: Math.max(1, size) }, () => MetaPost.create(options)));
342
+ return new MetaPostPool(all);
343
+ }
344
+ acquire() {
345
+ const mp = this.idle.pop();
346
+ return mp ? Promise.resolve(mp) : new Promise((r) => this.waiters.push(r));
347
+ }
348
+ release(mp) { const w = this.waiters.shift(); if (w)
349
+ w(mp);
350
+ else
351
+ this.idle.push(mp); }
352
+ async run(source, options) {
353
+ const mp = await this.acquire();
354
+ try {
355
+ return await mp.run(source, options);
356
+ }
357
+ finally {
358
+ this.release(mp);
359
+ }
360
+ }
361
+ dispose() { for (const mp of this.all)
362
+ mp.dispose(); }
363
+ }
364
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * logger.ts — levelled logging for the engines (docs/08 §4). Every part of
3
+ * the pipeline reports through one Logger; records at or below the chosen
4
+ * level reach the sink (the console by default, or the `logger` option) and
5
+ * the rest cost nothing. Each level includes the ones before it:
6
+ *
7
+ * silent nothing
8
+ * error the errors of every run (MetaPost, TeX, dvisvgm, bundles, the host)
9
+ * warn plus warnings (the default)
10
+ * info plus one line per run and per engine pass, with timings
11
+ * debug plus the engines' own terminal output, line by line, as it is written
12
+ * trace plus every file looked up or fetched, cache hits, progress events
13
+ *
14
+ * The level is checked at call time, so `mp.logLevel = 'debug'` takes effect
15
+ * for the next line an engine prints.
16
+ */
17
+ import type { LogLevel, LogRecord, LogSource } from './types.js';
18
+ export declare const LOG_LEVELS: readonly LogLevel[];
19
+ export declare const DEFAULT_LOG_LEVEL: LogLevel;
20
+ export type LogSink = (record: LogRecord) => void;
21
+ type Level = Exclude<LogLevel, 'silent'>;
22
+ export declare function isLogLevel(x: unknown): x is LogLevel;
23
+ export declare class Logger {
24
+ level: LogLevel;
25
+ sink: LogSink;
26
+ private readonly t0;
27
+ constructor(level?: LogLevel, sink?: LogSink);
28
+ enabled(level: Level): boolean;
29
+ log(level: Level, source: LogSource, message: string): void;
30
+ error(source: LogSource, message: string): void;
31
+ warn(source: LogSource, message: string): void;
32
+ info(source: LogSource, message: string): void;
33
+ debug(source: LogSource, message: string): void;
34
+ trace(source: LogSource, message: string): void;
35
+ }
36
+ /** A logger that drops everything (for parts of the pipeline created without one). */
37
+ export declare const silentLogger: Logger;
38
+ /** `mp-tikz-wasm tex: ...` for an engine's line, `mp-tikz-wasm: ...` for the library's own. */
39
+ export declare function formatRecord(r: LogRecord): string;
40
+ /** The default sink. Errors and warnings use the console's own levels; the two
41
+ * verbose levels use console.log rather than console.debug, which Chrome
42
+ * hides under "Verbose" by default — someone who asked for the engines'
43
+ * output should see it without changing a DevTools filter. */
44
+ export declare function consoleSink(r: LogRecord): void;
45
+ /** `ok in 123 ms`-style helpers shared by the summaries. */
46
+ export declare function ms(x: number): string;
47
+ export declare function plural(n: number, noun: string): string;
48
+ export {};
@@ -0,0 +1,55 @@
1
+ export const LOG_LEVELS = ['silent', 'error', 'warn', 'info', 'debug', 'trace'];
2
+ export const DEFAULT_LOG_LEVEL = 'warn';
3
+ export function isLogLevel(x) { return typeof x === 'string' && LOG_LEVELS.includes(x); }
4
+ export class Logger {
5
+ level;
6
+ sink;
7
+ t0 = now();
8
+ constructor(level = DEFAULT_LOG_LEVEL, sink = consoleSink) {
9
+ this.level = level;
10
+ this.sink = sink;
11
+ }
12
+ enabled(level) { return LOG_LEVELS.indexOf(level) <= LOG_LEVELS.indexOf(this.level); }
13
+ log(level, source, message) {
14
+ if (!this.enabled(level))
15
+ return;
16
+ this.sink({ level, source, message, time: Math.round((now() - this.t0) * 10) / 10 });
17
+ }
18
+ error(source, message) { this.log('error', source, message); }
19
+ warn(source, message) { this.log('warn', source, message); }
20
+ info(source, message) { this.log('info', source, message); }
21
+ debug(source, message) { this.log('debug', source, message); }
22
+ trace(source, message) { this.log('trace', source, message); }
23
+ }
24
+ /** A logger that drops everything (for parts of the pipeline created without one). */
25
+ export const silentLogger = new Logger('silent', () => { });
26
+ /** `mp-tikz-wasm tex: ...` for an engine's line, `mp-tikz-wasm: ...` for the library's own. */
27
+ export function formatRecord(r) {
28
+ return r.source === 'host' ? `mp-tikz-wasm: ${r.message}` : `mp-tikz-wasm ${r.source}: ${r.message}`;
29
+ }
30
+ /** The default sink. Errors and warnings use the console's own levels; the two
31
+ * verbose levels use console.log rather than console.debug, which Chrome
32
+ * hides under "Verbose" by default — someone who asked for the engines'
33
+ * output should see it without changing a DevTools filter. */
34
+ export function consoleSink(r) {
35
+ if (typeof console === 'undefined')
36
+ return;
37
+ const line = formatRecord(r);
38
+ switch (r.level) {
39
+ case 'error':
40
+ console.error(line);
41
+ break;
42
+ case 'warn':
43
+ console.warn(line);
44
+ break;
45
+ case 'info':
46
+ console.info(line);
47
+ break;
48
+ default: console.log(line);
49
+ }
50
+ }
51
+ /** `ok in 123 ms`-style helpers shared by the summaries. */
52
+ export function ms(x) { return `${x < 10 ? x.toFixed(1) : Math.round(x)} ms`; }
53
+ export function plural(n, noun) { return `${n} ${noun}${n === 1 ? '' : 's'}`; }
54
+ function now() { return typeof performance !== 'undefined' ? performance.now() : Date.now(); }
55
+ //# sourceMappingURL=logger.js.map