From 6546abca6e71a0fe3f390031212c3e895f5909e9 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Thu, 16 Jul 2026 14:08:43 +0300 Subject: [PATCH] Bonsai: embed bundled fonts in exported drawing SVGs Drawing stylesheets reference the bundled OpenGost Type B TT font by family name only. The SVG writer inlined the stylesheet text into the SVG's style tag but never embedded the actual font data, so exported drawings only rendered with the intended typeface if that font also happened to be installed at the OS level of whoever opened the file, silently falling back to a generic font otherwise. Embed each bundled font as a base64 encoded @font-face rule so drawing SVGs are self contained and render consistently regardless of the viewer's installed fonts. Fixes #5433. Generated with the assistance of an AI coding tool. --- .../bonsai/bim/module/drawing/svgwriter.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/bonsai/bonsai/bim/module/drawing/svgwriter.py b/src/bonsai/bonsai/bim/module/drawing/svgwriter.py index 8093a5e72a..9a3b13c153 100644 --- a/src/bonsai/bonsai/bim/module/drawing/svgwriter.py +++ b/src/bonsai/bonsai/bim/module/drawing/svgwriter.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +import base64 import math import os import shutil @@ -277,6 +278,7 @@ class SvgWriter: self.height = self.raw_height * self.svg_scale def add_stylesheet(self): + self.embed_fonts() paths = self.resource_paths["Stylesheet"] if not paths: return @@ -288,6 +290,31 @@ class SvgWriter: with open(path, "r") as stylesheet: self.svg.defs.add(self.svg.style(stylesheet.read())) + def embed_fonts(self) -> None: + """Embed Bonsai's bundled fonts as base64 encoded @font-face rules. + + Drawing stylesheets (e.g. default.css) reference fonts such as "OpenGost Type + B TT" by family name only. Without embedding the actual font data, the text in + an exported SVG only renders with the intended typeface if that font happens to + also be installed at the OS level of whoever opens the file, silently falling + back to a generic font otherwise. See #5433. + """ + font_faces = [] + formats = (("ttf", "truetype", "font/ttf"), ("otf", "opentype", "font/otf")) + for extension, font_format, mime in formats: + for font_path in tool.Blender.get_data_dir_paths("fonts", f"*.{extension}"): + try: + font_data = base64.b64encode(font_path.read_bytes()).decode("ascii") + except OSError: + print(f"WARNING. Couldn't read bundled font for embedding: {font_path}") + continue + font_faces.append( + f"@font-face {{ font-family: '{font_path.stem}'; " + f"src: url(data:{mime};base64,{font_data}) format('{font_format}'); }}" + ) + if font_faces: + self.svg.defs.add(self.svg.style("\n".join(font_faces))) + def add_markers(self): path = self.resource_paths["Markers"] if not path: