"""Regenerate the image-bearing fixtures for the asset path (0.10.0). Five documents, one per reader, each carrying a KNOWN number of images so a gate can state "carried N of M" with M read out of the source rather than out of this package. Written in a second file rather than appended to `make_fixtures.py` for one reason: every fixture that file emits is byte-pinned by a test, and the object numbering of the PDF builders is part of those bytes. Adding an XObject to a shared builder would regenerate files whose whole value is that they have not moved. The same policy holds here as there: no generator library. The PNG is written out with `zlib` from the stdlib, the JPEG as a header sequence (the readers copy JPEG bytes through and read nothing but the frame marker, so a decodable photograph would test nothing extra and could not be hand-audited), and the containers are assembled part by part. Run from the repository root: python3 tests/fixtures/make_image_fixtures.py """ from __future__ import annotations import io import struct import zipfile import zlib from pathlib import Path HERE = Path(__file__).parent IMAGES = HERE / "image-inbox" _XML = '' _ZIP_DATE = (2020, 1, 1, 0, 0, 0) def png(width: int, height: int, value: int = 0x40) -> bytes: """A real, single-channel PNG of a flat grey.""" def chunk(kind: bytes, payload: bytes) -> bytes: return ( len(payload).to_bytes(4, "big") + kind + payload + zlib.crc32(kind + payload).to_bytes(4, "big") ) ihdr = struct.pack(">IIBBBBB", width, height, 8, 0, 0, 0, 0) raw = b"".join(b"\x00" + bytes([value] * width) for _ in range(height)) return ( b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b"") ) def jpeg_header(width: int, height: int) -> bytes: """A JPEG's marker sequence: SOI, JFIF, a baseline frame header, EOI. Not a decodable photograph, and that is the point of it. The `pdf` reader passes `DCTDecode` bytes through untouched and reads only the frame header for the size, which is precisely what R761's own table images need -- 29 of its 50 image objects are `DCTDecode`. A fixture that also carried entropy data would exercise no additional line of this package and could not be read byte by byte by a person. """ frame = bytes([8, height >> 8, height & 0xFF, width >> 8, width & 0xFF, 1, 1, 0x11, 0]) return ( b"\xff\xd8" b"\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00" + b"\xff\xc0" + (len(frame) + 2).to_bytes(2, "big") + frame + b"\xff\xd9" ) # --- pdf --------------------------------------------------------------------- # # TWO images on one page, and they are deliberately of the two kinds the # measurement on R761 found: 29 `DCTDecode` objects, which arrive as a finished # JPEG file, and 21 `FlateDecode` ones, which arrive as raw samples with the # colour model in the dictionary beside them and have to be encoded to be # carried at all. A fixture with only one kind would leave half the reader # unexercised, and it is the encoded half that can be silently wrong. PDF_GRAY_WIDTH, PDF_GRAY_HEIGHT = 4, 3 PDF_GRAY_SAMPLES = bytes([0, 60, 120, 180, 20, 80, 140, 200, 40, 100, 160, 255]) PDF_JPEG_WIDTH, PDF_JPEG_HEIGHT = 360, 269 PDF_CONTENT = ( b"BT /F1 12 Tf 20 170 Td (Toleranseklasse er gitt i tabell 84-2) Tj ET\n" b"q 80 0 0 60 20 90 cm /ImFlate Do Q\n" b"q 80 0 0 60 20 20 cm /ImJpeg Do Q\n" ) def build_image_pdf() -> bytes: """A one-page PDF with a Flate image and a DCT image in its resources.""" flate = zlib.compress(PDF_GRAY_SAMPLES, 9) jpeg = jpeg_header(PDF_JPEG_WIDTH, PDF_JPEG_HEIGHT) objects = [ b"<< /Type /Catalog /Pages 2 0 R >>", b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>", b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Contents 4 0 R " b"/Resources << /Font << /F1 5 0 R >> " b"/XObject << /ImFlate 6 0 R /ImJpeg 7 0 R >> >> >>", b"<< /Length " + str(len(PDF_CONTENT)).encode() + b" >>\nstream\n" + PDF_CONTENT + b"endstream", b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>", b"<< /Type /XObject /Subtype /Image /Width " + str(PDF_GRAY_WIDTH).encode() + b" /Height " + str(PDF_GRAY_HEIGHT).encode() + b" /ColorSpace /DeviceGray /BitsPerComponent 8 /Filter /FlateDecode /Length " + str(len(flate)).encode() + b" >>\nstream\n" + flate + b"\nendstream", b"<< /Type /XObject /Subtype /Image /Width " + str(PDF_JPEG_WIDTH).encode() + b" /Height " + str(PDF_JPEG_HEIGHT).encode() + b" /ColorSpace /DeviceGray /BitsPerComponent 8 /Filter /DCTDecode /Length " + str(len(jpeg)).encode() + b" >>\nstream\n" + jpeg + b"\nendstream", ] out = bytearray(b"%PDF-1.4\n") offsets = [] for number, body in enumerate(objects, start=1): offsets.append(len(out)) out += str(number).encode() + b" 0 obj\n" + body + b"\nendobj\n" xref_at = len(out) size = str(len(objects) + 1).encode() out += b"xref\n0 " + size + b"\n0000000000 65535 f \n" for offset in offsets: out += ("%010d 00000 n \n" % offset).encode() out += b"trailer\n<< /Size " + size + b" /Root 1 0 R >>\n" out += b"startxref\n" + str(xref_at).encode() + b"\n%%EOF\n" return bytes(out) # --- html -------------------------------------------------------------------- # # THREE `` and only two of them can be carried. The remote one is the # boundary written as a fixture: this package never opens a socket during # extraction (the network gate is an explicit per-run opt-in and extraction is # not on that path), so a remote source becomes a pointer WITHOUT a file, and # the gate counts it as found-and-not-carried rather than as absent. HTML_DOCUMENT = """ Prosess 84

