summaryrefslogtreecommitdiffstats
path: root/tests/__init__.py
blob: ae5cf6a72dc36ecebdbca1d5a8f194c17915157e (plain)
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
"""
Test suite for pydyf.

This module adds a PNG export based on GhostScript, that is released under
AGPL. As "the end user has the ability to opt out of installing the AGPL
version of [Ghostscript] during the install process", and with explicit
aggreement from Artifex, it is OK to distribute this code under BSD.

See https://www.ghostscript.com/license.html.

"""

import io
import os
from pathlib import Path
from subprocess import PIPE, run

from PIL import Image

PIXELS_BY_CHAR = dict(
    _=(255, 255, 255),  # white
    R=(255, 0, 0),  # red
    B=(0, 0, 255),  # blue
    G=(0, 255, 0),  # lime green
    K=(0, 0, 0),  # black
    z=None,  # any color
)


def assert_pixels(document, reference_pixels):
    """Test that the rendered document matches the reference pixels."""

    # Transform the PDF document into a list of RGB tuples
    pdf = io.BytesIO()
    document.write(pdf)
    command = [
        'gs', '-q', '-dNOPAUSE', '-dSAFER', '-sDEVICE=png16m',
        '-r576', '-dDownScaleFactor=8', '-sOutputFile=-', '-']
    png = run(command, input=pdf.getvalue(), stdout=PIPE).stdout
    image = Image.open(io.BytesIO(png))
    pixels = image.getdata()

    # Transform reference drawings into a list of RGB tuples
    lines = tuple(
        line.strip() for line in reference_pixels.splitlines() if line.strip())
    assert len({len(line) for line in lines}) == 1, (
        'The lines of reference pixels don’t have the same length')
    width, height = len(lines[0]), len(lines)
    assert (width, height) == image.size, (
        f'Reference size is {width}×{height}, '
        f'output size is {image.width}×{image.height}')
    reference_pixels = tuple(
        PIXELS_BY_CHAR[char] for line in lines for char in line)

    # Compare pixels
    if pixels != reference_pixels:  # pragma: no cover
        for i, (value, reference) in enumerate(zip(pixels, reference_pixels)):
            if reference is None:
                continue
            if any(value != reference
                   for value, reference in zip(value, reference)):
                name = os.environ.get('PYTEST_CURRENT_TEST')
                name = name.split(':')[-1].split(' ')[0]
                write_png(f'{name}', pixels, width, height)
                reference_pixels = [
                    pixel or (255, 255, 255) for pixel in reference_pixels]
                write_png(f'{name}-reference', reference_pixels, width, height)
                x, y = i % width, i // width
                assert 0, (
                    f'Pixel ({x}, {y}) in {name}: '
                    f'reference rgba{reference}, got rgba{value}')


def write_png(name, pixels, width, height):  # pragma: no cover
    """Take a pixel matrix and write a PNG file."""
    directory = Path(__file__).parent / 'results'
    directory.mkdir(exist_ok=True)
    image = Image.new('RGB', (width, height))
    image.putdata(pixels)
    image.save(directory / f'{name}.png')