import { convertToAscii } from '../lib/ascii-converter' import { conversionDimensions } from '../lib/resource-limits' import { createTextCanvas, encodePng } from '../lib/text-export-utils' const root = '/research/2026-11-08' const options = { brightness: 1, contrast: 1, saturation: 1, invert: false, hue: 0, sepia: 0, dithering: 'none', mode: 'standard' } as const const ramp = '@%#*+=-:. ' const bytes64 = (bytes: Uint8Array) => { let result = '' for (let offset = 0; offset < bytes.length; offset += 8192) result += String.fromCharCode(...bytes.subarray(offset, offset + 8192)) return btoa(result) } const hash = async (bytes: Uint8Array) => { const digest = await crypto.subtle.digest('SHA-256', new Uint8Array(bytes)) return [...new Uint8Array(digest)].map(byte => byte.toString(16).padStart(2, '0')).join('') } async function capture() { const exports = [] for (const source of ['gradient', 'checkerboard', 'disk']) { const response = await fetch(`${root}/${source}.png`, { cache: 'no-store' }) if (!response.ok) throw new Error(`Fixture request failed: ${response.status}`) const bytes = new Uint8Array(await response.arrayBuffer()) const dataUrl = `data:image/png;base64,${bytes64(bytes)}` const image = new Image() image.src = dataUrl await image.decode() for (const columns of [40, 80, 120]) { const { height: rows } = conversionDimensions(image.width, image.height, columns, false) const sample = document.createElement('canvas') sample.width = columns sample.height = rows const context = sample.getContext('2d') if (!context) throw new Error('No sampling context') context.drawImage(image, 0, 0, columns, rows) const rgba = new Uint8Array(context.getImageData(0, 0, columns, rows).data) const text = await convertToAscii(dataUrl, columns, ramp, options) const txt = new TextEncoder().encode(text) const canvas = createTextCanvas(text, { fontFamily: 'monospace' }) const png = new Uint8Array(await (await encodePng(canvas)).arrayBuffer()) exports.push({ source, columns, rows, text, txtBytes: txt.length, txtSha256: await hash(txt), pngBytes: png.length, pngSha256: await hash(png), pngWidth: canvas.width, pngHeight: canvas.height, sourceSha256: await hash(bytes), rasterSha256: await hash(rgba), rgba: bytes64(rgba), png: bytes64(png) }) canvas.width = canvas.height = sample.width = sample.height = 0 } image.removeAttribute('src') } const context = document.createElement('canvas').getContext('2d') if (!context) throw new Error('No font measurement context') context.fontKerning = 'none' const characters = [ ...Array.from({ length: 95 }, (_, i) => String.fromCodePoint(0x20 + i)), ...Array.from({ length: 32 }, (_, i) => String.fromCodePoint(0x2580 + i)), '\u2800', '\u2801', '\u28ff', '\u{1f600}', '\u{1f469}\u200d\u{1f4bb}', 'e\u0301', ] const fonts = ['monospace', 'Consolas', 'Courier New', 'Arial'].map(requestedFont => { context.font = requestedFont === 'monospace' ? '16px monospace' : `16px "${requestedFont}"` return { requestedFont, canvasFont: context.font, characters: characters.map(text => ({ text, codePoints: [...text].map(char => `U+${char.codePointAt(0)!.toString(16).toUpperCase().padStart(4, '0')}`), utf16Units: text.length, utf8Bytes: new TextEncoder().encode(text).length, width: context.measureText(text).width, repeatedWidth: context.measureText(text.repeat(8)).width })) } }) return { schemaVersion: 1, capturedAt: new Date().toISOString(), userAgent: navigator.userAgent, platform: navigator.platform, sourceWidth: 320, sourceHeight: 180, ramp, options, pngSettings: { fontFamily: 'monospace', fontSize: 14, padding: 20, invert: false }, glyphSettings: { fontSize: 16, fontKerning: context.fontKerning, repetitions: 8, webFonts: 'None loaded by this capture.', resolvedFonts: 'Not identified; platform fallback is permitted.' }, exports, fonts } } capture().then(result => { const output = document.querySelector('pre') const link = document.querySelector('a') if (!output || !link) throw new Error('Capture controls missing') output.textContent = JSON.stringify({ userAgent: result.userAgent, exports: result.exports.length, fontRequests: result.fonts.length }, null, 2) link.href = URL.createObjectURL(new Blob([JSON.stringify(result, null, 2) + '\n'], { type: 'application/json' })) link.download = 'ascii-research-capture.json' link.hidden = false }).catch(error => { const output = document.querySelector('pre') if (output) output.textContent = `Capture failed: ${error instanceof Error ? error.message : String(error)}` console.error(error) })