Embed images into SVG output
This commit is contained in:
parent
e31ed72655
commit
6f9bb67d02
@ -20,6 +20,7 @@ from wireviz.DataClasses import (
|
|||||||
Tweak,
|
Tweak,
|
||||||
Side,
|
Side,
|
||||||
)
|
)
|
||||||
|
from wireviz.svgembed import embed_svg_images_file
|
||||||
from wireviz.wv_bom import (
|
from wireviz.wv_bom import (
|
||||||
HEADER_MPN,
|
HEADER_MPN,
|
||||||
HEADER_PN,
|
HEADER_PN,
|
||||||
@ -646,13 +647,9 @@ class Harness:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def svg(self):
|
def svg(self):
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
graph = self.graph
|
graph = self.graph
|
||||||
data = BytesIO()
|
return embed_svg_images(graph.pipe(format="svg").decode("utf-8"), Path.cwd())
|
||||||
data.write(graph.pipe(format="svg"))
|
|
||||||
data.seek(0)
|
|
||||||
return data.read()
|
|
||||||
|
|
||||||
def output(
|
def output(
|
||||||
self,
|
self,
|
||||||
@ -671,9 +668,14 @@ class Harness:
|
|||||||
if f in ("png", "svg", "html"):
|
if f in ("png", "svg", "html"):
|
||||||
if f == "html": # if HTML format is specified,
|
if f == "html": # if HTML format is specified,
|
||||||
f = "svg" # generate SVG for embedding into HTML
|
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
|
# TODO: prevent rendering SVG twice when both SVG and HTML are specified
|
||||||
graph.format = f
|
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
|
# GraphViz output
|
||||||
if "gv" in fmt:
|
if "gv" in fmt:
|
||||||
graph.save(filename=f"{filename}.gv")
|
graph.save(filename=f"{filename}.gv")
|
||||||
@ -692,8 +694,11 @@ class Harness:
|
|||||||
# TODO: implement PDF output
|
# TODO: implement PDF output
|
||||||
print("PDF output is not yet supported")
|
print("PDF output is not yet supported")
|
||||||
# delete SVG if not needed
|
# delete SVG if not needed
|
||||||
if "html" in fmt and not "svg" in fmt and not svg_already_exists:
|
if "html" in fmt and not "svg" in fmt:
|
||||||
Path(f"{filename}.svg").unlink()
|
# 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):
|
def bom(self):
|
||||||
if not self._bom:
|
if not self._bom:
|
||||||
|
|||||||
52
src/wireviz/svgembed.py
Normal file
52
src/wireviz/svgembed.py
Normal 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)
|
||||||
@ -37,7 +37,7 @@ def generate_html_output(
|
|||||||
html = open_file_read(templatefile).read()
|
html = open_file_read(templatefile).read()
|
||||||
|
|
||||||
# embed SVG diagram
|
# 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(
|
svgdata = re.sub(
|
||||||
"^<[?]xml [^?>]*[?]>[^<]*<!DOCTYPE [^>]*>",
|
"^<[?]xml [^?>]*[?]>[^<]*<!DOCTYPE [^>]*>",
|
||||||
"<!-- XML and DOCTYPE declarations from SVG file removed -->",
|
"<!-- XML and DOCTYPE declarations from SVG file removed -->",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user