diff --git a/build.py b/build.py
new file mode 100755
index 00000000..2de1a24f
--- /dev/null
+++ b/build.py
@@ -0,0 +1,512 @@
+#!/usr/bin/env python
+#
+# SVGSlice
+#
+# Released under the GNU General Public License, version 2.
+# Email Lee Braiden of Digital Unleashed at lee.b@digitalunleashed.com
+# with any questions, suggestions, patches, or general uncertainties
+# regarding this software.
+#
+
+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.
+
+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.
+
+Please remember to HIDE the slices layer before exporting, so that the rectangles themselves are not drawn in the final image slices."""
+
+# How it works:
+#
+# Basically, svgslice just parses an SVG file, looking for the tags that define
+# the slices should be, and saves them in a list of rectangles. Next, it generates
+# an XHTML file, passing that out stdout to Inkscape. This will be saved by inkscape
+# under the name chosen in the save dialog. Finally, it calls
+# inkscape again to render each rectangle as a slice.
+#
+# Currently, nothing fancy is done to layout the XHTML file in a similar way to the
+# original document, so the generated pages is essentially just a quick way to see
+# all of the slices in once place, and perhaps a starting point for more layout work.
+#
+
+from optparse import OptionParser
+
+optParser = OptionParser()
+optParser.add_option('-n','--name',dest='name',default='cursor',help='Specified name of theme')
+optParser.add_option('-i','--inherit',dest='inherit',default='Adwaita',help='Specified name of theme wan to inherit DEFAULT:Adwaita')
+optParser.add_option('-d','--debug',action='store_true',dest='debug',help='Enable extra debugging info.')
+optParser.add_option('-t','--test',action='store_true',dest='testing',help='Test mode: leave temporary files for examination.')
+optParser.add_option('-p','--sliceprefix',action='store',dest='sliceprefix',help='Specifies the prefix to use for individual slice filenames.')
+
+from xml.sax import saxutils, make_parser, SAXParseException
+from xml.sax.handler import feature_namespaces
+import os, sys, tempfile, shutil ,glob ,errno
+
+svgFilename = None
+
+def dbg(msg):
+ if options.debug:
+ sys.stderr.write(msg)
+
+def cleanup():
+ if svgFilename != None and os.path.exists(svgFilename):
+ os.unlink(svgFilename)
+
+def fatalError(msg):
+ sys.stderr.write(msg)
+ cleanup()
+ sys.exit(20)
+
+def baseName(svgFilename):
+ base=os.path.basename(svgFilename)
+ base=os.path.splitext(base)[0]
+ return base
+
+def symlink(target, link_name):
+ try:
+ os.symlink(target, link_name)
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ os.remove(link_name)
+ os.symlink(target, link_name)
+ else:
+ raise e
+
+class SVGRect:
+ """Manages a simple rectangular area, along with certain attributes such as a name"""
+ def __init__(self, x1,y1,x2,y2, name=None):
+ self.x1 = x1
+ self.y1 = y1
+ self.x2 = x2
+ self.y2 = y2
+ self.name = name
+ dbg("New SVGRect: (%s)" % name)
+
+ def renderFromSVG(self, svgFName, sliceFName):
+ for size in (24, 28, 32, 40, 48, 56, 64, 72, 80, 88, 96):
+ os.system("mkdir -p build/%s/pngs/%sx%s" % (options.name,size, size))
+ rc = os.system('inkscape --without-gui -w %s -h %s --export-id="%s" --export-png="build/%s/pngs/%sx%s/%s" "%s"' % (size, size, self.name, options.name, size, size, sliceFName, svgFName))
+ if rc > 0:
+ fatalError('ABORTING: Inkscape failed to render the slice.')
+
+
+class SVGHandler(saxutils.xmlreader.handler.ContentHandler):
+ """Base class for SVG parsers"""
+ def __init__(self):
+ self.pageBounds = SVGRect(0,0,0,0)
+
+ def isFloat(self, stringVal):
+ try:
+ return (float(stringVal), True)[1]
+ except (ValueError, TypeError):
+ return False
+
+ def parseCoordinates(self, val):
+ """Strips the units from a coordinate, and returns just the value."""
+ if val.endswith('px'):
+ val = float(val.rstrip('px'))
+ elif val.endswith('pt'):
+ val = float(val.rstrip('pt'))
+ elif val.endswith('cm'):
+ val = float(val.rstrip('cm'))
+ elif val.endswith('mm'):
+ val = float(val.rstrip('mm'))
+ elif val.endswith('in'):
+ val = float(val.rstrip('in'))
+ elif val.endswith('%'):
+ val = float(val.rstrip('%'))
+ elif self.isFloat(val):
+ val = float(val)
+ else:
+ fatalError("Coordinate value %s has unrecognised units. Only px,pt,cm,mm,and in units are currently supported." % val)
+ return val
+
+ def startElement_svg(self, name, attrs):
+ """Callback hook which handles the start of an svg image"""
+ dbg('startElement_svg called')
+ width = attrs.get('width', None)
+ height = attrs.get('height', None)
+ self.pageBounds.x2 = self.parseCoordinates(width)
+ self.pageBounds.y2 = self.parseCoordinates(height)
+
+ def endElement(self, name):
+ """General callback for the end of a tag"""
+ dbg('Ending element "%s"' % name)
+
+
+class SVGLayerHandler(SVGHandler):
+ """Parses an SVG file, extracing slicing rectangles from a "slices" layer"""
+ def __init__(self):
+ SVGHandler.__init__(self)
+ self.svg_rects = []
+ self.layer_nests = 0
+
+ def inSlicesLayer(self):
+ return (self.layer_nests >= 1)
+
+ def add(self, rect):
+ """Adds the given rect to the list of rectangles successfully parsed"""
+ self.svg_rects.append(rect)
+
+ def startElement_layer(self, name, attrs):
+ """Callback hook for parsing layer elements
+
+ Checks to see if we're starting to parse a slices layer, and sets the appropriate flags. Otherwise, the layer will simply be ignored."""
+ dbg('found layer: name="%s" id="%s"' % (name, attrs['id']))
+ if attrs.get('inkscape:groupmode', None) == 'layer':
+ if self.inSlicesLayer() or attrs['inkscape:label'] == 'slices':
+ self.layer_nests += 1
+
+ def endElement_layer(self, name):
+ """Callback for leaving a layer in the SVG file
+
+ Just undoes any flags set previously."""
+ dbg('leaving layer: name="%s"' % name)
+ if self.inSlicesLayer():
+ self.layer_nests -= 1
+
+ def startElement_rect(self, name, attrs):
+ """Callback for parsing an SVG rectangle
+
+ 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
+ rectangles. Otherwise, it will be ignored."""
+ if self.inSlicesLayer():
+ x1 = self.parseCoordinates(attrs['x'])
+ y1 = self.parseCoordinates(attrs['y'])
+ x2 = self.parseCoordinates(attrs['width']) + x1
+ y2 = self.parseCoordinates(attrs['height']) + y1
+ name = attrs['id']
+ rect = SVGRect(x1,y1, x2,y2, name)
+ self.add(rect)
+
+ def startElement(self, name, attrs):
+ """Generic hook for examining and/or parsing all SVG tags"""
+ if options.debug:
+ dbg('Beginning element "%s"' % name)
+ if name == 'svg':
+ self.startElement_svg(name, attrs)
+ elif name == 'g':
+ # inkscape layers are groups, I guess, hence 'g'
+ self.startElement_layer(name, attrs)
+ elif name == 'rect':
+ self.startElement_rect(name, attrs)
+
+ def endElement(self, name):
+ """Generic hook called when the parser is leaving each SVG tag"""
+ dbg('Ending element "%s"' % name)
+ if name == 'g':
+ self.endElement_layer(name)
+
+ def generateXHTMLPage(self):
+ """Generates an XHTML page for the SVG rectangles previously parsed."""
+ write = sys.stdout.write
+ write('\n')
+ write('\n')
+ write('\n')
+ write('
\n')
+ write(' Sample SVGSlice Output\n')
+ write(' \n')
+ write(' \n')
+ write(' 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 :)
\n')
+
+ write(' ')
+ for rect in self.svg_rects:
+ write('
\n' % (sliceprefix + rect.name + '.png', rect.name))
+ write('
')
+
+ write('
')
+
+ write(' \n')
+ write('\n')
+
+
+if __name__ == '__main__':
+ # parse command line into arguments and options
+ (options, args) = optParser.parse_args()
+
+ if len(args) != 1:
+ fatalError("\nCall me with the SVG as a parameter.\n\n")
+ originalFilename = args[0]
+
+ svgFilename = originalFilename + '.svg'
+ shutil.copyfile(originalFilename, svgFilename)
+
+ # setup program variables from command line (in other words, handle non-option args)
+ basename = os.path.splitext(svgFilename)[0]
+
+ if options.sliceprefix:
+ sliceprefix = options.sliceprefix
+ else:
+ sliceprefix = ''
+
+ # initialise results before actually attempting to parse the SVG file
+ svgBounds = SVGRect(0,0,0,0)
+ rectList = []
+
+ # Try to parse the svg file
+ xmlParser = make_parser()
+ xmlParser.setFeature(feature_namespaces, 0)
+
+ # setup XML Parser with an SVGLayerHandler class as a callback parser ####
+ svgLayerHandler = SVGLayerHandler()
+ xmlParser.setContentHandler(svgLayerHandler)
+ try:
+ xmlParser.parse(svgFilename)
+ except SAXParseException as e:
+ 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." % (svgFilename, e.getLineNumber(), e.getColumnNumber(), e.getMessage()))
+
+ # verify that the svg file actually contained some rectangles.
+ if len(svgLayerHandler.svg_rects) == 0:
+ 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:""" + usageMsg)
+ else:
+ dbg("Parsing successful.")
+
+ #svgLayerHandler.generateXHTMLPage()
+
+ # loop through each slice rectangle, and render a PNG image for it
+ for rect in svgLayerHandler.svg_rects:
+ sliceFName = sliceprefix + rect.name + '.png'
+
+ dbg('Saving slice as: "%s"' % sliceFName)
+ rect.renderFromSVG(svgFilename, sliceFName)
+
+ cleanup()
+
+ dbg('\n\nSlicing complete.\n')
+
+ """Generate cursor files based on 96x96 size hotspots."""
+
+ files = (
+ (45.5, 46.8, "X_cursor"),
+ (50, 46, "bd_double_arrow"),
+ (16, 84, "bottom_left_corner"),
+ (84, 84, "bottom_right_corner"),
+ (50, 72, "bottom_side"),
+ (48, 72, "bottom_tee"),
+ (14.5, 10.6, "circle"),
+ (23.1, 74.1, "color-picker"),
+ (18.8, 11.2, "copy"),
+ (44, 44, "cross"),
+ (48, 48, "crossed_circle"),
+ (44, 44, "crosshair"),
+ (45, 50, "dnd-ask"),
+ (45, 50, "dnd-copy"),
+ (45, 50, "dnd-link"),
+ (45, 50, "dnd-move"),
+ (45, 50, "dnd-none"),
+ (46, 46, "dotbox"),
+ (46, 46, "fd_double_arrow"),
+ (48, 50, "grabbing"),
+ (47.6, 28, "hand1"),
+ (36, 20, "hand2"),
+ (27, 15, "left_ptr"),
+ (23.2, 13.2, "left_ptr_watch"),
+ (24, 50, "left_side"),
+ (28, 48, "left_tee"),
+ (19, 11.4, "link"),
+ (21, 67, "ll_angle"),
+ (72, 67, "lr_angle"),
+ (19, 11, "move"),
+ (28.6, 82.6, "pencil"),
+ (48, 48, "plus"),
+ (46, 80, "question_arrow"),
+ (65, 16, "right_ptr"),
+ (76, 50, "right_side"),
+ (72, 48, "right_tee"),
+ (50, 72, "sb_down_arrow"),
+ (47, 46, "sb_h_double_arrow"),
+ (24, 45.6, "sb_left_arrow"),
+ (75, 45, "sb_right_arrow"),
+ (51, 17, "sb_up_arrow"),
+ (45, 45, "sb_v_double_arrow"),
+ (50, 46, "tcross"),
+ (21, 21, "top_left_corner"),
+ (72, 21, "top_right_corner"),
+ (50, 20, "top_side"),
+ (48, 24, "top_tee"),
+ (22, 25, "ul_angle"),
+ (72, 25, "ur_angle"),
+ (48, 46, "watch"),
+ (50, 50, "xterm"),
+ )
+
+ """Genrate CursorList"""
+
+ content="""
+00000000000000020006000e7e9ffc3f progress
+00008160000006810000408080010102 size_ver
+03b6e0fcb3499374a867c041f52298f0 circle
+08e8e1c95fe2fc01f976f1e063a24ccd progress
+3ecb610c1bf2410f44200f48c40d3599 progress
+5c6cd98b3f3ebcb1f9c7f1c204630408 help
+9d800788f1b08800ae810202380a0822 pointer
+640fb0e74195791501fd1ed57b41487f alias
+1081e37283d90000800003c07f3ef6bf copy
+3085a0e285430894940527032f8b26df alias
+4498f0e0c1937ffe01fd06f973665830 dnd-move
+6407b0e94181790501fd1e167b474872 copy
+9081237383d90e509aa00f00170e968f dnd-move
+a2a266d0498c3104214a47bd64ab0fc8 alias
+b66166c04f8c3109214a4fbd64a50fc8 copy
+d9ce0ab605698f320427677b458ad60b help
+e29285e634086352946a0e7090d73106 pointer
+fcf21c00b30f7e3f83fe0dfd12e71cff dnd-move
+alias copy
+all-scroll fleur
+bottom_left_corner size_bdiag
+bottom_right_corner size_fdiag
+cell crosshair
+center_ptr default
+circle not-allowed
+closedhand dnd-move
+col-resize size_hor
+color-picker crosshair
+context-menu default
+copy dnd-move
+cross crosshair
+tcross crosshair
+crossed_circle not-allowed
+dnd-copy copy
+dnd-none dnd-move
+dnd-no-drop not-allowed
+draft pencil
+e-resize size_hor
+forbidden no-drop
+h_double_arrow size_hor
+half-busy progress
+hand1 pointer
+hand2 pointer
+help default
+ibeam text
+left_ptr default
+left_ptr_help help
+left_ptr_watch progress
+left_side left-arrow
+link alias
+move dnd-move
+n-resize size-ver
+no-drop not-allowed
+plus cell
+pointing_hand pointer
+question_arrow help
+right_ptr default
+right_side right-arrow
+row-resize size_ver
+s-resize size_ver
+sb_h_double_arrow size_hor
+sb_v_double_arrow size_ver
+size_all fleur
+split_h col-resize
+split_v row-resize
+top_left_corner size_fdiag
+top_right_corner size_bdiag
+top_side up-arrow
+v_double_arrow size_ver
+vertical-text text
+w-resize size_hor
+watch wait
+whats_this help
+xterm text
+dnd-move default
+down-arrow default
+crosshair default
+fleur default
+left-arrow default
+not-allowed default
+openhand default
+pencil default
+pirate default
+pointer default
+progress default
+right-arrow default
+size-bdiag default
+size-fdiag default
+size-hor default
+size-ver default
+text default
+up-arrow default
+wait default
+x-cursor default
+wayland-cursor default
+zoom-in default
+zoom-out default
+ne-resize size_bdiag
+sw-resize size_bdiag
+nw-resize size_fdiag
+se-resize size_fdiag
+"""
+
+ #create build/config directory
+ os.system("mkdir -p build/config")
+
+ #create & write hotspot
+ for file_ in files:
+ with open("build/config/%s.cursor" % file_[2], "w") as f:
+ for size in (24, 28, 32, 40, 48, 56, 64, 72, 80, 88, 96):
+ f.write("{size} {xhot} {yhot} {size}x{size}/{file_}.png\n".format(
+ size = size,
+ xhot = round(file_[0] / 96 * size),
+ yhot = round(file_[1] / 96 * size),
+ file_= file_[2]
+ ))
+
+ #create CursorList
+ fileCursorList=open("build/cursorList","w")
+ fileCursorList.write(content)
+ fileCursorList.close()
+
+ dbg('\n\nConfig successfully generated\n')
+
+ os.system("mkdir -p %s/cursors"%(options.name))
+
+ for filepath in glob.iglob('build/config/*.cursor'):
+ base=os.path.basename(filepath)
+ base=os.path.splitext(base)[0]
+ print('Genrating %s'%(base))
+ rc = os.system('xcursorgen -p build/%s/pngs "%s" "%s/cursors/%s"' % (options.name, filepath,options.name, base))
+ if rc > 0:
+ fatalError('\n\nABORTING: Xcursorgen failed to generate the cursor.\n')
+ fatalError('\nFAIL: %s %s \n'%(filepath, rc))
+
+ try:
+ cursor_config=open('build/cursorList', 'r')
+ #change wrok dir for symblink
+ os.chdir('%s/cursors'%(options.name))
+ while cursor_config.readline():
+ line = cursor_config.readline()
+ line=line.strip('\n')
+ # print(line)
+ fromlink, tolink =line.split(' ', 1)
+
+ if os.path.exists(fromlink):
+ continue
+
+ print('Linking %s -> %s'%(fromlink, tolink))
+ symlink(tolink, fromlink)
+
+ cursor_config.close()
+ except FileNotFoundError:
+ fatalError('\n\nFAIL: cursorList does not exist\n')
+ print('FAIL: cursorList does not exist')
+
+ dbg('\n\nCursor build complete.\n')
+
+ #create .theme files
+ #go to parent directory here is Options.name
+ os.chdir('..')
+ #cursor.theme content
+ print('\n\nCreating indexing...')
+ fileCursorTheme = open('cursor.theme','w')
+ fileCursorTheme.write('[Icon Theme]\n')
+ fileCursorTheme.write('Name=%s\n'%(options.name))
+ fileCursorTheme.write('Inherits=%s\n'%(options.inherit))
+ fileCursorTheme.close()
+ #index.theme content
+ fileIndexTheme = open('index.theme','w')
+ fileIndexTheme.write('[Icon Theme]\n')
+ fileIndexTheme.write('Name=%s\n'%(options.name))
+
+ for lang in ('ar','bg','ca','cs','da','de','dz','el','en_CA','en_GB','eo','es','et','eu','fi','fr','ga','gl','gu','he','hr','hu','id','it','ja','km','ko','lt','mk','ms','nb','ne','nl','pa','pl','pt_BR','pt','ro','ru','rw','sk','sr_Latn','sr','sv','tr','tt','uk','vi','xh','yi','zh_CN','zh_TW'):
+ fileIndexTheme.write('Name[%s]=%s\n'%(lang, options.name))
+ fileIndexTheme.write('Comment=%s cursor theme'%(options.name))
+ fileIndexTheme.close()
+ print('\nCreating indexing... DONE')
+ dbg('\n\nCursor indexing complete.\n')