File: test_compatibility.py

package info (click to toggle)
inkscape-textext 1.11.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 10,836 kB
  • sloc: python: 3,471; sh: 26; makefile: 25
file content (265 lines) | stat: -rw-r--r-- 10,336 bytes parent folder | download
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
"""
This file is part of TexText, an extension for the vector
illustration program Inkscape.

Copyright (c) 2006-2025 TexText developers.

TexText is released under the 3-Clause BSD license. See
file LICENSE.txt or go to https://github.com/textext/textext
for full license details.
"""
import os
import pytest
import sys
import textext.base as textext
import tempfile
import subprocess
import shutil
import json
import PIL.Image

if os.name == "nt":
    EXTENSION_DIR = os.path.join(os.getenv("APPDATA"), "inkscape\\extensions\\textext")
    INKSCAPE_EXE = "C:\\Program Files\\Inkscape\\bin\\inkscape.com"
    COMPARE_EXE = ["C:\\Program Files\\ImageMagick-7.0.10-Q8\\magick", "compare"]
else:
    EXTENSION_DIR = os.path.expanduser("~/.config/inkscape/extensions/textext")
    INKSCAPE_EXE = "inkscape"
    COMPARE_EXE = ["compare"]

# Set this to False to keep results in separate pytests_results folder
RESULTS_INTO_TEMPDIR = True


class TempDirectory(object):

    def __init__(self, suffix=""):
        self.__name = None
        self.__suffix = suffix

    def __enter__(self):
        if RESULTS_INTO_TEMPDIR:
            self.__name = tempfile.mkdtemp()
        else:
            dn = os.path.join(os.getcwd(), "pytests_results", self.__suffix)
            if not os.path.exists(dn):
                os.makedirs(dn)  # ToDo: Make use of exist_ok flag when porting to Python 3.7
            self.__name = dn
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if (self.__name is not None) and RESULTS_INTO_TEMPDIR:
            shutil.rmtree(self.__name)

    @property
    def name(self):
        return self.__name


