write_tiff.py

The Python file of the “Every picture a scanner put in one file” example. A file of three sheets and a file holding one photograph, each picture on a page cut to its own size; the photograph goes in exactly as the camera wrote it, since reading software reads a photograph itself.

Python 116 lines

What this example is for

A scanner does not give you one file per sheet. It gives you one file with the whole batch inside, and the same is true of a fax machine: every page of the call is in there, one after another. Software that only looks at the first one quietly loses the rest, and nobody notices until the day somebody needs page four.

What this example shows

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""Places every picture a TIFF holds on a page of its own, and writes the result to
disk.

The Python twin of the `write_tiff` example in Rust.

A TIFF holds any number of pictures, one directory apiece: a scanner puts every sheet
of a batch in one file, and a fax puts every page of a call there. Each of them gets a
page exactly its own size — at 72 dots to the inch one pixel is one point — so the page
that comes back out of a renderer is the picture that went in.

Named nothing, the example reads the two committed fixtures: one holding a photograph
in a single strip, whose bytes go into the document undecoded because PDF reads a
photograph itself, and one holding three pictures whose samples are read and written
again. It opens on a sheet laying all of them out the way a document would.

Usage: python examples/write_tiff.py [out.pdf] [file.tif...]
"""

from __future__ import annotations

import sys
from pathlib import Path

import _licence
import _out

import hqf_pdf

# The files the example reads when the caller names none: the ones committed for the
# tests, so that it runs on any machine.
IMAGES = Path(__file__).resolve().parents[2] / "hqf-pdf" / "tests" / "images"
DEFAULT_FILES = [IMAGES / "photograph.tif", IMAGES / "pages.tif"]

# How wide the sheet the showcase is laid out on is.
A4_WIDTH = 595.276

# The side of the box each picture of the showcase is fitted into, and how far apart the
# boxes stand.
BOX_SIDE = 120.0
BOX_STEP = 130.0


def sheet_of_all(handles: list) -> hqf_pdf.Page:
    """One sheet holding every picture the files gave, each fitted whole into a box of
    one size and standing on the same line as the rest."""
    content = hqf_pdf.Content()

    content.set_fill(hqf_pdf.Rgb(0.93, 0.94, 0.96))
    content.rect(0.0, 700.0, A4_WIDTH, 142.0)
    content.fill()

    left = 60.0
    for handle in handles:
        width, height = handle.fit_within(BOX_SIDE, BOX_SIDE)
        content.draw_image(handle, left, 600.0, width, height)
        left += BOX_STEP

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    return page


def main() -> None:
    out = _out.output_path("tiff")

    named = [Path(argument) for argument in sys.argv[2:]]
    showcase = not named
    paths = DEFAULT_FILES if showcase else named

    doc = hqf_pdf.Document()
    doc.set_license(_licence.licensed())
    doc.set_info("Title", "hqf-pdf TIFF sample")

    handles = []
    for path in paths:
        data = path.read_bytes()
        held = hqf_pdf.Image.tiff_pages(data)
        print(f"{path}: {held} picture(s)")

        for page in range(held):
            image = hqf_pdf.Image.from_tiff_page(data, page)
            resolution = image.resolution
            declared = (
                f"{resolution.x} by {resolution.y} dots to the inch"
                if resolution is not None
                else "stating no resolution of its own"
            )
            print(
                f"  {page + 1}: {image.width}x{image.height} {image.color_space!r},"
                f" {image.bits_per_component} bits a component, {declared}"
            )
            handles.append(doc.add_image(image))

    if showcase:
        doc.add_page(sheet_of_all(handles))
    for handle in handles:
        # A page the size of the picture, with the picture filling it. The size is the
        # one the file itself asks for; a file asking for none is laid down one pixel to
        # the point.
        width, height = handle.size
        content = hqf_pdf.Content()
        content.draw_image(handle, 0.0, 0.0, width, height)

        sheet = hqf_pdf.Page(width, height)
        sheet.set_content(content)
        doc.add_page(sheet)

    pdf = doc.to_bytes()
    Path(out).parent.mkdir(parents=True, exist_ok=True)
    Path(out).write_bytes(pdf)

    print(f"wrote {out}: {len(pdf)} bytes, {doc.page_count} page(s)")


if __name__ == "__main__":
    main()