From 48af1fafa90dfc649f9f62241288b5f9d269f4e9 Mon Sep 17 00:00:00 2001 From: KAiZ <24286590+KaizIqbal@users.noreply.github.com> Date: Thu, 7 Nov 2019 09:51:56 +0530 Subject: [PATCH] buid files added --- anicursorgen.py | 418 +++++++++++++++++++++++++++ build.py | 512 --------------------------------- render-cursors.py | 714 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1132 insertions(+), 512 deletions(-) create mode 100755 anicursorgen.py delete mode 100755 build.py create mode 100755 render-cursors.py diff --git a/anicursorgen.py b/anicursorgen.py new file mode 100755 index 00000000..3bebcdc3 --- /dev/null +++ b/anicursorgen.py @@ -0,0 +1,418 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# anicursorgen +# Copyright (C) 2015 Руслан Ижбулатов +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#from __future__ import print_function + +import sys +import os +import argparse +import shlex +import io +import struct +import math +from PIL import Image +from PIL import ImageFilter + +p = struct.pack + +program_name = 'anicursorgen' +program_version = 1.0 + + +def main(): + parser = argparse.ArgumentParser(description='Creates .ani or .cur files from separate images and input metadata.', + add_help=False) + parser.add_argument('-V', '--version', action='version', version='{}-{}'.format(program_name, program_version), + help='Display the version number and exit.') + parser.add_argument('-h', '-?', action='help', + help='Display the usage message and exit.') + parser.add_argument('-p', '--prefix', metavar='dir', default=None, + help='Find cursor images in the directory specified by dir. If not specified, the current directory is used.') + parser.add_argument('-s', '--add-shadows', action='store_true', dest='add_shadows', default=False, + help='Generate shadows for cursors (disabled by default).') + parser.add_argument('-n', '--no-shadows', action='store_false', dest='add_shadows', default=False, + help='Do not generate shadows for cursors (put after --add-shadows to cancel its effect).') + + shadows = parser.add_argument_group( + title='Shadow generation', description='Only relevant when --add-shadows is given') + + shadows.add_argument('-r', '--right-shift', metavar='%', type=float, default=9.375, + help='Shift shadow right by this percentage of the canvas size (default is 9.375).') + shadows.add_argument('-d', '--down-shift', metavar='%', type=float, default=3.125, + help='Shift shadow down by this percentage of the canvas size (default is 3.125).') + shadows.add_argument('-b', '--blur', metavar='%', type=float, default=3.125, + help='Blur radius, in percentage of the canvas size (default is 3.125, set to 0 to disable blurring).') + shadows.add_argument('-c', '--color', metavar='%', default='0x00000040', + help='Shadow color in 0xRRGGBBAA form (default is 0x00000040).') + + parser.add_argument('input_config', default='-', metavar='input-config [output-file]', nargs='?', + help='Input config file (stdin by default).') + parser.add_argument('output_file', default='-', metavar='', nargs='?', + help='Output cursor file (stdout by default).') + + args = parser.parse_args() + + try: + if args.color[0] != '0' or args.color[1] not in ['x', 'X'] or len(args.color) != 10: + raise ValueError + args.color = (int(args.color[2:4], 16), int(args.color[4:6], 16), int( + args.color[6:8], 16), int(args.color[8:10], 16)) + except: + print("Can't parse the color '{}'".format(args.color), file=sys.stderr) + parser.print_help() + return 1 + + if args.prefix is None: + args.prefix = os.getcwd() + + if args.input_config == '-': + input_config = sys.stdin + else: + input_config = open(args.input_config, 'rb') + + if args.output_file == '-': + output_file = sys.stdout + else: + output_file = open(args.output_file, 'wb') + + result = make_cursor_from(input_config, output_file, args) + + input_config.close() + output_file.close() + + return result + + +def make_cursor_from(inp, out, args): + frames = parse_config_from(inp, args.prefix) + + animated = frames_have_animation(frames) + + if animated: + result = make_ani(frames, out, args) + else: + buf = make_cur(frames, args) + copy_to(out, buf) + result = 0 + + return result + + +def copy_to(out, buf): + buf.seek(0, io.SEEK_SET) + while True: + b = buf.read(1024) + if len(b) == 0: + break + out.write(b) + + +def frames_have_animation(frames): + sizes = set() + + for frame in frames: + if frame[4] == 0: + continue + if frame[0] in sizes: + return True + sizes.add(frame[0]) + + return False + + +def make_cur(frames, args, animated=False): + buf = io.BytesIO() + buf.write(p(' f2[0]: + return 1 + else: + return 0 + + frames = sorted(frames, frame_size_cmp, reverse=True) + + for frame in frames: + width = frame[0] + if width > 255: + width = 0 + height = width + buf.write(p('= len(framesets): + framesets.append([]) + + framesets[counter].append(frame) + counter += 1 + + for i in range(1, len(framesets)): + if len(framesets[i - 1]) != len(framesets[i]): + print("Frameset {} has size {}, expected {}".format( + i, len(framesets[i]), len(framesets[i - 1])), file=sys.stderr) + return None + + for frameset in framesets: + for i in range(1, len(frameset)): + if frameset[i - 1][4] != frameset[i][4]: + print("Frameset {} has duration {} for framesize {}, but {} for framesize {}".format( + i, frameset[i][4], frameset[i][0], frameset[i - 1][4], frameset[i - 1][0]), file=sys.stderr) + return None + + def frameset_size_cmp(f1, f2): + if f1[0][0] < f2[0][0]: + return -1 + elif f1[0][0] > f2[0][0]: + return 1 + else: + return 0 + + framesets = sorted(framesets, frameset_size_cmp, reverse=True) + + return framesets + + +def make_ani(frames, out, args): + framesets = make_framesets(frames) + if framesets is None: + return 1 + + buf = io.BytesIO() + + buf.write(b'RIFF') + riff_len_pos = buf.seek(0, io.SEEK_CUR) + buf.write(p(' 4: + try: + duration = int(words[4]) + except: + continue + else: + duration = 0 + + frames.append((size, hotx, hoty, filename, duration)) + + return frames + + +def create_shadow(orig, args): + blur_px = orig.size[0] / 100.0 * args.blur + right_px = int(orig.size[0] / 100.0 * args.right_shift) + down_px = int(orig.size[1] / 100.0 * args.down_shift) + + shadow = Image.new('RGBA', orig.size, (0, 0, 0, 0)) + shadowize(shadow, orig, args.color) + shadow.load() + + if args.blur > 0: + crop = (int(math.floor(-blur_px)), int(math.floor(-blur_px)), + orig.size[0] + int(math.ceil(blur_px)), orig.size[1] + int(math.ceil(blur_px))) + right_px += int(math.floor(-blur_px)) + down_px += int(math.floor(-blur_px)) + shadow = shadow.crop(crop) + flt = ImageFilter.GaussianBlur(blur_px) + shadow = shadow.filter(flt) + shadow.load() + + shadowed = Image.new('RGBA', orig.size, (0, 0, 0, 0)) + shadowed.paste(shadow, (right_px, down_px)) + shadowed.crop((0, 0, orig.size[0], orig.size[1])) + shadowed = Image.alpha_composite(shadowed, orig) + + return 0, shadowed + + +def shadowize(shadow, orig, color): + o_pxs = orig.load() + s_pxs = shadow.load() + for y in range(orig.size[1]): + for x in range(orig.size[0]): + o_px = o_pxs[x, y] + if o_px[3] > 0: + s_pxs[x, y] = (color[0], color[1], color[2], + int(color[3] * (o_px[3] / 255.0))) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/build.py b/build.py deleted file mode 100755 index 2de1a24f..00000000 --- a/build.py +++ /dev/null @@ -1,512 +0,0 @@ -#!/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(' %s (please add real alternative text for this image)\n' % (sliceprefix + rect.name + '.png', rect.name)) - write('

