212 lines
7.3 KiB
Python
Executable File
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() |