Source code elsie/render/inkscape.py

  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
import contextlib
import logging
import os
import subprocess
import tempfile


@contextlib.contextmanager
def svg_file_input(inkscape, svg: str, binary=False):
    with tempfile.NamedTemporaryFile("wb" if binary else "w", suffix=".svg") as f:
        f.write(svg)
        f.flush()
        inkscape.run_command(f"file-open:{f.name}")
        yield
        inkscape.run_command("file-close")


class InkscapeShell:
    def __init__(self, inkscape_bin: str):

        self.process = subprocess.Popen(
            [inkscape_bin, "--shell"],
            stdout=subprocess.PIPE,
            stdin=subprocess.PIPE,
            stderr=subprocess.PIPE,
            universal_newlines=True,
        )
        self.wait_for_prompt()

        self.run_command("file-close")

    def close(self):
        self.process.stdout.close()

    def wait_for_prompt(self):
        chars = []
        stdout = self.process.stdout
        while True:
            character = stdout.read(1)
            if (
                character == " "
                and chars
                and chars[-1] == ">"
                and (len(chars) == 1 or chars[-2] == "\n")
            ):
                line = "".join(chars[:-2])
                return line
            else:
                chars.append(character)

    def convert_to_pdf(self, source, target: str, type: str):
        with svg_file_input(self, source, binary=True):
            self.run_command("export-area-page")
            # Inkscape 1.1
            self.run_command("export-png-color-mode:RGBA_8")
            self.run_command("export-background-opacity:0")
            self.run_command(f"export-type:{type}")
            self.run_command(f"export-filename:{target}")
            self.run_command("export-do")

    def get_width(self, svg: str, id: str):
        return self.run_query(svg, "query-width", id)

    def get_height(self, svg: str, id: str):
        return self.run_query(svg, "query-height", id)

    def get_x(self, svg: str, id: str):
        return self.run_query(svg, "query-x", id)

    def run_query(self, svg: str, query: str, id: str):
        with svg_file_input(self, svg):
            self.run_command(f"select:{id}")
            value = self.run_command(query)
            try:
                return float(value)
            except ValueError:
                raise Exception(
                    f"Inkscape query executed ({query}) and should return "
                    f"float but returned {repr(value)}"
                )

    def run_command(self, command: str):
        logging.debug(f"Sending {command} to Inkscape")
        self.process.stdin.write(f"{command}\n")
        self.process.stdin.flush()
        output = self.wait_for_prompt().strip()
        if output:
            output = output.splitlines(keepends=False)[-1]
        return output

    def get_version(self):
        return self.run_command("inkscape-version")


def export_by_inkscape(inkscape: InkscapeShell, source: str, target: str, type: str):
    inkscape.convert_to_pdf(source, target, type)
    if not os.path.isfile(target):
        raise Exception(
            "Inkscape should produced file '{}' but it is not found".format(target)
        )