') - - write('

Valid XHTML 1.0!

') - - 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') diff --git a/render-cursors.py b/render-cursors.py new file mode 100755 index 00000000..0957df76 --- /dev/null +++ b/render-cursors.py @@ -0,0 +1,714 @@ +#!/usr/bin/env python3 +# +# 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('-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.') +optParser.add_option('-r','--remove-shadows',action='store_true',dest='remove_shadows',help='Remove shadows the cursors have.') +optParser.add_option('-o','--hotspots',action='store_true',dest='hotspots',help='Produce hotspot images and hotspot datafiles.') +optParser.add_option('-s','--scales',action='store_true',dest='scales',help='Produce 125% (Large) and 150% (Extra Large) scaled versions of each image as well.') +optParser.add_option('-m','--min-canvas-size',action='store',type='int',dest='min_canvas_size',default=-1, help='Cursor canvas must be at least this big (defaults to -1).') +optParser.add_option('-f','--fps',action='store',type='int',dest='fps',default=60,help='Assume that all animated cursors have this FPS (defaults to 60).') +optParser.add_option('-a','--anicursorgen',action='store_true',dest='anicur',default=False,help='Assume that anicursorgen will be used to assemble cursors (xcursorgen is assumed by default).') +optParser.add_option('-c','--corner-align',action='store_true',dest='align_corner',default=False,help='Align cursors to the top-left corner (by default they are centered).') +optParser.add_option('-i','--invert',action='store_true',dest='invert',default=False,help='Invert colors (disabled by default).') +optParser.add_option('-n','--number-of-renderers',action='store',type='int',dest='number_of_renderers',default=1, help='Number of renderer instances run in parallel. Defaults to 1. Set to 0 for autodetection.') + +from xml.sax import saxutils, make_parser, SAXParseException, handler, xmlreader +from xml.sax.handler import feature_namespaces +import os, sys, tempfile, shutil, subprocess +import re +from threading import Thread +from PIL import Image +import multiprocessing +import io + +svgFilename = None +hotsvgFilename = None +sizes = [24,32,48,64,96] +scale_pairs = [(1.25, 's1'), (1.50, 's2')] +mode_shadows = ['shadows'] +mode_hotspots = ['hotspots'] +mode_slices = ['slices'] +mode_invert = ['invert'] + +def natural_sort(l): + convert = lambda text: int(text) if text.isdigit() else text.lower() + alphanum_key = lambda key: [ convert(c) for c in re.split(b'([0-9]+)', key) ] + return sorted(l, key = alphanum_key) + +def dbg(msg): + if options.debug: + sys.stderr.write(msg+'\n') + +def cleanup(): + global inkscape_instances + for inkscape, inkscape_stderr, inkscape_stderr_thread in inkscape_instances: + inkscape.communicate (('quit\n').encode()) + del inkscape + del inkscape_stderr_thread + del inkscape_stderr + del inkscape_instances + if svgFilename != None and os.path.exists(svgFilename): + os.unlink(svgFilename) + if hotsvgFilename != None and os.path.exists(hotsvgFilename): + os.unlink(hotsvgFilename) + +def fatalError(msg): + sys.stderr.write(msg) + cleanup() + sys.exit(20) + +def stderr_reader(inkscape, inkscape_stderr): + try: + while True: + line = inkscape_stderr.readline() + if line and len (line.rstrip ('\n').rstrip ('\r')) > 0: + fatalError('ABORTING: Inkscape failed to render a slice: {}'.format (line)) + elif line: + print (("STDERR> {}".format (line))) + else: + raise EOFError + except EOFError as error: + print(error) + +def find_hotspot (hotfile): + img = Image.open(hotfile) + pixels = img.load() + reddest = [-1, -1, -999999] + for y in range(img.size[1]): + for x in range(img.size[0]): + redness = pixels[x,y][0] - pixels[x,y][1] - pixels[x,y][2] + if redness > reddest[2]: + reddest = [x, y, redness] + return (reddest[0] + 1, reddest[1] + 1) + +def cropalign (size, filename): + img = Image.open (filename) + content_dimensions = img.getbbox () + if content_dimensions is None: + content_dimensions = (0, 0, img.size[0], img.size[1]) + hcropped = content_dimensions[2] - content_dimensions[0] + vcropped = content_dimensions[3] - content_dimensions[1] + if hcropped > size or vcropped > size: + if hcropped > size: + left = (hcropped - size) / 2 + right = (hcropped - size) - left + else: + left = 0 + right = 0 + if vcropped > size: + top = (vcropped - size) / 2 + bottom = (vcropped - size) - top + else: + top = 0 + bottom = 0 + content_dimensions = (content_dimensions[0] + left, content_dimensions[1] + top, content_dimensions[2] - right, content_dimensions[3] - bottom) + sys.stderr.write ("WARNING: {} is too big to be cleanly cropped to {} ({}x{} at best), cropping to {}x{}!\n".format (filename, size, hcropped, vcropped, content_dimensions[2] - content_dimensions[0], content_dimensions[3] - content_dimensions[1])) + sys.stderr.flush () + if options.testing: + img.save (filename + ".orig.png", "png") + dbg("{} content is {} {} {} {}".format (filename, content_dimensions[0], content_dimensions[1], content_dimensions[2], content_dimensions[3])) + cropimg = img.crop ((content_dimensions[0], content_dimensions[1], content_dimensions[2], content_dimensions[3])) + pixels = cropimg.load () + if options.testing: + cropimg.save (filename + ".crop.png", "png") + if options.align_corner: + expimg = cropimg.crop ((0, 0, size, size)) + result = (content_dimensions[0], content_dimensions[1]) + else: + hslack = size - cropimg.size[0] + vslack = size - cropimg.size[1] + left = hslack / 2 + top = vslack / 2 + expimg = cropimg.crop ((-left, -top, size - left, size - top)) + result = (content_dimensions[0] - left, content_dimensions[1] - top) + pixels = expimg.load () + if options.invert: + negative (expimg) + expimg.save (filename, "png") + del cropimg + del img + return result + +def cropalign_hotspot (new_base, size, filename): + if new_base is None: + return + img = Image.open (filename) + expimg = img.crop ((new_base[0], new_base[1], new_base[0] + size, new_base[1] + size)) + pixels = expimg.load () + expimg.save (filename, "png") + del img + +def negative (img): + pixels = img.load () + for y in range (0, img.size[1]): + for x in range (0, img.size[0]): + r, g, b, a = pixels[x,y] + pixels[x,y] = (255 - r, 255 - g, 255 - b, a) + +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, slicename, skipped, roundrobin, hotsvgFName): + + def do_res (size, output, svgFName, skipped, roundrobin): + global inkscape_instances + if os.path.exists (output): + skipped[output] = True + return + command = '-w {size} -h {size} --export-id="{export_id}" --export-png="{export_png}" {svg}\n'.format (size=size, export_id=self.name, export_png=output, svg=svgFName) + dbg("Command: {}".format (command)) + inkscape_instances[roundrobin[0]][0].stdin.write ((command).encode()) + + pngsliceFName = slicename + '.png' + hotsliceFName = slicename + '.hotspot.png' + + dbg('Saving slice as: "%s"' % pngsliceFName) + for i, size in enumerate (sizes): + subdir = 'bitmaps/{}x{}'.format (size, size) + if not os.path.exists (subdir): + os.makedirs (subdir) + relslice = '{}/{}'.format (subdir, pngsliceFName) + do_res (size, relslice, svgFName, skipped, roundrobin) + if options.hotspots: + hotrelslice = '{}/{}'.format (subdir, hotsliceFName) + do_res (size, hotrelslice, hotsvgFName, skipped, roundrobin) + for scale in scale_pairs: + subdir = 'bitmaps/{}x{}_{}'.format (size, size, scale[1]) + relslice = '{}/{}'.format (subdir, pngsliceFName) + if not os.path.exists (subdir): + os.makedirs (subdir) + scaled_size = int (size * scale[0]) + do_res (scaled_size, relslice, svgFName, skipped, roundrobin) + if options.hotspots: + hotrelslice = '{}/{}'.format (subdir, hotsliceFName) + do_res (scaled_size, hotrelslice, hotsvgFName, skipped, roundrobin) + # This is not inside do_res() because we want each instance to work all scales in case scales are enabled, + # otherwise instances that get mostly smallscale renders will finish up way before the others + roundrobin[0] += 1 + if roundrobin[0] >= options.number_of_renderers: + roundrobin[0] = 0 + +def get_next_size (index, current_size): + if index % 2 == 0: + # 24->32, 48->64, 96->128, 192->256 + return (current_size * 4) / 3 + else: + # 32->48, 64->96, 128->192, 256->384 + return (current_size * 3) / 2 + +def get_csize (index, current_size): + size = current_size + if len (scale_pairs) > 0: + size = get_next_size (index, size) + return max (options.min_canvas_size, size) + +def postprocess_slice (slicename, skipped): + pngsliceFName = slicename + '.png' + hotsliceFName = slicename + '.hotspot.png' + + for i, size in enumerate (sizes): + subdir = 'bitmaps/{}x{}'.format (size, size) + relslice = '{}/{}'.format (subdir, pngsliceFName) + csize = get_csize (i, size) + if relslice not in skipped: + # new_base = cropalign (csize, relslice) + if options.hotspots: + hotrelslice = '{}/{}'.format (subdir, hotsliceFName) + # cropalign_hotspot (new_base, csize, hotrelslice) + for scale in scale_pairs: + subdir = 'bitmaps/{}x{}_{}'.format (size, size, scale[1]) + relslice = '{}/{}'.format (subdir, pngsliceFName) + if relslice not in skipped: + # new_base = cropalign (csize, relslice) + if options.hotspots: + hotrelslice = '{}/{}'.format (subdir, hotsliceFName) + # cropalign_hotspot (new_base, csize, hotrelslice) + +def write_xcur(slicename): + pngsliceFName = slicename + '.png' + hotsliceFName = slicename + '.hotspot.png' + + framenum = -1 + if slicename[-5:].startswith ('_'): + try: + framenum = int (slicename[-4:]) + slicename = slicename[:-5] + except: + pass + + # This relies on the fact that frame 1 is the first frame of an animation in the rect list + # If that is not so, the *icongen input file will end up missing some of the lines + if framenum == -1 or framenum == 1: + mode = 'wb' + else: + mode = 'ab' + if framenum == -1: + fps_field = '' + else: + if options.anicur: + # For anicursorgen use jiffies + fps_field = ' {}'.format (int (60.0 / options.fps)) + else: + # For xcursorgen use milliseconds + fps_field = ' {}'.format (int (1000.0 / options.fps)) + xcur = {} + xcur['s0'] = open ('bitmaps/{}.in'.format (slicename), mode) + if len (scale_pairs) > 0: + xcur['s1'] = open ('bitmaps/{}.s1.in'.format (slicename), mode) + xcur['s2'] = open ('bitmaps/{}.s2.in'.format (slicename), mode) + for i, size in enumerate (sizes): + subdir = 'bitmaps/{}x{}'.format (size, size) + relslice = '{}/{}'.format (subdir, pngsliceFName) + hotrelslice = '{}/{}'.format (subdir, hotsliceFName) + hot = find_hotspot (hotrelslice) + csize = get_csize (i, size) + xcur['s0'].write ("{csize} {hotx} {hoty} {filename}{fps_field}\n".format (csize=csize, hotx=hot[0], hoty=hot[1], filename='{}x{}/{}'.format (size, size, pngsliceFName), fps_field=fps_field).encode()) + for scale in scale_pairs: + subdir = 'bitmaps/{}x{}_{}'.format (size, size, scale[1]) + relslice = '{}/{}'.format (subdir, pngsliceFName) + scaled_size = int (size * scale[0]) + hotrelslice = '{}/{}'.format (subdir, hotsliceFName) + hot = find_hotspot (hotrelslice) + xcur[scale[1]].write ("{csize} {hotx} {hoty} {filename}{fps_field}\n".format (csize=csize, hotx=hot[0], hoty=hot[1], filename='{}x{}_{}/{}'.format (size, size, scale[1], pngsliceFName), fps_field=fps_field)) + xcur['s0'].close () + if len (scale_pairs) > 0: + xcur['s1'].close () + xcur['s2'].close () + +def sort_file(filename): + with open (filename, 'rb') as src: + contents = src.readlines () + with open (filename, 'wb') as dst: + for line in natural_sort (contents): + dst.write (line) + +def sort_xcur(slicename, passed): + pngsliceFName = slicename + '.png' + + framenum = -1 + if slicename[-5:].startswith ('_'): + try: + framenum = int (slicename[-4:]) + slicename = slicename[:-5] + except: + pass + if slicename in passed: + return + passed[slicename] = True + + sort_file ('bitmaps/{}.in'.format (slicename)) + if len (scale_pairs) > 0: + sort_file ('bitmaps/{}.s1.in'.format (slicename)) + sort_file ('bitmaps/{}.s2.in'.format (slicename)) + +def delete_hotspot(slicename): + hotsliceFName = slicename + '.hotspot.png' + + for i, size in enumerate (sizes): + subdir = 'bitmaps/{}x{}'.format (size, size) + hotrelslice = '{}/{}'.format (subdir, hotsliceFName) + if os.path.exists (hotrelslice): + os.unlink (hotrelslice) + for scale in scale_pairs: + subdir = 'bitmaps/{}x{}_{}'.format (size, size, scale[1]) + hotrelslice = '{}/{}'.format (subdir, hotsliceFName) + if os.path.exists (hotrelslice): + os.unlink (hotrelslice) + +class SVGHandler(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) as e: + 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""" + 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(' %s (please add real alternative text for this image)\n' % (sliceprefix + rect.name + '.png', rect.name)) + write('

