change license to CCBYSA3
[moblin-cursor-theme.git] / renderpngs.py
blob946b15d3ed417f6f544e7d058f329a4996cf9445
1 #!/usr/bin/env python
3 # SVGSlice
5 # Released under the GNU General Public License, version 2.
6 # Email Lee Braiden of Digital Unleashed at lee.b@digitalunleashed.com
7 # with any questions, suggestions, patches, or general uncertainties
8 # regarding this software.
11 usageMsg = """You need to add a layer called "slices", and draw rectangles on it to represent the areas that should be saved as slices. It helps when drawing these rectangles if you make them translucent.
13 If you name these slices using the "id" field of Inkscape's built-in XML editor, that name will be reflected in the slice filenames.
15 Please remember to HIDE the slices layer before exporting, so that the rectangles themselves are not drawn in the final image slices."""
17 # How it works:
19 # Basically, svgslice just parses an SVG file, looking for the tags that define
20 # the slices should be, and saves them in a list of rectangles. Next, it generates
21 # an XHTML file, passing that out stdout to Inkscape. This will be saved by inkscape
22 # under the name chosen in the save dialog. Finally, it calls
23 # inkscape again to render each rectangle as a slice.
25 # Currently, nothing fancy is done to layout the XHTML file in a similar way to the
26 # original document, so the generated pages is essentially just a quick way to see
27 # all of the slices in once place, and perhaps a starting point for more layout work.
30 from optparse import OptionParser
32 optParser = OptionParser()
33 optParser.add_option('-d','--debug',action='store_true',dest='debug',help='Enable extra debugging info.')
34 optParser.add_option('-t','--test',action='store_true',dest='testing',help='Test mode: leave temporary files for examination.')
35 optParser.add_option('-p','--sliceprefix',action='store',dest='sliceprefix',help='Specifies the prefix to use for individual slice filenames.')
37 from xml.sax import saxutils, make_parser, SAXParseException
38 from xml.sax.handler import feature_namespaces
39 import os, sys, tempfile, shutil
41 svgFilename = None
44 def dbg(msg):
45 if options.debug:
46 sys.stderr.write(msg)
48 def cleanup():
49 if svgFilename != None and os.path.exists(svgFilename):
50 os.unlink(svgFilename)
52 def fatalError(msg):
53 sys.stderr.write(msg)
54 cleanup()
55 sys.exit(20)
58 class SVGRect:
59 """Manages a simple rectangular area, along with certain attributes such as a name"""
60 def __init__(self, x1,y1,x2,y2, name=None):
61 self.x1 = x1
62 self.y1 = y1
63 self.x2 = x2
64 self.y2 = y2
65 self.name = name
66 dbg("New SVGRect: (%s)" % name)
68 def renderFromSVG(self, svgFName, sliceFName):
69 rc = os.system('inkscape --without-gui --export-id="%s" --export-png="pngs/24x24/%s" "%s"' % (self.name, sliceFName, svgFName))
70 if rc > 0:
71 fatalError('ABORTING: Inkscape failed to render the slice.')
72 rc = os.system('inkscape -w 32 -h 32 --without-gui --export-id="%s" --export-png="pngs/32x32/%s" "%s"' % (self.name, sliceFName, svgFName))
73 if rc > 0:
74 fatalError('ABORTING: Inkscape failed to render the slice.')
75 rc = os.system('inkscape -w 48 -h 48 --without-gui --export-id="%s" --export-png="pngs/48x48/%s" "%s"' % (self.name, sliceFName, svgFName))
76 if rc > 0:
77 fatalError('ABORTING: Inkscape failed to render the slice.')
78 # rc = os.system('inkscape -w 128 -h 128 --without-gui --export-id="%s" --export-png="pngs/128x128/%s" "%s"' % (self.name, sliceFName, svgFName))
79 # if rc > 0:
80 # fatalError('ABORTING: Inkscape failed to render the slice.')
83 class SVGHandler(saxutils.DefaultHandler):
84 """Base class for SVG parsers"""
85 def __init__(self):
86 self.pageBounds = SVGRect(0,0,0,0)
88 def isFloat(self, stringVal):
89 try:
90 return (float(stringVal), True)[1]
91 except (ValueError, TypeError), e:
92 return False
94 def parseCoordinates(self, val):
95 """Strips the units from a coordinate, and returns just the value."""
96 if val.endswith('px'):
97 val = float(val.rstrip('px'))
98 elif val.endswith('pt'):
99 val = float(val.rstrip('pt'))
100 elif val.endswith('cm'):
101 val = float(val.rstrip('cm'))
102 elif val.endswith('mm'):
103 val = float(val.rstrip('mm'))
104 elif val.endswith('in'):
105 val = float(val.rstrip('in'))
106 elif val.endswith('%'):
107 val = float(val.rstrip('%'))
108 elif self.isFloat(val):
109 val = float(val)
110 else:
111 fatalError("Coordinate value %s has unrecognised units. Only px,pt,cm,mm,and in units are currently supported." % val)
112 return val
114 def startElement_svg(self, name, attrs):
115 """Callback hook which handles the start of an svg image"""
116 dbg('startElement_svg called')
117 width = attrs.get('width', None)
118 height = attrs.get('height', None)
119 self.pageBounds.x2 = self.parseCoordinates(width)
120 self.pageBounds.y2 = self.parseCoordinates(height)
122 def endElement(self, name):
123 """General callback for the end of a tag"""
124 dbg('Ending element "%s"' % name)
127 class SVGLayerHandler(SVGHandler):
128 """Parses an SVG file, extracing slicing rectangles from a "slices" layer"""
129 def __init__(self):
130 SVGHandler.__init__(self)
131 self.svg_rects = []
132 self.layer_nests = 0
134 def inSlicesLayer(self):
135 return (self.layer_nests >= 1)
137 def add(self, rect):
138 """Adds the given rect to the list of rectangles successfully parsed"""
139 self.svg_rects.append(rect)
141 def startElement_layer(self, name, attrs):
142 """Callback hook for parsing layer elements
144 Checks to see if we're starting to parse a slices layer, and sets the appropriate flags. Otherwise, the layer will simply be ignored."""
145 dbg('found layer: name="%s" id="%s"' % (name, attrs['id']))
146 if attrs.get('inkscape:groupmode', None) == 'layer':
147 if self.inSlicesLayer() or attrs['inkscape:label'] == 'slices':
148 self.layer_nests += 1
150 def endElement_layer(self, name):
151 """Callback for leaving a layer in the SVG file
153 Just undoes any flags set previously."""
154 dbg('leaving layer: name="%s"' % name)
155 if self.inSlicesLayer():
156 self.layer_nests -= 1
158 def startElement_rect(self, name, attrs):
159 """Callback for parsing an SVG rectangle
161 Checks if we're currently in a special "slices" layer using flags set by startElement_layer(). If we are, the current rectangle is considered to be a slice, and is added to the list of parsed
162 rectangles. Otherwise, it will be ignored."""
163 if self.inSlicesLayer():
164 x1 = self.parseCoordinates(attrs['x'])
165 y1 = self.parseCoordinates(attrs['y'])
166 x2 = self.parseCoordinates(attrs['width']) + x1
167 y2 = self.parseCoordinates(attrs['height']) + y1
168 name = attrs['id']
169 rect = SVGRect(x1,y1, x2,y2, name)
170 self.add(rect)
172 def startElement(self, name, attrs):
173 """Generic hook for examining and/or parsing all SVG tags"""
174 if options.debug:
175 dbg('Beginning element "%s"' % name)
176 if name == 'svg':
177 self.startElement_svg(name, attrs)
178 elif name == 'g':
179 # inkscape layers are groups, I guess, hence 'g'
180 self.startElement_layer(name, attrs)
181 elif name == 'rect':
182 self.startElement_rect(name, attrs)
184 def endElement(self, name):
185 """Generic hook called when the parser is leaving each SVG tag"""
186 dbg('Ending element "%s"' % name)
187 if name == 'g':
188 self.endElement_layer(name)
190 def generateXHTMLPage(self):
191 """Generates an XHTML page for the SVG rectangles previously parsed."""
192 write = sys.stdout.write
193 write('<?xml version="1.0" encoding="UTF-8"?>\n')
194 write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">\n')
195 write('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n')
196 write(' <head>\n')
197 write(' <title>Sample SVGSlice Output</title>\n')
198 write(' </head>\n')
199 write(' <body>\n')
200 write(' <p>Sorry, SVGSlice\'s XHTML output is currently very basic. Hopefully, it will serve as a quick way to preview all generated slices in your browser, and perhaps as a starting point for further layout work. Feel free to write it and submit a patch to the author :)</p>\n')
202 write(' <p>')
203 for rect in self.svg_rects:
204 write(' <img src="%s" alt="%s (please add real alternative text for this image)" longdesc="Please add a full description of this image" />\n' % (sliceprefix + rect.name + '.png', rect.name))
205 write(' </p>')
207 write('<p><a href="http://validator.w3.org/check?uri=referer"><img src="http://www.w3.org/Icons/valid-xhtml10" alt="Valid XHTML 1.0!" height="31" width="88" /></a></p>')
209 write(' </body>\n')
210 write('</html>\n')
213 if __name__ == '__main__':
214 # parse command line into arguments and options
215 (options, args) = optParser.parse_args()
217 if len(args) != 1:
218 fatalError("\nCall me with the SVG as a parameter.\n\n")
219 originalFilename = args[0]
221 svgFilename = originalFilename + '.svg'
222 shutil.copyfile(originalFilename, svgFilename)
224 # setup program variables from command line (in other words, handle non-option args)
225 basename = os.path.splitext(svgFilename)[0]
227 if options.sliceprefix:
228 sliceprefix = options.sliceprefix
229 else:
230 sliceprefix = ''
232 # initialise results before actually attempting to parse the SVG file
233 svgBounds = SVGRect(0,0,0,0)
234 rectList = []
236 # Try to parse the svg file
237 xmlParser = make_parser()
238 xmlParser.setFeature(feature_namespaces, 0)
240 # setup XML Parser with an SVGLayerHandler class as a callback parser ####
241 svgLayerHandler = SVGLayerHandler()
242 xmlParser.setContentHandler(svgLayerHandler)
243 try:
244 xmlParser.parse(svgFilename)
245 except SAXParseException, e:
246 fatalError("Error parsing SVG file '%s': line %d,col %d: %s. If you're seeing this within inkscape, it probably indicates a bug that should be reported." % (svgfile, e.getLineNumber(), e.getColumnNumber(), e.getMessage()))
248 # verify that the svg file actually contained some rectangles.
249 if len(svgLayerHandler.svg_rects) == 0:
250 fatalError("""No slices were found in this SVG file. Please refer to the documentation for guidance on how to use this SVGSlice. As a quick summary:
252 """ + usageMsg)
253 else:
254 dbg("Parsing successful.")
256 #svgLayerHandler.generateXHTMLPage()
258 # loop through each slice rectangle, and render a PNG image for it
259 for rect in svgLayerHandler.svg_rects:
260 sliceFName = sliceprefix + rect.name + '.png'
262 dbg('Saving slice as: "%s"' % sliceFName)
263 rect.renderFromSVG(svgFilename, sliceFName)
265 cleanup()
267 dbg('Slicing complete.')