clone

a browser-first diagram editor with live svg previews, local drafts and encrypted, accountless sharing

main
diagramzip / renderers / edge / scripts / build / tikz.mjs
tikz.mjs 6.8K
1import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'2import { gunzipSync } from 'node:zlib'3import { resolve } from 'node:path'45// The browser unit's pinned, published dist tree is the source of truth for6// the native extraction. This script intentionally performs only mechanical7// copying and a small, asserted source transformation; it does not rebuild8// TikZJax or alter the browser unit.9const edgeRoot = resolve(import.meta.dirname, '../..')10const clientRoot = resolve(edgeRoot, '../client/tikz')11const output = resolve(edgeRoot, 'artifacts/tikz')12const sourceOutput = resolve(output, 'source')13const sourceDist = resolve(clientRoot, 'dist')14const sourceRoot = existsSync(resolve(sourceDist, 'run-tex.js')) ? sourceDist : output15const sourceRunTex = existsSync(resolve(sourceDist, 'run-tex.js')) ? resolve(sourceDist, 'run-tex.js') : resolve(output, 'source/run-tex.js')16const licenseRoot = existsSync(resolve(clientRoot, 'vendor-licenses')) ? resolve(clientRoot, 'vendor-licenses') : resolve(output, 'licenses')1718const required = ['run-tex.js', 'tex.wasm.gz', 'core.dump.gz', 'tex_files', 'fonts']19for (const entry of required.slice(1)) {20 if (!existsSync(resolve(sourceRoot, entry))) throw new Error(`Missing pinned TikZJax asset: ${entry}`)21}2223if (!existsSync(sourceRunTex)) throw new Error('Missing pinned TikZJax run-tex.js source.')24const originalRunTex = readFileSync(sourceRunTex, 'utf8')25if (sourceRoot !== output) rmSync(output, { recursive: true, force: true })26mkdirSync(sourceOutput, { recursive: true })27if (sourceRoot !== output) {28 cpSync(resolve(sourceRoot, 'tex_files'), resolve(output, 'tex_files'), { recursive: true })29 cpSync(resolve(sourceRoot, 'fonts'), resolve(output, 'fonts'), { recursive: true })30 cpSync(resolve(sourceRoot, 'tex.wasm.gz'), resolve(output, 'tex.wasm.gz'))31 cpSync(resolve(sourceRoot, 'core.dump.gz'), resolve(output, 'core.dump.gz'))32}33writeFileSync(resolve(sourceOutput, 'run-tex.js'), originalRunTex)34for (const license of ['GPL-3.0.txt', 'LPPL-1.3c.txt']) {35 mkdirSync(resolve(output, 'licenses'), { recursive: true })36 const destination = resolve(output, 'licenses', license)37 if (resolve(licenseRoot, license) !== destination) cpSync(resolve(licenseRoot, license), destination)38}39writeFileSync(resolve(output, 'tex.wasm'), gunzipSync(readFileSync(resolve(output, 'tex.wasm.gz'))))4041let core = originalRunTex42const expose = 'const YA=UA.expose;UA.registerSerializer,UA.Transfer;'43if (!core.includes(expose)) throw new Error('TikZJax expose hook did not match the pinned source.')44core = core.replace(expose, 'const YA=value=>{globalThis.__tikzCaptured=value};')45const instantiate = 'const B=await WebAssembly.instantiate(Zn,{library:A,env:{memory:n}})'46if (!core.includes(instantiate)) throw new Error('TikZJax Wasm instantiate hook did not match the pinned source.')47core = core.replace(instantiate, 'const B=new WebAssembly.Instance(globalThis.__tikzWasm,{library:A,env:{memory:n}})')48if (!core.includes('await Un(B.instance.exports)')) throw new Error('TikZJax Wasm export hook did not match the pinned source.')49core = core.replace('await Un(B.instance.exports)', 'await Un(B.exports)')50const inflate = 'const Xn=async A=>{const t=await fetch(`${zn}/${A}`);if(t.ok){const A=t.body.getReader(),e=new zr.Inflate;for(;;){const{done:t,value:r}=await A.read();if(t)break;e.push(r)}if(A.releaseLock(),e.err)throw new Error(e.err);return e.result}throw new Error(`Unable to load ${A}. File not available.`)}'51if (!core.includes(inflate)) throw new Error('TikZJax inflate hook did not match the pinned source.')52const streamingInflate = 'const Xn=async(A,t)=>{const e=await zn(A);if(e.ok){const A=e.body.getReader(),r=new zr.Inflate;let n=0;t&&(r.onData=A=>{if(n+A.length>t.length)throw new Error(`Inflated ${A} exceeds its destination.`);t.set(A,n),n+=A.length});for(;;){const{done:e,value:t}=await A.read();if(e)break;r.push(t)}if(A.releaseLock(),r.err)throw new Error(r.err);if(t&&n!==t.length)throw new Error(`Inflated ${A} has an unexpected size.`);return t?t.subarray(0,n):r.result}throw new Error(`Unable to load ${A}. File not available.`)}'53core = core.replace(inflate, streamingInflate)54const loadAssets = 'async load(A){zn=A,Zn=await Xn("tex.wasm.gz"),Wn=new Uint8Array(await Xn("core.dump.gz"),0,65536*wn)}'55if (!core.includes(loadAssets)) throw new Error('TikZJax asset-load hook did not match the pinned source.')56core = core.replace(loadAssets, 'async load(A){zn=A}')57const dumpCopy = 'new Uint8Array(n.buffer,0,65536*wn).set(Wn.slice(0)),In(n.buffer),Fn("input.tex\\n\\\\end\\n"),Gn(Xn)'58if (!core.includes(dumpCopy)) throw new Error('TikZJax dump-copy hook did not match the pinned source.')59core = core.replace(dumpCopy, 'await Xn("core.dump.gz",new Uint8Array(n.buffer,0,65536*wn)),In(n.buffer),Fn("input.tex\\n\\\\end\\n"),Gn(Xn)')60const externalFallback = 'catch{try{const t=await fetch(A);if(!t.ok)throw new Error(`Unable to load ${A}.`);{const e=await t.text();An[A]=e}}catch{}}'61if (!core.includes(externalFallback)) throw new Error('TikZJax external-file hook did not match the pinned source.')62core = core.replace(externalFallback, 'catch{}')63if (!core.endsWith('})();')) throw new Error('TikZJax wrapper terminator did not match the pinned source.')64core = `${core.slice(0, -5)}65})();export const tikzCore=globalThis.__tikzCaptured;`66writeFileSync(resolve(output, 'tikz-core.js'), core)67writeFileSync(resolve(output, 'tikz-core.d.ts'), 'export interface TikzCore {\n load(fetchAsset: (assetPath: string) => Promise<Response>): Promise<void>\n texify(source: string, options: Record<string, string>): Promise<string>\n}\nexport const tikzCore: TikzCore\n')68writeFileSync(resolve(output, 'tex.wasm.d.ts'), 'declare const module: WebAssembly.Module\nexport default module\n')69writeFileSync(resolve(output, 'provenance.md'), [70 '# TikZJax edge artifact',71 '',72 'This artifact is derived from @planktimerr/tikzjax@1.0.63, pinned to',73 'upstream revision e4bb417fe574f58f6db611cefc6be4855ab9d345. The generated',74 'tikz-core.js is the distributed run-tex.js with the threads worker export',75 'replaced by a direct load/texify capture, the compressed core dump stream-',76 'inflated directly into fresh Wasm memory, and arbitrary external TeX file',77 'fallbacks removed. Asset reads use the Worker static-assets binding. The',78 'compressed tex.wasm.gz fetch is unused by the edge',79 'path; tex.wasm is statically compiled into the Worker with the CompiledWasm',80 'rule. It is used only with the bundled tex_files/ package set.',81 '',82 'The unmodified corresponding input is published at source/run-tex.js; the',83 'mechanical extraction is reproducibly produced by',84 'renderers/edge/scripts/build/tikz.mjs.',85 '',86 'The TikZJax source is GPL-3.0-or-later; its TeX/TikZ components are covered',87 'by LPPL-1.3c. The corresponding notices are included in licenses/.',88 '',89].join('\n'))9091console.log(`TikZ edge artifact rebuilt from ${sourceRoot}`)