1#!/usr/bin/env python3
2
3# Copied from gnome-icon-theme and adjusted lightly.
4#
5# GNOME icon theme is distributed under the terms of either
6# GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
7
8import os
9import sys
10import xml.sax
11import subprocess
12
13INKSCAPE = '/usr/bin/inkscape'
14OPTIPNG = '/usr/bin/optipng'
15SRC = 'ui/icons/src'
16OUT = 'ui/icons'
17
18inkscape_process = None
19
20def optimize_png(png_file):
21    if os.path.exists(OPTIPNG):
22        process = subprocess.Popen([OPTIPNG, '-quiet', '-o7', png_file])
23        process.wait()
24
25def wait_for_prompt(process, command=None):
26    if command is not None:
27        process.stdin.write((command+'\n').encode('utf-8'))
28
29    # This is kinda ugly ...
30    # Wait for just a '>', or '\n>' if some other char appearead first
31    output = process.stdout.read(1)
32    if output == b'>':
33        return
34
35    output += process.stdout.read(1)
36    while output != b'\n>':
37        output += process.stdout.read(1)
38        output = output[1:]
39
40def start_inkscape():
41    process = subprocess.Popen([INKSCAPE, '--shell'], bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
42    wait_for_prompt(process)
43    return process
44
45def inkscape_render_rect(icon_file, rect, output_file):
46    global inkscape_process
47    if inkscape_process is None:
48        inkscape_process = start_inkscape()
49    wait_for_prompt(inkscape_process, '%s -i %s -e %s' % (icon_file, rect, output_file))
50    optimize_png(output_file)
51
52class ContentHandler(xml.sax.ContentHandler):
53    ROOT = 0
54    SVG = 1
55    LAYER = 2
56    OTHER = 3
57    TEXT = 4
58    def __init__(self, path, force=False, filter=None):
59        self.stack = [self.ROOT]
60        self.inside = [self.ROOT]
61        self.path = path
62        self.rects = []
63        self.state = self.ROOT
64        self.chars = ""
65        self.force = force
66        self.filter = filter
67
68    def endDocument(self):
69        pass
70
71    def startElement(self, name, attrs):
72        if self.inside[-1] == self.ROOT:
73            if name == "svg":
74                self.stack.append(self.SVG)
75                self.inside.append(self.SVG)
76                return
77        elif self.inside[-1] == self.SVG:
78            if (name == "g" and ('inkscape:groupmode' in attrs) and ('inkscape:label' in attrs)
79               and attrs['inkscape:groupmode'] == 'layer' and attrs['inkscape:label'].startswith('baseplate')):
80                self.stack.append(self.LAYER)
81                self.inside.append(self.LAYER)
82                self.context = None
83                self.icon_name = None
84                self.rects = []
85                return
86        elif self.inside[-1] == self.LAYER:
87            if name == "text" and ('inkscape:label' in attrs) and attrs['inkscape:label'] == 'context':
88                self.stack.append(self.TEXT)
89                self.inside.append(self.TEXT)
90                self.text='context'
91                self.chars = ""
92                return
93            elif name == "text" and ('inkscape:label' in attrs) and attrs['inkscape:label'] == 'icon-name':
94                self.stack.append(self.TEXT)
95                self.inside.append(self.TEXT)
96                self.text='icon-name'
97                self.chars = ""
98                return
99            elif name == "rect":
100                self.rects.append(attrs)
101
102        self.stack.append(self.OTHER)
103
104
105    def endElement(self, name):
106        stacked = self.stack.pop()
107        if self.inside[-1] == stacked:
108            self.inside.pop()
109
110        if stacked == self.TEXT and self.text is not None:
111            assert self.text in ['context', 'icon-name']
112            if self.text == 'context':
113                self.context = self.chars
114            elif self.text == 'icon-name':
115                self.icon_name = self.chars
116            self.text = None
117        elif stacked == self.LAYER:
118            assert self.icon_name
119            assert self.context
120
121            if self.filter is not None and not self.icon_name in self.filter:
122                return
123
124            print (self.context, self.icon_name)
125            for rect in self.rects:
126                width = rect['width']
127                height = rect['height']
128                id = rect['id']
129
130                # dir = os.path.join(OUT, "%sx%s" % (width, height), self.context)
131                dir = os.path.join(OUT, "%sx%s" % (width, height))
132                outfile = os.path.join(dir, self.icon_name+'.png')
133                if not os.path.exists(dir):
134                    os.makedirs(dir)
135                # Do a time based check!
136                if self.force or not os.path.exists(outfile):
137                    inkscape_render_rect(self.path, id, outfile)
138                    sys.stdout.write('.')
139                else:
140                    stat_in = os.stat(self.path)
141                    stat_out = os.stat(outfile)
142                    if stat_in.st_mtime > stat_out.st_mtime:
143                        inkscape_render_rect(self.path, id, outfile)
144                        sys.stdout.write('.')
145                    else:
146                        sys.stdout.write('-')
147                sys.stdout.flush()
148            sys.stdout.write('\n')
149            sys.stdout.flush()
150
151    def characters(self, chars):
152        self.chars += chars.strip()
153
154if len(sys.argv) == 1:
155    if not os.path.exists(OUT):
156        os.mkdir(OUT)
157    print ('Rendering from SVGs in', SRC)
158    for file in os.listdir(SRC):
159        if file[-4:] == '.svg':
160            file = os.path.join(SRC, file)
161            handler = ContentHandler(file)
162            xml.sax.parse(open(file), handler)
163else:
164    file = os.path.join(SRC, sys.argv[1] + '.svg')
165    if len(sys.argv) > 2:
166        icons = sys.argv[2:]
167    else:
168        icons = None
169    if os.path.exists(os.path.join(file)):
170        handler = ContentHandler(file, True, filter=icons)
171        xml.sax.parse(open(file), handler)
172    else:
173        print ("Error: No such file", file)
174        sys.exit(1)
175