') + + write('

Valid XHTML 1.0!

') + + write(' \n') + write('\n') + +class SVGFilter (saxutils.XMLFilterBase): + def __init__ (self, upstream, downstream, mode, **kwargs): + saxutils.XMLFilterBase.__init__(self, upstream) + self._downstream = downstream + self.mode = mode + + def startDocument (self): + self.in_throwaway_layer_stack = [False] + + def startElement (self, localname, attrs): + def modify_style (style, old_style, new_style=None): + styles = style.split (';') + new_styles = [] + if old_style is not None: + match_to = old_style + ':' + for s in styles: + if len (s) > 0 and (old_style is None or not s.startswith (match_to)): + new_styles.append (s) + if new_style is not None: + new_styles.append (new_style) + return ';'.join (new_styles) + + dict = {} + is_throwaway_layer = False + is_slices = False + is_hotspots = False + is_shadows = False + is_layer = False + if localname == 'g': + for key, value in attrs.items (): + if key == 'inkscape:label': + if value == 'slices': + is_slices = True + elif value == 'hotspots': + is_hotspots = True + elif value == 'shadows': + is_shadows = True + elif key == 'inkscape:groupmode': + if value == 'layer': + is_layer = True + if mode_shadows in self.mode and is_shadows: + # Only remove the shadows + is_throwaway_layer = True + elif mode_hotspots in self.mode and not (is_hotspots or is_slices): + # Remove all layers but hotspots and slices + if localname == 'g': + is_throwaway_layer = True + idict = {} + idict.update (attrs) + if 'style' not in attrs.keys (): + idict['style'] = '' + for key, value in idict.items(): + alocalname = key + if alocalname == 'style': + had_style = True + if alocalname == 'style' and is_slices: + # Make slices invisible. Do not check the mode, because there is + # no circumstances where we *want* to render slices + value = modify_style (value, 'display', 'display:none') + if alocalname == 'style' and is_hotspots: + if mode_hotspots in self.mode: + # Make hotspots visible in hotspots mode + value = modify_style (value, 'display', 'display:inline') + else: + # Make hotspots invisible otherwise + value = modify_style (value, 'display', 'display:none') + if alocalname == 'style' and mode_invert in self.mode and is_layer and is_shadows: + value = modify_style (value, None, 'filter:url(#InvertFilter)') + dict[key] = value + + if self.in_throwaway_layer_stack[0] or is_throwaway_layer: + self.in_throwaway_layer_stack.insert(0, True) + else: + self.in_throwaway_layer_stack.insert(0, False) + attrs = xmlreader.AttributesImpl(dict) + self._downstream.startElement(localname, attrs) + + def characters(self, content): + if self.in_throwaway_layer_stack[0]: + return + self._downstream.characters(content) + + def endElement(self, localname): + if self.in_throwaway_layer_stack.pop(0): + return + self._downstream.endElement(localname) + +def filter_svg (input, output, mode): + """filter_svg(input:file, output:file, mode) + + Parses the SVG input from the input stream. + For mode == 'hotspots' it filters out all + layers except for hotspots and slices. Also makes hotspots + visible. + For mode == 'shadows' it filters out the shadows layer. + """ + + mode_objs = [] + if 'hotspots' in mode: + mode_objs.append (mode_hotspots) + if 'shadows' in mode: + mode_objs.append (mode_shadows) + if 'slices' in mode: + mode_objs.append (mode_slices) + if 'invert' in mode: + mode_objs.append (mode_invert) + if len (mode_objs) == 0: + raise ValueError() + + output_gen = saxutils.XMLGenerator(output) + parser = make_parser() + filter = SVGFilter(parser, output_gen, mode_objs) + filter.setFeature(handler.feature_namespaces, False) + filter.setErrorHandler(handler.ErrorHandler()) + # This little I/O dance is here to ensure that SAX parser does not stash away + # an open file descriptor for the input file, which would prevent us from unlinking it later + with open (input, 'rb') as inp: + contents = inp.read () + contents_io = io.BytesIO (contents) + source_object = saxutils.prepare_input_source (contents_io) + filter.parse(source_object) + del filter + del parser + del output_gen + +def autodetect_threadcount (): + try: + count = multiprocessing.cpu_count() + except NotImplementedError: + count = 1 + return count + +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' + hotsvgFilename = originalFilename + '.hotspots.svg' + modes = ['slices'] + if options.remove_shadows: + modes.append ('shadows') + if options.invert: + modes.append ('invert') + + with open (svgFilename, 'wb') as output: + filter_svg(originalFilename, output, modes) + + if options.hotspots: + with open (hotsvgFilename, 'wb') as output: + filter_svg(originalFilename, output, ['hotspots']) + # 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 = '' + + if not options.scales: + del scale_pairs[:] + + if options.number_of_renderers <= 0: + options.number_of_renderers = autodetect_threadcount () + + inkscape_instances = [] + + for i in range (0, options.number_of_renderers): + inkscape = subprocess.Popen (['inkscape', '--without-gui', '--shell'], stdin=subprocess.PIPE, stderr=subprocess.PIPE) + if inkscape is None: + fatalError("Failed to start Inkscape shell process") + inkscape_stderr = inkscape.stderr + inkscape_stderr_thread = Thread (target = stderr_reader, args=(inkscape, inkscape_stderr)) + inkscape_instances.append ([inkscape, inkscape_stderr, inkscape_stderr_thread]) + + # 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() + del xmlParser + + skipped = {} + roundrobin = [0] + + # loop through each slice rectangle, and render a PNG image for it + svgLayerHandler.svg_rects + for rect in svgLayerHandler.svg_rects: + slicename = sliceprefix + rect.name + rect.renderFromSVG(svgFilename, slicename, skipped, roundrobin, hotsvgFilename) + + cleanup() + + for rect in svgLayerHandler.svg_rects: + slicename = sliceprefix + rect.name + postprocess_slice(slicename, skipped) + if options.hotspots: + write_xcur(slicename) + + if options.hotspots: + passed = {} + for rect in svgLayerHandler.svg_rects: + slicename = sliceprefix + rect.name + sort_xcur(slicename, passed) + #if not option.testing: + # delete_hotspot(slicename) + + dbg('Slicing complete.')