Files
branding/svg_processor.py
2026-03-30 00:26:18 +00:00

212 lines
7.3 KiB
Python
Executable File

import argparse
import json
import base64
import os
from pathlib import Path
import re
import shutil
from lxml import etree
import cairosvg
import scour.scour
from PIL import Image
def convert_to_pixels(value, dpi=96):
"""Convert SVG length values to pixels"""
if value is None:
return 0
value = str(value).strip()
# Handle different units
match = re.match(r'^([\d.]+)(in|cm|mm|pt|px)?$', value)
if not match:
return float(value) if value else 0
num, unit = match.groups()
num = float(num)
if unit == 'in':
return num * dpi
elif unit == 'cm':
return num * dpi / 2.54
elif unit == 'mm':
return num * dpi / 25.4
elif unit == 'pt':
return num * dpi / 72
else: # px or no unit
return num
def get_bounding_box(svg_tree):
"""Calculate bounds of all visible elements"""
max_x = float('-inf')
max_y = float('-inf')
for element in svg_tree.xpath("//*"):
# Skip non-visual elements
tag_name = etree.QName(element).localname
if tag_name in ('defs', 'style', 'metadata', "svg", "page"):
continue
# Get position/size attributes - simplified approach
x = float(element.get('x', 0))
y = float(element.get('y', 0))
# need to detect transform in parent
transformx = 0
transformy = 0
parent = element.getparent()
transform_parent = parent.get('transform', "")
if transform_parent and "translate" in transform_parent:
t = transform_parent.removeprefix("translate").replace("(","").replace(")","")
transformx_str, transformy_str = transform_parent.removeprefix("translate").replace("(","").replace(")","").split(",")
transformx = float(transformx_str)
transformy = float(transformy_str)
transform_element = element.get('transform', "")
if transform_element and "translate" in transform_element:
transformx_str, transformy_str = transform_element.removeprefix("translate").replace("(","").replace(")","").split(",")
transformx = transformx + float(transformx_str)
transformy = transformy + float(transformy_str)
width = float(convert_to_pixels(element.get('width', 0)))
height = float(convert_to_pixels(element.get('height', 0)))
x = x + transformx
y = y + transformy
max_x = max(max_x, x + width)
max_y = max(max_y, y + height)
if max_x == float('inf'): # no elements found
return 100, 100
return max_x , max_y
def update_size(svg_tree, size_dict):
max_x = 0
max_y = 0
vx = 0
vy = 0
if size_dict:
max_x = size_dict.get("x",0)
max_y = size_dict.get("y",0)
vx = size_dict.get("vx",0)
vy = size_dict.get("vy",0)
if max_x ==0 or max_y == 0:
max_x , max_y = get_bounding_box(svg_tree)
svg_root = svg_tree.getroot()
svg_root.set('viewBox', f"{vx} {vy} {max_x} {max_y}")
svg_root.set('width', f"{max_x}")
svg_root.set('height', f"{max_y}")
def remove_elements(svg_tree, remove_ids):
"""Remove elements with specified IDs from the SVG tree."""
namespaces = {
'inkscape': 'http://www.inkscape.org/namespaces/inkscape'
}
for element_id in remove_ids:
element = svg_tree.find(f".//*[@id='{element_id}']")
if element is not None:
element.getparent().remove(element)
element = svg_tree.find(f".//*[@inkscape:label='{element_id}']", namespaces=namespaces)
if element is not None:
element.getparent().remove(element)
def embed_fonts(svg_tree, fonts_dict):
"""Embed fonts specified in fonts_dict into the SVG."""
defs = svg_tree.find(".//{http://www.w3.org/2000/svg}defs")
if defs is None:
defs = etree.SubElement(svg_tree.getroot(), "{http://www.w3.org/2000/svg}defs")
style = etree.SubElement(defs, "{http://www.w3.org/2000/svg}style")
style.text = ""
for font_family, font_path in fonts_dict.items():
if os.path.exists(font_path):
with open(font_path, 'rb') as f:
font_data = base64.b64encode(f.read()).decode('utf-8')
mime_type = "font/ttf" # Assume TTF, adjust if needed
data_url = f"data:{mime_type};base64,{font_data}"
style.text += f"@font-face {{ font-family: '{font_family}'; src: url('{data_url}'); }}\n"
if not os.path.exists(f"/usr/local/share/fonts/{Path(font_path).name}"):
shutil.copy2(font_path, "/usr/local/share/fonts",)
def optimize_svg(svg_tree):
"""Optimize the SVG using scour."""
svg_string = etree.tostring(svg_tree, encoding='unicode')
options = scour.scour.sanitizeOptions()
#options.enable_viewboxing = True
options.enable_id_stripping = True
options.enable_comment_stripping = True
options.shorten_ids = True
options.disable_style_to_xml = True
options.indent_type = None
optimized_svg = scour.scour.scourString(svg_string, options)
return optimized_svg
def main():
parser = argparse.ArgumentParser(description="Process SVG file: remove elements, embed fonts, export optimized formats.")
parser.add_argument("input_svg", help="Path to input SVG file")
parser.add_argument("json_file", help="Path to JSON file with removal and font info")
parser.add_argument("output_dir", help="Output directory for generated files")
args = parser.parse_args()
# Load JSON
with open(args.json_file, 'r') as f:
config = json.load(f)
remove_ids = config.get("remove", [])
fonts_dict = config.get("fonts", {})
size_dict = config.get("size", {})
# Parse SVG
svg_tree = etree.parse(args.input_svg)
# Remove elements
remove_elements(svg_tree, remove_ids)
# Embed fonts
embed_fonts(svg_tree, fonts_dict)
update_size(svg_tree, size_dict)
# Optimize SVG
optimized_svg = optimize_svg(svg_tree)
# Ensure output directory exists
os.makedirs(args.output_dir, exist_ok=True)
prefix = ""
if config.get("file_name"):
prefix = f"{config.get('file_name')}_"
# Write optimized SVG
svg_output = os.path.join(args.output_dir, f"{prefix}optimized.svg")
with open(svg_output, 'w') as f:
f.write(optimized_svg)
# Convert to PNG
png_output = os.path.join(args.output_dir, f"{prefix}output.png")
cairosvg.svg2png(bytestring=optimized_svg.encode('utf-8'), write_to=png_output, unsafe=True)
# Convert to JPEG
jpeg_output = os.path.join(args.output_dir, f"{prefix}output.jpg")
# Cairosvg doesn't directly support JPEG, so use the PNG to JPEG
img = Image.open(jpeg_output.replace('.jpg', '.png'))
img.convert("RGB").save(jpeg_output, 'JPEG')
# Create thumbnail JPEG
thumbnail_output = os.path.join(args.output_dir, f"{prefix}thumbnail.jpg")
img.thumbnail((128, 128)) # Example size, adjust as needed
img.convert("RGB").save(thumbnail_output, 'JPEG')
if __name__ == "__main__":
main()