Скріншоти XTerm ув SVG
Oct. 4th, 2024 11:39Як нарід робить векторні скріншоти терміналу? Ось такі:
Я гадав є конвертори аутпуту зі 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