clone

a browser-first diagram editor with live svg previews, local drafts and encrypted, accountless sharing

main
diagramzip / renderers / python / blockdiag-family / src / render.py
render.py 3.2K
1import re2from pathlib import Path3from types import ModuleType45import actdiag6import actdiag.builder7import actdiag.drawer8import actdiag.parser9import blockdiag10import blockdiag.builder11import blockdiag.drawer12import blockdiag.elements13import blockdiag.parser14import nwdiag15import nwdiag.builder16import nwdiag.drawer17import nwdiag.parser18import packetdiag19import packetdiag.builder20import packetdiag.drawer21import packetdiag.parser22import rackdiag23import rackdiag.builder24import rackdiag.drawer25import rackdiag.parser26import seqdiag27import seqdiag.builder28import seqdiag.drawer29import seqdiag.parser30from blockdiag import plugins31from blockdiag.utils import images32from blockdiag.utils.fontmap import FontMap3334from protocol import ProtocolError35from upstream_runtime import register363738MODULES: dict[str, ModuleType] = {39 "blockdiag": blockdiag,40 "seqdiag": seqdiag,41 "actdiag": actdiag,42 "nwdiag": nwdiag,43 "packetdiag": packetdiag,44 "rackdiag": rackdiag,45}4647SIZE = re.compile(r"^(\d+)x(\d+)$")48FONT_PATH = Path(__file__).parent / "assets" / "DejaVuSerif.ttf"495051def _deny_external_image(source, mode="Pillow"):52 if isinstance(source, (str, bytes)):53 raise OSError("External and filesystem images are not supported.")54 return _ORIGINAL_IMAGE_OPEN(source, mode)555657def _deny_image_attribute(_node, _value):58 raise ProtocolError(422, "unsupported_resource", "Remote and filesystem-backed diagram images are not supported.")596061_ORIGINAL_IMAGE_OPEN = images.open62images.open = _deny_external_image63blockdiag.elements.DiagramNode.set_icon = _deny_image_attribute64blockdiag.elements.DiagramNode.set_background = _deny_image_attribute65register()666768def _output_size(options: dict[str, str]) -> list[int] | None:69 value = options.get("size")70 if value is None:71 return None72 matched = SIZE.fullmatch(value)73 if matched is None:74 raise ProtocolError(400, "invalid_options", "options.size must use WIDTHxHEIGHT decimal notation.")75 return [int(dimension) for dimension in matched.groups()]767778def _fontmap() -> FontMap:79 if not FONT_PATH.is_file():80 raise RuntimeError("The bundled DejaVu Serif font is missing.")81 fontmap = FontMap()82 fontmap.set_default_font(str(FONT_PATH))83 return fontmap848586def render_svg(engine: str, source: str, options: dict[str, str] | None = None) -> tuple[str, str]:87 module = MODULES[engine]88 options = options or {}89 try:90 tree = module.parser.parse_string(source)91 if engine == "blockdiag":92 diagram = module.builder.ScreenNodeBuilder.build(tree, None)93 else:94 diagram = module.builder.ScreenNodeBuilder.build(tree)95 drawer = module.drawer.DiagramDraw(96 "SVG",97 diagram,98 None,99 fontmap=_fontmap(),100 code=source,101 antialias="antialias" in options,102 nodoctype="no-doctype" in options,103 transparency=True,104 )105 drawer.draw()106 svg = drawer.save(size=_output_size(options))107 if not isinstance(svg, str):108 raise RuntimeError("The renderer did not return SVG text.")109 return svg, module.__version__110 finally:111 images.cleanup()112 plugins.cleanup()