symbolic icons: add contact sheet & extractor
[mypaint:mypaint.git] / svg / symbolic-icons-extract.py
1 #!/usr/bin/python
2 # Extracts symbolic icons from a contact sheet using Inkscape.
3 # Copyright (c) 2013 Andrew Chadwick <a.t.chadwick@gmail.com>
4 #
5 # Based on Jakub Steiner's r.rb, rewritten in Python for the MyPaint distrib.
6 # Jakub Steiner <jimmac@gmail.com>
7
8
9 ## Imports
10
11 import os
12 import sys
13 import xml.etree.ElementTree as ET
14 import logging
15 logger = logging.getLogger(__name__)
16 import subprocess
17 import gzip
18
19
20 ## Constants
21
22 CONTACT_SHEET = "symbolic-icons.svgz"
23 OUTPUT_ICONS_ROOT = "../desktop/icons"
24 OUTPUT_THEME = "hicolor"
25 INKSCAPE = "inkscape"
26 SCOUR = "scour"
27 NAMESPACES = { "inkscape": "http://www.inkscape.org/namespaces/inkscape",
28                "svg": "http://www.w3.org/2000/svg", }
29 SUFFIX24 = ":24"
30
31
32 def extract_icon(svg, group_id, output_dir):
33     """Extract one icon"""
34     assert group_id.startswith("mypaint-")
35     logger.info("Extracting %s", group_id)
36     if not os.path.exists(output_dir):
37         os.makedirs(output_dir)
38     if group_id.endswith(SUFFIX24):
39         size = 24
40         icon_name = group_id[:-3] + "-symbolic"
41     else:
42         size = 16
43         icon_name = group_id + "-symbolic"
44     output_tmp = os.path.join(output_dir, ".%s.TMP.svg" % (icon_name,))
45     svg.write(output_tmp)
46     cmd = [INKSCAPE, "-f", output_tmp,   # '--without-gui' doesn't work...
47            "--select", group_id,
48            "--verb=FitCanvasToSelection",
49            "--verb=EditInvertInAllLayers", "--verb=EditDelete",
50            "--verb=EditSelectAll",
51            "--verb=SelectionUnGroup", "--verb=StrokeToPath",
52            "--verb=FileVacuum", "--verb=FileSave", "--verb=FileClose"]
53     subprocess.check_call(cmd)
54     svg = ET.parse(output_tmp)
55     groups = svg.findall(".//svg:g", NAMESPACES)
56     assert groups is not None
57     for group in groups:
58         remove_rects_of_size(group, size)
59     svg.write(output_tmp)
60     output_file = os.path.join(output_dir, "%s.svg" % (icon_name,))
61     cmd = [SCOUR, '--quiet', '-i', output_tmp, '-o', output_file]
62     subprocess.check_call(cmd)
63     os.unlink(output_tmp)
64     logger.info("Wrote %s", output_file)
65
66
67 def remove_rects_of_size(group, size):
68     """Removes the backdrop 16x16 or 24x24 rect from an icon's group"""
69     for rect in group.findall("./svg:rect", NAMESPACES):
70         rw = int(round(float(rect.get("width", 0))))
71         rh = int(round(float(rect.get("height", 0))))
72         if rw == size and rh == size:
73             logger.debug("removing %r (is %dpx)", rect, size)
74             group.remove(rect)
75
76
77 def extract_icons(svg, basedir, group_ids):
78     """Extract icon groups using Inkscape, both 16px scalable & 24x24"""
79     for group_id in group_ids:
80         group = svg.find(".//svg:g[@id='%s']" % (group_id,), NAMESPACES)
81         if group is None:
82             logger.error("No group named %r", group_id)
83         else:
84             outdir = os.path.join(basedir, "scalable", "actions")
85             extract_icon(svg, group_id, outdir)
86         group_id_24 = group_id + SUFFIX24
87         group = svg.find(".//svg:g[@id='%s']" % (group_id_24,), NAMESPACES)
88         if group is None:
89             logger.info("%r: no 24px variant (%r)", group_id, group_id_24)
90         else:
91             outdir = os.path.join(basedir, "24x24", "actions")
92             extract_icon(svg, group_id_24, outdir)
93
94
95 def show_icon_groups(svg):
96     """Print groups from the contact sheet which could be icons"""
97     layers = svg.findall("svg:g[@inkscape:groupmode='layer']", NAMESPACES)
98     for layer in layers:
99         groups = layer.findall(".//svg:g", NAMESPACES)
100         if groups is None:
101             continue
102         for group in layer.findall(".//svg:g", NAMESPACES):
103             group_id = group.get("id")
104             if group_id is None:
105                 continue
106             if ( group_id.startswith("mypaint-") and
107                  not group_id.endswith(SUFFIX24) ):
108                 print group_id
109
110
111 def main():
112     """Main function for the tool"""
113     logging.basicConfig(level=logging.INFO)
114     for prefix, uri in NAMESPACES.iteritems():
115         ET.register_namespace(prefix, uri)
116     basedir = os.path.join(OUTPUT_ICONS_ROOT, OUTPUT_THEME)
117     if not os.path.isdir(basedir):
118         logger.error("No dir named %r", basedir)
119         sys.exit(1)
120     logger.info("Reading %r", CONTACT_SHEET)
121     svg_fp = gzip.open(CONTACT_SHEET, mode='rb')
122     svg = ET.parse(svg_fp)
123     svg_fp.close()
124     group_ids = sys.argv[1:]
125     if group_ids:
126         logger.info("Attempting to extract %d icon(s)", len(group_ids))
127         extract_icons(svg, basedir, group_ids)
128     else:
129         logger.info("Listing groups which (might) represent exportable icons "
130                     "in %r", CONTACT_SHEET)
131         show_icon_groups(svg)
132     logger.info("Done")
133
134
135 if __name__ == '__main__':
136     main()
137