def svg_to_png(svg, png, dpi=None, height=None, render_area="drawing"):
    assert os.path.isfile(svg)
    options = []
    if dpi:
        options.append("--export-dpi=%d" % dpi)
    if height:
        options.append("--export-height=%d" % height)
    if render_area == "drawing":
        options.append("--export-area-drawing")
    elif render_area == "document":
        pass
    else:
        raise RuntimeError("Unknwon export option `%s`" % render_area)

    subprocess.call([INKSCAPE_EXE, "--export-type=png"] + options + ["--export-filename=%s" % png] + [svg],
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    assert os.path.isfile(png)


def images_are_same(png1, png2, fuzz="0%", size_abs_tol=10, size_rel_tol=0.005, pixel_diff_abs_tol=0,
                    pixel_diff_rel_tol=0.0):
    """
    :param (str) png1: path to first image
    :param (str) png2: path to second image
    :param (str) fuzz: colors within this distance are considered equal
    :param (int) size_abs_tol: absolute size tolerance (in pixels)
    :param (float) size_rel_tol: relative size tolerance
    :param (int) pixel_diff_abs_tol: max number of different pixels
    :param (float) pixel_diff_rel_tol: max relative number of different pixels of image size
    :return (bool, str): pair of check result and message
    """

    assert os.path.isfile(png1)
    assert os.path.isfile(png2)

    im1 = PIL.Image.open(png1)
    im2 = PIL.Image.open(png2)

    dw = im2.width - im1.width
    dh = im2.height - im1.height

    w = min(im2.width, im1.width)
    h = min(im2.height, im1.height)

    if dw != 0:
        sys.stderr.write("Images width differ by %d px\n" % dw)

    if dh != 0:
        sys.stderr.write("Images height differ by %d px\n" % dh)

    if dw > size_abs_tol or \
            dh > size_abs_tol or \
            dw > w * size_rel_tol or \
            dh > h * size_rel_tol:
        return False, "Images have too different sizes: %s vs %s\n" % (str(im1.size), str(im2.size))

    if dh != 0 or dw != 0:
        size_abs_tol //= 2
        pixel_diff_abs_tol //= 2
        w //= 2
        h //= 2
        sys.stderr.write("Images are downsampled to (%d, %d)\n" % (w, h))

    im1.resize((w, h), PIL.Image.LANCZOS).save(png1)
    im2.resize((w, h), PIL.Image.LANCZOS).save(png2)

    proc = subprocess.Popen(COMPARE_EXE + ["-channel", "rgba", "-metric", "ae",
                                           "-fuzz", fuzz, png1, png2,
                                           os.devnull if RESULTS_INTO_TEMPDIR else
                                           os.path.join(os.path.dirname(png1), "diff.png")],
                            stderr=subprocess.PIPE, stdout=subprocess.PIPE)

    stdout, stderr = proc.communicate()

    if proc.returncode in [0, 1]:
        try:
            diff_pixels = int(stderr.decode("utf-8"))
        except (ValueError, AttributeError):
            return False, "Can't parse `compare` output"

        if diff_pixels > pixel_diff_abs_tol:
            return False, "diff pixels (%d) > %d" % (diff_pixels, pixel_diff_abs_tol)

        if diff_pixels > w * h * pixel_diff_rel_tol:
            return False, "diff pixels (%d) > W*H*%f (%f)" % (
                diff_pixels, pixel_diff_rel_tol, w * h * pixel_diff_rel_tol)

        return True, "diff pixels (%d)" % diff_pixels
    else:
        if stdout: sys.stdout.write(stdout)
        if stderr: sys.stderr.write(stderr)
        return False, "`compare` return code is %d " % proc.returncode


def is_current_version_compatible(test_id,
                                  svg_original,
                                  svg_modified,
                                  json_config,
                                  converter,
                                  fuzz=None,
                                  pixel_diff_abs_tol=50,
                                  pixel_diff_rel_tol=0.001
                                  ):
    """
    :param (str) test_id: A string characterizing the test. Used in tmp dirs.
    :param (str) svg_original: path to snippet
    :param (str) svg_modified: path to empty svg created with same version of inkscape as `svg_snippet`
    :param (str) json_config: path to json config
    :param (str) converter: "pstoedit" or "pdf2svg"
    :param (str) fuzz: overwrite fuzz from config
    :param (int) pixel_diff_abs_tol: max number of different pixels
    :param (float) pixel_diff_rel_tol: max relative number of different pixels of image size
    """

    assert os.path.isfile(svg_original)
    assert os.path.isfile(svg_modified)
    assert os.path.isfile(json_config)
    assert converter in ["inkscape", "pstoedit", "pdf2svg"]

    with TempDirectory(test_id) as tempdir:
        tmp_dir = tempdir.name

        png1 = os.path.join(tmp_dir, "1.png")
        png2 = os.path.join(tmp_dir, "2.png")

        config = json.load(open(json_config, encoding="utf-8"))
        check_render = config["check"]["render"]

        render_options = {}
        if check_render:
            if check_render["scale-is-preserved"]:
                assert "height" not in check_render
                render_options["dpi"] = check_render.get("dpi", 90)
            else:
                assert "dpi" not in check_render
                render_options["height"] = check_render.get("height", 100)

            render_options["render_area"] = check_render.get("render-area", "document")

        svg_to_png(svg_modified, png1, **render_options)

        # inherit all from original
        mod_args = config["original"]  # type: dict
        # overwrite with modified
        mod_args.update(config["modified"])

        # Assing the preamble file: If no one is specified at all, use the default preamble file
        # from the extension directory. If no file with the specified name exist try to look in the
        # snippet dir. If this fails, too, use the default preamble from the extension dir
        extension_dir_preamble_file = os.path.join(EXTENSION_DIR, "default_packages.tex")
        if "preamble-file" not in mod_args or not mod_args["preamble-file"]:
            mod_args["preamble-file"] = extension_dir_preamble_file
        else:
            if not os.path.isfile(mod_args["preamble-file"]):
                snippet_dir_preamble_file = os.path.join(
                    os.path.dirname(os.path.abspath(json_config)),
                    mod_args["preamble-file"])
                if os.path.isfile(snippet_dir_preamble_file):
                    mod_args["preamble-file"] = snippet_dir_preamble_file
                else:
                    mod_args["preamble-file"] = extension_dir_preamble_file

        if "alignment" not in mod_args:
            mod_args["alignment"] = "middle center"

        # run TexText
        tt = textext.TexText()
        tt.run([
            r"--id=%s" % "content",  # todo: find a TexText node to avoid hard-coded ids
            r"--text=%s" % mod_args["text"],
            r"--scale-factor=%f" % mod_args["scale-factor"],
            r"--preamble-file=%s" % mod_args["preamble-file"],
            r"--alignment=%s" % mod_args["alignment"],
            svg_original], output=os.devnull)

        svg2 = os.path.join(tmp_dir, "svg2.svg")

        tt.document.write(svg2)

        svg_to_png(svg2, png2, **render_options)

        if not fuzz:
            fuzz = config["check"]["compare"].get("fuzz", "0%")

        return images_are_same(png1, png2,
                               fuzz=fuzz,
                               pixel_diff_abs_tol=pixel_diff_abs_tol,
                               pixel_diff_rel_tol=pixel_diff_rel_tol
                               )


def test_compatibility(root, inkscape_version, textext_version, converter, test_case):
    if inkscape_version.startswith("_") or textext_version.startswith("_") or converter.startswith(
            "_") or test_case.startswith("_"):
        pytest.skip("skip %s (remove underscore to enable)" % os.path.join(inkscape_version, textext_version, converter,
                                                                           test_case))
    test_id = "%s-%s-%s-%s" % (inkscape_version, textext_version, converter, test_case)
    result, message = is_current_version_compatible(
        test_id,
        svg_original=os.path.join(root, inkscape_version, textext_version, converter, test_case, "original.svg"),
        svg_modified=os.path.join(root, inkscape_version, textext_version, converter, test_case, "modified.svg"),
        json_config=os.path.join(root, inkscape_version, textext_version, converter, test_case, "config.json"),
        converter=converter
    )
    sys.stderr.write(message + "\n")
    assert result, message