oxedyne/daimond/dev/probe_typstoutline.mjs
4.5 KiB, 1 run
created by r2519314175:101, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // probe_typstoutline.mjs — where a section rail's entries could come from. |
| 2 | // |
| 3 | // The live view is asked for a toggled rail of hyperlinked sections. A rail needs |
| 4 | // three things per entry and the document has to answer all three: the words, the |
| 5 | // level, and THE PAGE. This asks every route the vendored pair offers, and reports |
| 6 | // what each one actually hands back. |
| 7 | // |
| 8 | // It asserts nothing and is not a verifier. |
| 9 | // |
| 10 | // eval "$(bash dev/world.sh 5 --env)" |
| 11 | // node dev/probe_typstoutline.mjs |
| 12 | import fs from 'node:fs'; |
| 13 | import { open, scratch } from './harness.mjs'; |
| 14 | |
| 15 | const WORK = scratch('typstoutline'); |
| 16 | fs.mkdirSync(WORK, { recursive: true }); |
| 17 | |
| 18 | const DOC = `#set page(width: 200pt, height: 160pt, margin: 14pt) |
| 19 | #set text(size: 9pt) |
| 20 | = Front matter |
| 21 | #lorem(40) |
| 22 | = Chapter one |
| 23 | == Section one point one |
| 24 | #lorem(120) |
| 25 | == Section one point two |
| 26 | #lorem(120) |
| 27 | = Chapter two |
| 28 | #lorem(200) |
| 29 | == Section two point one |
| 30 | #lorem(60) |
| 31 | `; |
| 32 | |
| 33 | const s = await open({ name: 'typstoutline' }); |
| 34 | const p = s.page; |
| 35 | await p.waitForTimeout(1200); |
| 36 | |
| 37 | const r = await p.evaluate(async (src) => { |
| 38 | const out = {}; |
| 39 | const m = await import('/pkg/oxedyne_daimond.js'); |
| 40 | m.set_locked_packs(''); |
| 41 | await m.write_file('outprobe/main.typ', src); |
| 42 | const c = await m.typst_compile_project_vector('outprobe/main.typ'); |
| 43 | if (!c.vector) return { error: String(c.error || 'nothing').slice(0, 400) }; |
| 44 | |
| 45 | // ── 1. the renderer, on the artifact the view already holds ── |
| 46 | const g = await import('/vendor/typst/typst_ts_renderer.mjs'); |
| 47 | await g.default('/vendor/typst/typst_ts_renderer_bg.wasm'); |
| 48 | const rr = await new g.TypstRendererBuilder().build(); |
| 49 | const ses = rr.session_from_artifact(c.vector, 'vector'); |
| 50 | out.pages = ses.pages_info.page_count; |
| 51 | try { |
| 52 | const cu = rr.get_customs(ses); |
| 53 | out.customs = cu ? cu.map((x) => { |
| 54 | try { return [String(x[0]), (x[1] && x[1].length) || 0]; } |
| 55 | catch (e) { return String(x).slice(0, 80); } |
| 56 | }) : null; |
| 57 | } catch (e) { out.customs = 'threw: ' + (e && e.message); } |
| 58 | // What one page's markup carries that could name a heading. |
| 59 | const info = ses.pages_info; |
| 60 | const pg0 = info.page(0); |
| 61 | out.page0 = { h: pg0.height_pt, off: pg0.page_off }; |
| 62 | ses.free(); |
| 63 | const ses2 = rr.session_from_artifact(c.vector, 'vector'); |
| 64 | const svg = ses2.render_in_window(0, 0, info.width(), 1e6); |
| 65 | ses2.free(); |
| 66 | out.svgKB = +(svg.length / 1024).toFixed(1); |
| 67 | out.svgHas = {}; |
| 68 | for (const k of ['data-tid', 'typst-group', 'typst-text', 'tsel', 'pseudo-link', |
| 69 | 'handleTypstLocation', 'typst-page', 'data-page', '<a ', 'xlink:href']) { |
| 70 | out.svgHas[k] = svg.split(k).length - 1; |
| 71 | } |
| 72 | out.svgFirstA = (svg.match(/<a [^>]{0,200}/) || [''])[0]; |
| 73 | out.svgPageGroups = (svg.match(/class="typst-page[^"]*"[^>]{0,140}/g) || []).slice(0, 4); |
| 74 | |
| 75 | // ── 2. the compiler, asked about the document rather than for it ── |
| 76 | // A SECOND compiler off the SAME wasm module, so this costs no second 27 MB. |
| 77 | const heap0 = window.DaimondTypst.heapMB(); |
| 78 | const cg = await import('/vendor/typst/typst_ts_web_compiler.mjs'); |
| 79 | const init = await cg.default('/vendor/typst/typst_ts_web_compiler_bg.wasm'); |
| 80 | const b = new cg.TypstCompilerBuilder(); |
| 81 | b.set_dummy_access_model(); |
| 82 | for (const n of ['LibertinusSerif-Regular.otf', 'LibertinusSerif-Bold.otf', |
| 83 | 'LibertinusSerif-Italic.otf', 'LibertinusSerif-BoldItalic.otf', |
| 84 | 'NewCMMath-Regular.otf']) { |
| 85 | try { |
| 86 | const resp = await fetch('/vendor/typst/fonts/' + n); |
| 87 | if (resp.ok) await b.add_raw_font(new Uint8Array(await resp.arrayBuffer())); |
| 88 | } catch (e) { /* whichever are there */ } |
| 89 | } |
| 90 | const comp = await b.build(); |
| 91 | comp.reset_shadow(); |
| 92 | comp.add_source('/main.typ', src); |
| 93 | out.query = {}; |
| 94 | const ask = (sel, field) => { |
| 95 | const t0 = performance.now(); |
| 96 | try { |
| 97 | const j = comp.query('/main.typ', undefined, sel, field); |
| 98 | return { ms: +(performance.now() - t0).toFixed(1), out: String(j).slice(0, 900) }; |
| 99 | } catch (e) { return { ms: +(performance.now() - t0).toFixed(1), err: String(e && e.message).slice(0, 200) }; } |
| 100 | }; |
| 101 | out.query.heading = ask('heading', undefined); |
| 102 | out.query.headingBody = ask('heading', 'body'); |
| 103 | out.query.headingLoc = ask('heading', 'location'); |
| 104 | out.query.headingPage = ask('heading', 'page'); |
| 105 | out.query.metadata = ask('<toc>', undefined); |
| 106 | out.heapAfter = { before: heap0, after: window.DaimondTypst.heapMB(), |
| 107 | second: init && init.memory ? init.memory.buffer.byteLength / 1048576 : 0 }; |
| 108 | return out; |
| 109 | }, DOC); |
| 110 | |
| 111 | if (r.error) { console.log('REFUSED: ' + r.error); await s.close(); process.exit(1); } |
| 112 | fs.writeFileSync(`${WORK}/probe.json`, JSON.stringify(r, null, 2)); |
| 113 | console.log(JSON.stringify(r, null, 2).slice(0, 12000)); |
| 114 | console.log('\nwritten to ' + WORK); |
| 115 | await s.close(); |