84 Konstruksjoner av betong

Toleranseklasse for de enkelte konstruksjonsdeler er gitt i tabell 84-2.

Tabell 84-2 Toleranseklasser

Figuren under viser prinsippet.

Figur 84-1 Prinsippskisse
Figur 84-1 Prinsippskisse

Og en som ligger et annet sted:

Ekstern figur """ # --- niso-sts ---------------------------------------------------------------- # # The shape the R761 delivery actually has, measured 2026-09-16: 50 `` # elements, every one a direct child of a ``, none inside a ``, # each carrying a bare file name in `xlink:href` that resolves against a sibling # `graphics/` directory. No `` anywhere near them -- the caption a # human reads is a `

` the extractor already emits on its own line. STS_DOCUMENT = """ R761 Konstruksjoner av betong Toleranser

Toleranseklasse er gitt i tabell 84-2.

Figur 84-1 viser prinsippet.

""" def build_docx() -> bytes: """A `.docx` with one embedded image, its alt text on the drawing.""" image = png(40, 30, value=0x30) parts: dict[str, str | bytes] = { "[Content_Types].xml": _XML + '' + '' + '' + '' + '' + "", "_rels/.rels": _XML + '' + '', "word/_rels/document.xml.rels": _XML + '' + '', "word/media/tabell-84-2.png": image, "word/document.xml": _XML + '' + "Toleranseklasse er gitt i tabell 84-2." + '' + '' + '' + '' + '' + '' + '' + "" + "Etter tabellen gjelder NS-EN 13670." + "", } return build_container(parts) def build_pptx() -> bytes: """A `.pptx` with one titled slide and one embedded image on it.""" image = png(48, 36, value=0x70) parts: dict[str, str | bytes] = { "[Content_Types].xml": _XML + '' + '' + '' + '' + '' + '' + "", "_rels/.rels": _XML + '' + '', "ppt/_rels/presentation.xml.rels": _XML + '' + '', "ppt/presentation.xml": _XML + '' + '', "ppt/slides/_rels/slide1.xml.rels": _XML + '' + '', "ppt/media/skisse.png": image, "ppt/slides/slide1.xml": _XML + '' + "" + '' + "" + '' + '' + "Toleranser" + '' + "" + '' + '' + '' + '' + "", } return build_container(parts) def build_container(parts: dict[str, str | bytes]) -> bytes: out = io.BytesIO() with zipfile.ZipFile(out, "w", compression=zipfile.ZIP_DEFLATED) as archive: for name, payload in parts.items(): info = zipfile.ZipInfo(name, date_time=_ZIP_DATE) info.compress_type = zipfile.ZIP_DEFLATED archive.writestr(info, payload) return out.getvalue() if __name__ == "__main__": (IMAGES / "graphics").mkdir(parents=True, exist_ok=True) # FIVE DISTINCT STEMS. One stem across five extensions is refused by the # door's own SS 3 collision rule -- measured while building this fixture: # `prosess-84.{pdf,docx,pptx}` gave `inbox_slug_collision: 2/7` and two of # the five readers were never exercised at all, with the gate reporting a # carrying defect that was really a fixture defect. written: list[tuple[str, bytes]] = [ ("graphics/tabell-84-2.png", png(120, 90, value=0x20)), ("graphics/figur-84-1.png", png(64, 48, value=0x80)), ("prosess-84-web.html", HTML_DOCUMENT.encode("utf-8")), ("prosess-84-sts.xml", STS_DOCUMENT.encode("utf-8")), ("prosess-84-tabell.pdf", build_image_pdf()), ("prosess-84-notat.docx", build_docx()), ("prosess-84-presentasjon.pptx", build_pptx()), ] for name, payload in written: (IMAGES / name).write_bytes(payload) print(f"wrote image-inbox/{name}")