Oct. 4th, 2024

henry_flower: A melancholy wolf (Default)

Як нарід робить векторні скріншоти терміналу? Ось такі:

Я гадав є конвертори аутпуту зі script(1) ув SVG, але нічого такого не знайшов. Результат з asciinema нормально конвертується лише ув бітмапові зображення. Мотлох ansi-ту-шото на пáйфоні та расті або видає скуйовджені світлини або не розуміє ті ansi сіквенсес, що відповідають за рух курсору. Покинутим termtosvg можна генерувати купу svg, але він покладається на пáйфонівський in memory емулятора терміналу, який має купу багів.

Сучасний xterm вміє робити "SVG Screen Dump", але з наївним позіціонуванням літер. Наприклад, рядок '$ uname' він серіялізує як:

<g fill='rgb(60.00%, 60.00%, 60.00%)'>
 <text x='2' y='17'>$</text>
 <text x='22' y='17'>u</text>
 <text x='32' y='17'>n</text>
 <text x='42' y='17'>a</text>
 <text x='52' y='17'>m</text>
 <text x='62' y='17'>e</text>
</g>

Це означає, що кернінґа шрифта до уваги не береться і результат виглядає чудернацько.

Сучасний xterm також вміє робити "XHTML Screen Dump" (ув меню Ctrl + Миша-1), який гендериться ув бовзері майже ідеально (з замалим line-height). Додати необхідного стилю до .xhtml файлу ж нескладно, але як конвертнути той xhtml ув svg?

Пассо нумеро уно

Бовзери вміють друкувати ув pdf. Кроум має headless режима, а гооглова бібліотека puppeteer (з дебільним інтерфейсом), має Page#pdf() метода. Нам лише потрібно вибрати розмір в'юпорту та опцію 'друкувати background graphics'.

$ cat html2pdf
#!/usr/bin/env node

import util from 'util'
import fs from 'fs'
import puppeteer from 'puppeteer'

async function pdf(url, output, opt) {
    let bowser = await puppeteer.launch()
    let page = await bowser.newPage()

    let print_settings = Object.assign({
        path: output,
        printBackground: true,
        width: 1920,
        height: 1080,
    }, opt)

    await page.emulateMediaType('screen')
    await page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36')
    try {
        await page.goto(urlise(url), { waitUntil: 'networkidle0' })
        await page.pdf(print_settings)
    } catch (e) {
        err('Error:', e.message)
    }

    await bowser.close()
}

function urlise(s) {
    return (/^https?:/.test(s)) ? s : 'file://' + fs.realpathSync(s)
}

function err(...msg) { console.error(...msg); process.exit(1) }

let args = util.parseArgs({
    options: {
        width: { type: 'string', short: 'w' },
        height: { type: 'string', short: 'h' },
    },
    allowPositionals: true
})

if ( !(args.positionals[0] && args.positionals[1]))
    err('Usage: html2pdf URL output.pdf')

pdf(args.positionals[0], args.positionals[1], args.values)

Скрипта підтримує -w та -h опції і конвертує будь-яку уйоб-сторінку, а не тільки локальні файли. Якщо не виставити user-agent, по-замовчуванню буде HeadlessChrome, на що деякі сайти неприємно реагують.

Пассо нумеро дуе

PDF, на жаль, не буде мати розмір конь тенту, а буде містити borders (як це українською? межа?) Прибрати їх простіше всього Inkscape'ом:

$ inkscape --actions "select-all;fit-canvas-to-selection" 1.pdf -o 2.pdf

Пассо нумеро тре

$ pdf2svg 2.pdf 1.svg

Воно автоматом перетворює текст ув path. Зменшити розмір .svg можна svgcleaner'ом.

На кроці нумеро дуе можна було би зразу конвертнути рельтат ув svg, але він тоді виходить гаргантюажного розміру.

Енівей, фінальний мейкфайла:

$ cat xterm-xhtml-to-svg
#!/usr/bin/make -f

font := monospace

$(if $(and $(i),$(o)),,$(error Usage: xterm-xhtml-to-svg i=1.xhtml o=1.svg))
__dir__ := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))

$(o): $(i).dirty.svg; svgcleaner --quiet $< $@

%.nokogiri.xhtml: $(i)
    ruby -rnokogiri -e 'd = Nokogiri.XML(STDIN.read); d.css("#vt100 > pre").first["style"] = "font-family: '$(call se,$(font))'; line-height: 1.2"; puts d.to_xml' < $< > $@

%.uncropped.pdf: %.nokogiri.xhtml; $(__dir__)/html2pdf $< $@

%.pdf: %.uncropped.pdf
    inkscape --actions "select-all;fit-canvas-to-selection" $< -o $@

%.dirty.svg: %.pdf; pdf2svg $< $@

se = '$(subst ','\'',$1)'
.INTERMEDIATE: $(i).dirty.svg

May 2025

M T W T F S S
   12 34
5678910 11
1213 1415 161718
19202122232425
262728293031 

Expand Cut Tags

No cut tags