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.
This commit is contained in:
Petru Conduraru
2026-07-16 14:08:43 +03:00
parent 821cf7b671
commit 6546abca6e
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
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: