# Author: Jan Mrázek # License: MIT import platform import subprocess import textwrap import os from typing import Union from tempfile import TemporaryDirectory # from PIL import Image from lxml.etree import _ElementTree # type: ignore # Converting SVG to bitmap is a hard problem. We used Wand (and thus # imagemagick) to do the conversion. However, imagemagick is really hard to # configure properly and it breaks often. Therefore, we provide a custom module # that has several conversion strategies that reflect the platform. We also try # to provide descriptive enough message so the user can detect what is wrong. if platform.system() == "Windows": from .convert_windows import detectInkscape else: from .convert_unix import detectInkscape, rsvgSvgToPng def inkscapeSvgToPng(inputFilename: str, outputFilename: str, dpi: int) -> None: """ A strategy to convert an SVG file into a PNG file using Inkscape """ command = [detectInkscape(), "--export-type=png", f"--export-dpi={dpi}", f"--export-filename={outputFilename}", inputFilename] def reportError(message: str) -> None: raise RuntimeError(f"Cannot convert {inputFilename} to {outputFilename}. Inkscape failed with:\n" + textwrap.indent(message, " ")) try: r = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output = r.stdout.decode("utf-8") + "\n" + r.stderr.decode("utf-8") # Inkscape doesn't respect error codes if "Can't open file" in output: reportError(output) except subprocess.CalledProcessError as e: output = e.stdout.decode("utf-8") + "\n" + e.stderr.decode("utf-8") reportError(output) def svgToPng(inputFilename: str, outputFilename: str, dpi: int=300) -> None: """ Convert SVG file into a PNG file based on platform-dependent strategies """ if platform.system() == "Windows": strategies = { "Inkscape": inkscapeSvgToPng } else: strategies = { "RSVG": rsvgSvgToPng, # We prefer it over Inkscape as it is much faster "Inkscape": inkscapeSvgToPng } errors = {} for name, strategy in strategies.items(): try: strategy(inputFilename, outputFilename, dpi) return except Exception as e: errors[name] = str(e) message = "Cannot convert PNG to SVG; all strategies failed:\n" for name, error in errors.items(): m = f"- Strategy '{name}' failed with: {textwrap.indent(error, ' ')}\n" message += textwrap.indent(m, " ") raise RuntimeError(message) def save(image: _ElementTree, filename: str, dpi: int=600, format: str=None) -> None: """ Given an SVG tree or an image, save to a filename. The format is deduced from the extension. """ ftype = os.path.splitext(filename)[1][1:].lower() if format is None else format # if isinstance(image, Image.Image): # if ftype not in ["jpg", "jpeg", "png", "bmp"]: # raise TypeError(f"Cannot save bitmap image into {ftype}") # image.save(filename) # return if isinstance(image, _ElementTree): if ftype == "svg": image.write(filename) return with TemporaryDirectory() as d: svg_filename = os.path.join(d, "image.svg") if ftype == "png": png_filename = filename else: png_filename = os.path.join(d, "image.png") image.write(svg_filename) svgToPng(svg_filename, png_filename, dpi=dpi) if ftype == "png": return # Image.open(png_filename).convert("RGB").save(filename) # return raise TypeError(f"Unknown image type: {type(image)}")