Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embed images in SVG output #189

Merged
merged 1 commit into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions src/wireviz/Harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
Tweak,
Side,
)
from wireviz.svgembed import embed_svg_images_file
from wireviz.wv_bom import (
HEADER_MPN,
HEADER_PN,
Expand Down Expand Up @@ -646,13 +647,9 @@ 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 @@ -671,9 +668,14 @@ def output(
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 @@ -692,8 +694,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