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."""
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
49 if svgFilename
!= None and os
.path
.exists(svgFilename
):
50 os
.unlink(svgFilename
)
59 """Manages a simple rectangular area, along with certain attributes such as a name"""
60 def __init__(self
, x1
,y1
,x2
,y2
, name
=None):
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
))
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
))
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
))
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))
80 # fatalError('ABORTING: Inkscape failed to render the slice.')
83 class SVGHandler(saxutils
.DefaultHandler
):
84 """Base class for SVG parsers"""
86 self
.pageBounds
= SVGRect(0,0,0,0)
88 def isFloat(self
, stringVal
):
90 return (float(stringVal
), True)[1]
91 except (ValueError, TypeError), e
:
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
):
111 fatalError("Coordinate value %s has unrecognised units. Only px,pt,cm,mm,and in units are currently supported." % 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"""
130 SVGHandler
.__init
__(self
)
134 def inSlicesLayer(self
):
135 return (self
.layer_nests
>= 1)
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
169 rect
= SVGRect(x1
,y1
, x2
,y2
, name
)
172 def startElement(self
, name
, attrs
):
173 """Generic hook for examining and/or parsing all SVG tags"""
175 dbg('Beginning element "%s"' % name
)
177 self
.startElement_svg(name
, attrs
)
179 # inkscape layers are groups, I guess, hence 'g'
180 self
.startElement_layer(name
, attrs
)
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
)
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')
197 write(' <title>Sample SVGSlice Output</title>\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')
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
))
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>')
213 if __name__
== '__main__':
214 # parse command line into arguments and options
215 (options
, args
) = optParser
.parse_args()
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
232 # initialise results before actually attempting to parse the SVG file
233 svgBounds
= SVGRect(0,0,0,0)
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
)
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:
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
)
267 dbg('Slicing complete.')