forked from GoodStartLabs/AI_Diplomacy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsvg_optimizer.py
More file actions
169 lines (139 loc) · 5.95 KB
/
svg_optimizer.py
File metadata and controls
169 lines (139 loc) · 5.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#!/usr/bin/env python3
"""
SVG Optimizer - A tool to simplify and optimize SVG files
This module provides functions to clean up SVG files by:
1. Removing unnecessary metadata, comments, and custom namespaces
2. Simplifying paths using the svgelements library
3. Standardizing dimensions with viewBox
4. Removing inline styles and unnecessary attributes
Author: AI Assistant
"""
import xml.etree.ElementTree as ET
import io
import re
import os
from svgelements import Path
def simplify_svg(svg_content):
"""
Simplifies and optimizes SVG content by:
1. Removing unnecessary metadata, comments, and hidden elements.
2. Simplifying paths and shapes.
3. Standardizing dimensions (setting viewBox).
Args:
svg_content (str): The SVG content as a string
Returns:
str: The optimized SVG content
"""
# Remove comments before parsing (ElementTree doesn't handle comments well)
svg_content = re.sub(r'<!--[\s\S]*?-->', '', svg_content)
# Parse the SVG content
try:
# Register SVG namespaces to avoid prefix generation
ET.register_namespace('', 'http://www.w3.org/2000/svg')
ET.register_namespace('xlink', 'http://www.w3.org/1999/xlink')
# Parse the SVG
tree = ET.parse(io.StringIO(svg_content))
root = tree.getroot()
except ET.ParseError as e:
return f"Error parsing SVG: {e}"
# 1. Remove metadata, custom elements, and unnecessary elements
for element in list(root): # Iterate over a copy to allow modification
tag = element.tag
if (tag.endswith('}DISPLAY') or
tag.endswith('}ORDERDRAWING') or
tag.endswith('}PROVINCE_DATA') or
tag == '{http://www.w3.org/2000/svg}defs' or
tag == '{http://www.w3.org/2000/svg}metadata' or
tag == '{http://www.w3.org/2000/svg}style'):
root.remove(element)
# Remove jdipNS namespace declaration and attributes as they are custom
jdipns_prefix = '{svg.dtd}'
for element in root.iter():
attrib_to_remove = []
for attrib_name in element.attrib:
if attrib_name.startswith('{svg.dtd}'):
attrib_to_remove.append(attrib_name)
for attrib_name in attrib_to_remove:
del element.attrib[attrib_name]
# 2. Simplify paths and shapes
for path_element in root.findall('.//{http://www.w3.org/2000/svg}path'):
d_attribute = path_element.get('d')
if d_attribute:
try:
# Parse the path data
path = Path(d_attribute)
# Simplify the path using svgelements
# First convert all arcs to cubic bezier curves for better handling
path.approximate_arcs_with_cubics()
# Basic path simplification - remove redundant commands
simplified_d = path.d()
# Set the simplified path back
path_element.set('d', simplified_d)
except Exception as e:
print(f"Path simplification error for path {path_element.get('id', 'unknown')}: {e}")
# Keep the original path if simplification fails
continue
# 3. Standardize dimensions with viewBox
if 'viewBox' not in root.attrib:
# Try to get width and height
width = root.get('width')
height = root.get('height')
if width and height:
# Extract numeric values
width_value = re.match(r'(\d+)', width)
height_value = re.match(r'(\d+)', height)
if width_value and height_value:
width_num = float(width_value.group(1))
height_num = float(height_value.group(1))
root.set('viewBox', f"0 0 {width_num} {height_num}")
# Remove width and height if viewBox is present
if 'viewBox' in root.attrib:
if 'width' in root.attrib:
del root.attrib['width']
if 'height' in root.attrib:
del root.attrib['height']
# 4. Export as clean SVG code snippet
output_io = io.StringIO()
tree.write(output_io, encoding='unicode', xml_declaration=True, short_empty_elements=True)
# Get the output and clean it up
optimized_svg_code = output_io.getvalue()
# Remove DOCTYPE from string output as ElementTree doesn't fully remove it
optimized_svg_code = re.sub(r'<!DOCTYPE[^>]*>', '', optimized_svg_code)
# Remove comments that might have been missed by ElementTree
optimized_svg_code = re.sub(r'<!--[\s\S]*?-->', '', optimized_svg_code)
return optimized_svg_code.strip()
def find_parent(root, element):
"""
Find the parent of an element in the XML tree
Args:
root (Element): The root element of the tree
element (Element): The element to find the parent for
Returns:
Element: The parent element or None if not found
"""
for parent in root.iter():
for child in list(parent):
if child == element:
return parent
return None
if __name__ == "__main__":
# Test the function with a sample SVG
test_svg = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "svg.dtd">
<!-- Comment to be removed -->
<svg color-rendering="optimizeQuality" height="680px" preserveAspectRatio="xMinYMin" version="1.0" viewBox="0 0 1835 1360" width="918px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:jdipNS="svg.dtd">
<jdipNS:DISPLAY>
<jdipNS:ZOOM min="5" max="2200" factor="1.2"/>
</jdipNS:DISPLAY>
<defs>
<style type="text/css"><![CDATA[
/* text */
svg { font-size: 100% }
]]></style>
</defs>
<g id="MapLayer">
<path id="test" d="M 10 10 L 20 20 L 30 30 Z"/>
</g>
</svg>"""
optimized = simplify_svg(test_svg)
print(optimized)