Embed images into SVG output

This commit is contained in:
Daniel Rojas 2021-10-16 22:36:42 +02:00
parent e31ed72655
commit 6f9bb67d02
3 changed files with 67 additions and 10 deletions

View File

@ -20,6 +20,7 @@ from wireviz.DataClasses import (
Tweak,
Side,
)
from wireviz.svgembed import embed_svg_images_file
from wireviz.wv_bom import (
HEADER_MPN,
HEADER_PN,
@ -646,13 +647,9 @@ class Harness:
@property
def svg(self):
from io import BytesIO
graph = self.graph
data = BytesIO()
data.write(graph.pipe(format="svg"))
data.seek(0)
return data.read()
return embed_svg_images(graph.pipe(format="svg").decode("utf-8"), Path.cwd())
def output(
self,
@ -671,9 +668,14 @@ class Harness:
if f in ("png", "svg", "html"):
if f == "html": # if HTML format is specified,
f = "svg" # generate SVG for embedding into HTML
# SVG file will be renamed/deleted later
_filename = f"{filename}.tmp" if f == "svg" else filename
# TODO: prevent rendering SVG twice when both SVG and HTML are specified
graph.format = f
graph.render(filename=filename, view=view, cleanup=cleanup)
graph.render(filename=_filename, view=view, cleanup=cleanup)
# embed images into SVG output
if "svg" in fmt or "html" in fmt:
embed_svg_images_file(f"{filename}.tmp.svg")
# GraphViz output
if "gv" in fmt:
graph.save(filename=f"{filename}.gv")
@ -692,8 +694,11 @@ class Harness:
# TODO: implement PDF output
print("PDF output is not yet supported")
# delete SVG if not needed
if "html" in fmt and not "svg" in fmt and not svg_already_exists:
Path(f"{filename}.svg").unlink()
if "html" in fmt and not "svg" in fmt:
# SVG file was just needed to generate HTML
Path(f"{filename}.tmp.svg").unlink()
elif "svg" in fmt:
Path(f"{filename}.tmp.svg").replace(f"{filename}.svg")
def bom(self):
if not self._bom:

52
src/wireviz/svgembed.py Normal file
View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
import base64
import re
from pathlib import Path
from typing import Union
mime_subtype_replacements = {"jpg": "jpeg", "tif": "tiff"}
def embed_svg_images(svg_in: str, base_path: Union[str, Path] = Path.cwd()) -> str:
images_b64 = {} # cache of base64-encoded images
def image_tag(pre: str, url: str, post: str) -> str:
return f'<image{pre} xlink:href="{url}"{post}>'
def replace(match: re.Match) -> str:
imgurl = match["URL"]
if not imgurl in images_b64: # only encode/cache every unique URL once
imgurl_abs = (Path(base_path) / imgurl).resolve()
image = imgurl_abs.read_bytes()
images_b64[imgurl] = base64.b64encode(image).decode("utf-8")
return image_tag(
match["PRE"] or "",
f"data:image/{get_mime_subtype(imgurl)};base64, {images_b64[imgurl]}",
match["POST"] or "",
)
pattern = re.compile(
image_tag(r"(?P<PRE> [^>]*?)?", r'(?P<URL>[^"]*?)', r"(?P<POST> [^>]*?)?"),
re.IGNORECASE,
)
return pattern.sub(replace, svg_in)
def get_mime_subtype(filename: Union[str, Path]) -> str:
mime_subtype = Path(filename).suffix.lstrip(".").lower()
if mime_subtype in mime_subtype_replacements:
mime_subtype = mime_subtype_replacements[mime_subtype]
return mime_subtype
def embed_svg_images_file(
filename_in: Union[str, Path], overwrite: bool = True
) -> None:
filename_in = Path(filename_in).resolve()
filename_out = filename_in.with_suffix(".b64.svg")
filename_out.write_text(
embed_svg_images(filename_in.read_text(), filename_in.parent)
)
if overwrite:
filename_out.replace(filename_in)

View File

@ -37,7 +37,7 @@ def generate_html_output(
html = open_file_read(templatefile).read()
# embed SVG diagram
with open_file_read(f"{filename}.svg") as file:
with open_file_read(f"{filename}.tmp.svg") as file:
svgdata = re.sub(
"^<[?]xml [^?>]*[?]>[^<]*<!DOCTYPE [^>]*>",
"<!-- XML and DOCTYPE declarations from SVG file removed -->",