Skip to content

Commit

Permalink
Embed images into SVG output
Browse files Browse the repository at this point in the history
  • Loading branch information
formatc1702 committed Oct 16, 2021
1 parent 86c32ce commit c900e24
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 13 deletions.
25 changes: 13 additions & 12 deletions src/wireviz/Harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Options,
Tweak,
)
from wireviz.svgembed import embed_svg_images_file
from wireviz.wv_bom import (
HEADER_MPN,
HEADER_PN,
Expand Down Expand Up @@ -682,13 +683,8 @@ def png(self):

@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,
Expand All @@ -699,17 +695,19 @@ def output(
) -> None:
# graphical output
graph = self.graph
svg_already_exists = Path(
f"{filename}.svg"
).exists() # if SVG already exists, do not delete later
# graphical output
for f in fmt:
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")
Expand All @@ -728,8 +726,11 @@ def output(
# 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:
Expand Down
52 changes: 52 additions & 0 deletions src/wireviz/svgembed.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion src/wireviz/wv_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 -->",
Expand Down

0 comments on commit c900e24

Please sign in to comment.