forked from elax46/custom-brand-icons
-
Notifications
You must be signed in to change notification settings - Fork 11
/
svg-to-js.py
158 lines (131 loc) · 6.1 KB
/
svg-to-js.py
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
# Convert SVG icons to JS format for HomeAssistant
import os
from pathlib import Path
from xml.dom import Node
from xml.dom.minidom import parseString
# Icon prefix in Home Assistant
ICONS_PREFIX = "custom"
def circle_to_path(circle):
# Convert circle to path https://www.smashingmagazine.com/2019/03/svg-circle-decomposition-paths/
cx = float(circle.getAttribute('cx'))
cy = float(circle.getAttribute('cy'))
r = float(circle.getAttribute('r'))
return f"M {cx - r}, {cy} a {r},{r} 0 1,0 {r * 2},0 a {r},{r} 0 1,0 -{r * 2},0"
def polygon_to_path(polygon):
# Convert polygon to path https://stackoverflow.com/questions/10717190/convert-svg-polygon-to-path
points = polygon.getAttribute('points').strip()
return f"M{points}z"
def polyline_to_path(polygon):
# Convert polyline to path https://stackoverflow.com/questions/10717190/convert-svg-polygon-to-path
points = polygon.getAttribute('points').strip()
return f"M{points}"
def rect_to_path(rect):
# Convert rect to path https://github.com/elrumordelaluz/element-to-path/blob/master/src/index.js
x = float(rect.getAttribute('x'))
y = float(rect.getAttribute('y'))
w = float(rect.getAttribute('width'))
h = float(rect.getAttribute('height'))
rx = 0 # TODO curves
ry = 0 # TODO curves
return f"M{x + rx} {y} H{x + w - rx} V{y + h - ry} H{x + rx} V{y + ry}z"
def ellipse_to_path(ellipse):
# Convert ellipse to path https://github.com/elrumordelaluz/element-to-path/blob/master/src/index.js
cx = float(ellipse.getAttribute('cx'))
cy = float(ellipse.getAttribute('cy'))
rx = float(ellipse.getAttribute('rx'))
ry = float(ellipse.getAttribute('ry'))
return f"M{cx + rx} {cy} A{rx} {ry} 0 0 1 {cx} {cy + ry} A{rx} {ry} 0 0 1 {cx - rx} {cy} A{rx} {ry} 0 0 1 {cx + rx} {cy} z"
# Beginning of file
js = open("custom-icons.js", 'w')
js.write("""var icons = {
""")
# Browe all icons from icon-svg folder
for filename in sorted(os.listdir("icon-svg")):
icon = os.path.join("icon-svg", filename)
# checking if it is a file
if os.path.isfile(icon):
svg = open(icon, 'r').read()
# Parse SVG as XML
xml = parseString(svg)
icon_name = Path(icon).stem
try:
# Look into all direct child nodes (and nodes inside "g") to detect unsupported SVG content
incompatible = False
nodes = xml.getElementsByTagName("svg")[0].childNodes + xml.getElementsByTagName("svg")[
0].getElementsByTagName("g")
for g_node in xml.getElementsByTagName("svg")[0].getElementsByTagName("g"):
nodes += g_node.childNodes
for node in nodes:
if (node.nodeType == Node.ELEMENT_NODE
and node.nodeName != "path"
and node.nodeName != "g"
and node.nodeName != "circle"
and node.nodeName != "polygon"
and node.nodeName != "polyline"
and node.nodeName != "rect"
and node.nodeName != "ellipse"
):
print("Incompatible icon %s : contains \"%s\" element" % (icon, node.nodeName))
incompatible = True
if incompatible:
# Do not process this icon if incompatible SVG content is found
continue
# Convert viewBox data
viewbox = xml.getElementsByTagName("svg")[0].getAttribute('viewBox').replace(" ", ",")
# Convert supported SVG content, because HomeAssistant only supports data in the "path" format
if (len(xml.getElementsByTagName("path")) >= 1
or len(xml.getElementsByTagName("circle")) >= 1
or len(xml.getElementsByTagName("polygon")) >= 1
or len(xml.getElementsByTagName("polyline")) >= 1
or len(xml.getElementsByTagName("rect")) >= 1
or len(xml.getElementsByTagName("ellipse")) >= 1
):
data = ""
for path in xml.getElementsByTagName("path"):
data += path.getAttribute('d') + " "
for circle in xml.getElementsByTagName("circle"):
data += circle_to_path(circle) + " "
for polygon in xml.getElementsByTagName("polygon"):
data += polygon_to_path(polygon) + " "
for polyline in xml.getElementsByTagName("polyline"):
data += polyline_to_path(polyline) + " "
for rect in xml.getElementsByTagName("rect"):
data += rect_to_path(rect) + " "
for ellipse in xml.getElementsByTagName("ellipse"):
data += ellipse_to_path(ellipse) + " "
# Write result to .js file
js.write(" \"%s\":[%s,\"%s\"],\n" % (icon_name, viewbox, data))
# For debug purposes, also generate an SVG with the converted content
svg_converted = open(os.path.join("icon-svg", "converted", filename), 'w').write(
f"""<svg viewBox="{xml.getElementsByTagName("svg")[0].getAttribute('viewBox')}" xmlns="http://www.w3.org/2000/svg">
<path d="{data}" />
</svg>""")
else:
print("Incompatible icon %s : %d paths" % (icon, len(xml.getElementsByTagName("path"))))
except Exception as e:
print("Incompatible icon %s : %s" % (icon, e))
# End of .js file
js.write(""" }
async function getIcon(name) {
if (!(name in icons)) {
console.log(`Icon "${name}" not available`);
return '';
}
var svgDef = icons[name];
var primaryPath = svgDef[4];
return {
path: primaryPath,
viewBox: svgDef[0] + " " + svgDef[1] + " " + svgDef[2] + " " + svgDef[3]
}
}
async function getIconList() {
return Object.entries(icons).map(([icon]) => ({
name: icon
}));
}
window.customIconsets = window.customIconsets || {};
window.customIconsets[\"""" + ICONS_PREFIX + """\"] = getIcon;
window.customIcons = window.customIcons || {};
window.customIcons[\"""" + ICONS_PREFIX + """\"] = { getIcon, getIconList };
""")
js.close()