oxedyne/daimond/dev/probe_typstink.mjs
4.6 KiB, 1 run
created by r2519314175:97, 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_typstink.mjs — photograph what the live view really draws. |
| 2 | // |
| 3 | // Three faults were reported together: ghosted/double-struck glyphs on all body |
| 4 | // text, solid black bars where the contents entries belong, and filled black boxes |
| 5 | // over inline `#link` spans. This drives the shipped path — the Doc panel's Compile |
| 6 | // button, which is what arms the live view — and photographs the pages, then counts |
| 7 | // what is in the shadow root and what colour the browser has resolved for it. |
| 8 | // |
| 9 | // It asserts nothing and is not a verifier. |
| 10 | // |
| 11 | // eval "$(bash dev/world.sh 5 --env)" |
| 12 | // node dev/probe_typstink.mjs |
| 13 | import fs from 'node:fs'; |
| 14 | import { open, scratch } from './harness.mjs'; |
| 15 | |
| 16 | const WORK = scratch('typstink'); |
| 17 | fs.mkdirSync(WORK, { recursive: true }); |
| 18 | |
| 19 | const DOC = `#set page(width: 400pt, height: 300pt, margin: 24pt) |
| 20 | #set text(size: 11pt) |
| 21 | = A heading |
| 22 | #outline() |
| 23 | Some ordinary body text, long enough to wrap onto a second line so that a |
| 24 | double-struck run would be obvious to anybody looking at it. |
| 25 | |
| 26 | Created using #link("https://typst.app")[Typst] and #link("https://rust-lang.org")[Rust]. |
| 27 | == Another heading |
| 28 | More text under it. |
| 29 | `; |
| 30 | |
| 31 | const s = await open({ name: 'typstink' }); |
| 32 | const p = s.page; |
| 33 | await p.setViewportSize({ width: 1600, height: 1000 }); |
| 34 | await p.waitForTimeout(1200); |
| 35 | await p.evaluate(async (src) => { |
| 36 | const m = await import('/pkg/oxedyne_daimond.js'); |
| 37 | m.set_locked_packs(''); |
| 38 | await m.write_file('inkprobe/main.typ', src); |
| 39 | }, DOC); |
| 40 | await p.evaluate(() => window.DaimondDoc.show('inkprobe/main.typ')); |
| 41 | await p.waitForTimeout(1200); |
| 42 | await p.click('[data-act="compile"]', { force: true }); |
| 43 | for (let i = 0; i < 120; i++) { |
| 44 | const st = await p.evaluate(() => window.DaimondTypstWatch.state()); |
| 45 | if (st.drawn) break; |
| 46 | await new Promise(r => setTimeout(r, 500)); |
| 47 | } |
| 48 | await p.waitForTimeout(1200); |
| 49 | |
| 50 | const seen = await p.evaluate(() => { |
| 51 | const h = document.getElementById('typst-live'); |
| 52 | const sr = h.querySelector('.tl-pages').shadowRoot; |
| 53 | const tsel = sr.querySelectorAll('.tsel'); |
| 54 | const link = sr.querySelectorAll('.pseudo-link'); |
| 55 | const glyph = sr.querySelectorAll('.outline_glyph'); |
| 56 | const cs = (el) => el ? getComputedStyle(el) : null; |
| 57 | const a = cs(tsel[0]), b = cs(link[0]); |
| 58 | return { |
| 59 | state: window.DaimondTypstWatch.state(), |
| 60 | styles: sr.querySelectorAll('style').length, |
| 61 | styleText: Array.from(sr.querySelectorAll('style')).map(s => s.textContent.length), |
| 62 | tsel: tsel.length, link: link.length, glyph: glyph.length, |
| 63 | tselColour: a ? a.color : null, |
| 64 | tselSize: a ? a.fontSize : null, |
| 65 | tselBox: tsel[0] ? JSON.stringify(tsel[0].getBoundingClientRect()) : null, |
| 66 | linkFill: b ? b.getAttribute ? getComputedStyle(link[0]).fill : null : null, |
| 67 | linkBox: link[0] ? JSON.stringify(link[0].getBoundingClientRect()) : null, |
| 68 | onclicks: sr.querySelectorAll('[onclick]').length, |
| 69 | }; |
| 70 | }); |
| 71 | console.log(JSON.stringify(seen, null, 1)); |
| 72 | |
| 73 | // ── And the settings the view now offers ─────────────────────── |
| 74 | const set = await p.evaluate(() => { |
| 75 | const w = window.DaimondTypstWatch; |
| 76 | const bar = document.querySelector('#typst-live .tl-bar'); |
| 77 | return { bar: bar ? bar.textContent.replace(/\s+/g, ' ').trim() : null, |
| 78 | buttons: Array.from(bar.querySelectorAll('button')).map(b => b.textContent + '|' + b.title), |
| 79 | state: (({ zoom, dark, pages }) => ({ zoom, dark, pages }))(w.state()) }; |
| 80 | }); |
| 81 | console.log('bar: ' + JSON.stringify(set, null, 1)); |
| 82 | await p.evaluate(() => { window.DaimondTypstWatch.dark(true); window.DaimondTypstWatch.zoom(1.5); }); |
| 83 | await p.waitForTimeout(600); |
| 84 | const nightBox = await p.evaluate(() => { |
| 85 | const r = document.querySelector('#typst-live .tl-scroll').getBoundingClientRect(); |
| 86 | return { x: r.x, y: r.y, width: r.width, height: r.height, |
| 87 | state: window.DaimondTypstWatch.state() }; |
| 88 | }); |
| 89 | await p.screenshot({ path: `${WORK}/night.png`, clip: { |
| 90 | x: Math.floor(nightBox.x), y: Math.floor(nightBox.y), |
| 91 | width: Math.floor(nightBox.width), height: Math.floor(Math.min(nightBox.height, 500)) } }); |
| 92 | console.log(`night+zoom: zoom ${nightBox.state.zoom}, dark ${nightBox.state.dark}, ` |
| 93 | + `at page ${nightBox.state.at ? nightBox.state.at.page + 1 : '?'}`); |
| 94 | await p.evaluate(() => { window.DaimondTypstWatch.dark(false); window.DaimondTypstWatch.zoom(1); }); |
| 95 | await p.waitForTimeout(400); |
| 96 | |
| 97 | const box = await p.evaluate(() => { |
| 98 | const r = document.querySelector('#typst-live .tl-scroll').getBoundingClientRect(); |
| 99 | return { x: r.x, y: r.y, width: r.width, height: r.height }; |
| 100 | }); |
| 101 | await p.screenshot({ path: `${WORK}/live.png`, clip: { |
| 102 | x: Math.floor(box.x), y: Math.floor(box.y), |
| 103 | width: Math.floor(box.width), height: Math.floor(Math.min(box.height, 700)) } }); |
| 104 | console.log('wrote ' + WORK + '/live.png'); |
| 105 | await s.close(); |