Benchmarks / CPU and I/O (per commit) (push) Waiting to run
Benchmarks / Frame budget (on demand) (push) Waiting to run
Build and test / Desktop (Linux) (push) Waiting to run
Build and test / Android (aarch64) (push) Blocked by required conditions
Build and test / Windows (x86_64, cross) (push) Blocked by required conditions
Build and test / Layer separation (push) Waiting to run
Build and test / Publish the release (push) Blocked by required conditions
Build and test / android-image (push) Waiting to run
🐳 Android image / Build and push (push) Waiting to run
Build and test / windows-image (push) Waiting to run
🐳 Windows image / Build and push (push) Waiting to run
Manual pages / Publish the manual (push) Waiting to run
Traceability / Requirement traces (push) Waiting to run
The page gains a print stylesheet: A4 with a cover, a contents page whose entries carry their page numbers, a chapter to a page with its name at the head of each page and the page count at the foot, figures and table rows never split, and the light palette. A line above the title links the PDF and the EPUB on the published site, absolutely, since an installed copy has none beside it. The Manual pages workflow makes both: tools/manual/pdf.py renders the page with WeasyPrint from JPEG copies of its pictures, which takes the PDF from 28 MB to 8, and pandoc makes the EPUB from the Markdown.
58 lines
2.1 KiB
Python
58 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
"""The manual as a PDF: docs/manual/index.html through its print stylesheet.
|
|
|
|
python3 tools/manual/pdf.py docs/manual out.pdf
|
|
|
|
Needs WeasyPrint (and so Pillow): `pip install weasyprint`. The Manual pages
|
|
workflow runs this and publishes the result beside the page.
|
|
|
|
The page's pictures are PNG screenshots and GIF recordings, 16 MB and 46 MB
|
|
of them, and WeasyPrint embeds a picture as it finds it: losslessly, a GIF
|
|
whole. So the PDF is made from a copy of the page whose pictures are JPEGs
|
|
(a recording's first frame, which is all a page can show) at the size they
|
|
were recorded. Full chroma, because a 4:2:0 JPEG smears the coloured noise
|
|
the AI denoise close-ups exist to show.
|
|
"""
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
|
|
from PIL import Image
|
|
from weasyprint import HTML
|
|
|
|
QUALITY = 88
|
|
|
|
|
|
def as_jpeg(src, dst):
|
|
with Image.open(src) as im:
|
|
im.seek(0)
|
|
im.convert('RGB').save(dst, 'JPEG', quality=QUALITY, subsampling=0, optimize=True)
|
|
|
|
|
|
def main(manual_dir, out):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
os.mkdir(os.path.join(tmp, 'media'))
|
|
renamed = {}
|
|
for name in sorted(os.listdir(os.path.join(manual_dir, 'media'))):
|
|
stem, ext = os.path.splitext(name)
|
|
if ext.lower() not in ('.png', '.gif'):
|
|
continue
|
|
jpg = f'{stem}.{ext[1:].lower()}.jpg' # two pictures may share a stem
|
|
as_jpeg(os.path.join(manual_dir, 'media', name), os.path.join(tmp, 'media', jpg))
|
|
renamed[name] = jpg
|
|
page = open(os.path.join(manual_dir, 'index.html'), encoding='utf-8').read()
|
|
page = re.sub(r'src="media/([^"]+)"',
|
|
lambda m: f'src="media/{renamed.get(m.group(1), m.group(1))}"', page)
|
|
with open(os.path.join(tmp, 'index.html'), 'w', encoding='utf-8') as f:
|
|
f.write(page)
|
|
HTML(os.path.join(tmp, 'index.html')).write_pdf(os.path.join(tmp, 'manual.pdf'))
|
|
shutil.move(os.path.join(tmp, 'manual.pdf'), out)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) != 3:
|
|
sys.exit(__doc__)
|
|
main(sys.argv[1], sys.argv[2])
|