summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: a9e9781)
raw | patch | inline | side by side (parent: a9e9781)
author | Alvin Penner <penner@vaxxine.com> | |
Sat, 19 Dec 2009 13:45:36 +0000 (08:45 -0500) | ||
committer | Alvin Penner <penner@vaxxine.com> | |
Sat, 19 Dec 2009 13:45:36 +0000 (08:45 -0500) |
share/extensions/render_alphabetsoup.py | patch | blob | history |
index 6bc38459b70863d1c65270dde23f97827f1bd255..7e400932813a070fdb30d6ecb58d13fd7c62dfa8 100644 (file)
-#!/usr/bin/env python
-'''
-Copyright (C) 2001-2002 Matt Chisholm matt@theory.org
-Copyright (C) 2008 Joel Holdsworth joel@airwebreathe.org.uk
- for AP
-
-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 2 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, write to the Free Software
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-'''
-
-import copy
-import inkex
-import simplestyle
-import math
-import cmath
-import string
-import random
-import render_alphabetsoup_config
-import bezmisc
-import simplepath
-import os
-import sys
-
-syntax = render_alphabetsoup_config.syntax
-alphabet = render_alphabetsoup_config.alphabet
-units = render_alphabetsoup_config.units
-font = render_alphabetsoup_config.font
-
-# Loads a super-path from a given SVG file
-def loadPath( svgPath ):
- extensionDir = os.path.normpath(
- os.path.join( os.getcwd(), os.path.dirname(__file__) )
- )
- # __file__ is better then sys.argv[0] because this file may be a module
- # for another one.
- tree = inkex.etree.parse( extensionDir + "/" + svgPath )
- root = tree.getroot()
- pathElement = root.find('{http://www.w3.org/2000/svg}path')
- if pathElement == None:
- return None, 0, 0
- d = pathElement.get("d")
- width = float(root.get("width"))
- height = float(root.get("height"))
- return simplepath.parsePath(d), width, height # Currently we only support a single path
-
-def combinePaths( pathA, pathB ):
- if pathA == None and pathB == None:
- return None
- elif pathA == None:
- return pathB
- elif pathB == None:
- return pathA
- else:
- return pathA + pathB
-
-def flipLeftRight( sp, width ):
- for cmd,params in sp:
- defs = simplepath.pathdefs[cmd]
- for i in range(defs[1]):
- if defs[3][i] == 'x':
- params[i] = width - params[i]
-
-def flipTopBottom( sp, height ):
- for cmd,params in sp:
- defs = simplepath.pathdefs[cmd]
- for i in range(defs[1]):
- if defs[3][i] == 'y':
- params[i] = height - params[i]
-
-def solveQuadratic(a, b, c):
- det = b*b - 4.0*a*c
- if det >= 0: # real roots
- sdet = math.sqrt(det)
- else: # complex roots
- sdet = cmath.sqrt(det)
- return (-b + sdet) / (2*a), (-b - sdet) / (2*a)
-
-def cbrt(x):
- if x >= 0:
- return x**(1.0/3.0)
- else:
- return -((-x)**(1.0/3.0))
-
-def findRealRoots(a,b,c,d):
- if a != 0:
- a, b, c, d = 1, b/float(a), c/float(a), d/float(a) # Divide through by a
- t = b / 3.0
- p, q = c - 3 * t**2, d - c * t + 2 * t**3
- u, v = solveQuadratic(1, q, -(p/3.0)**3)
- if type(u) == type(0j): # Complex Cubic Root
- r = math.sqrt(u.real**2 + u.imag**2)
- w = math.atan2(u.imag, u.real)
- y1 = 2 * cbrt(r) * math.cos(w / 3.0)
- else: # Complex Real Root
- y1 = cbrt(u) + cbrt(v)
-
- y2, y3 = solveQuadratic(1, y1, p + y1**2)
-
- if type(y2) == type(0j): # Are y2 and y3 complex?
- return [y1 - t]
- return [y1 - t, y2 - t, y3 - t]
- elif b != 0:
- det=c*c - 4.0*b*d
- if det >= 0:
- return [(-c + math.sqrt(det))/(2.0*b),(-c - math.sqrt(det))/(2.0*b)]
- elif c != 0:
- return [-d/c]
- return []
-
-def getPathBoundingBox( sp ):
-
- box = None
- last = None
- lostctrl = None
-
- for cmd,params in sp:
-
- segmentBox = None
-
- if cmd == 'M':
- # A move cannot contribute to the bounding box
- last = params[:]
- lastctrl = params[:]
- elif cmd == 'L':
- if last:
- segmentBox = (min(params[0], last[0]), max(params[0], last[0]), min(params[1], last[1]), max(params[1], last[1]))
- last = params[:]
- lastctrl = params[:]
- elif cmd == 'C':
- if last:
- segmentBox = (min(params[4], last[0]), max(params[4], last[0]), min(params[5], last[1]), max(params[5], last[1]))
-
- bx0, by0 = last[:]
- bx1, by1, bx2, by2, bx3, by3 = params[:]
-
- # Compute the x limits
- a = (-bx0 + 3*bx1 - 3*bx2 + bx3)*3
- b = (3*bx0 - 6*bx1 + 3*bx2)*2
- c = (-3*bx0 + 3*bx1)
- ts = findRealRoots(0, a, b, c)
- for t in ts:
- if t >= 0 and t <= 1:
- x = (-bx0 + 3*bx1 - 3*bx2 + bx3)*(t**3) + \
- (3*bx0 - 6*bx1 + 3*bx2)*(t**2) + \
- (-3*bx0 + 3*bx1)*t + \
- bx0
- segmentBox = (min(segmentBox[0], x), max(segmentBox[1], x), segmentBox[2], segmentBox[3])
-
- # Compute the y limits
- a = (-by0 + 3*by1 - 3*by2 + by3)*3
- b = (3*by0 - 6*by1 + 3*by2)*2
- c = (-3*by0 + 3*by1)
- ts = findRealRoots(0, a, b, c)
- for t in ts:
- if t >= 0 and t <= 1:
- y = (-by0 + 3*by1 - 3*by2 + by3)*(t**3) + \
- (3*by0 - 6*by1 + 3*by2)*(t**2) + \
- (-3*by0 + 3*by1)*t + \
- by0
- segmentBox = (segmentBox[0], segmentBox[1], min(segmentBox[2], y), max(segmentBox[3], y))
-
- last = params[-2:]
- lastctrl = params[2:4]
-
- elif cmd == 'Q':
- # Provisional
- if last:
- segmentBox = (min(params[0], last[0]), max(params[0], last[0]), min(params[1], last[1]), max(params[1], last[1]))
- last = params[-2:]
- lastctrl = params[2:4]
-
- elif cmd == 'A':
- # Provisional
- if last:
- segmentBox = (min(params[0], last[0]), max(params[0], last[0]), min(params[1], last[1]), max(params[1], last[1]))
- last = params[-2:]
- lastctrl = params[2:4]
-
- if segmentBox:
- if box:
- box = (min(segmentBox[0],box[0]), max(segmentBox[1],box[1]), min(segmentBox[2],box[2]), max(segmentBox[3],box[3]))
- else:
- box = segmentBox
- return box
-
-def mxfm( image, width, height, stack ): # returns possibly transformed image
- tbimage = image
- if ( stack[0] == "-" ): # top-bottom flip
- flipTopBottom(tbimage, height)
- stack.pop( 0 )
-
- lrimage = tbimage
- if ( stack[0] == "|" ): # left-right flip
- flipLeftRight(tbimage, width)
- stack.pop( 0 )
- return lrimage
-
-def comparerule( rule, nodes ): # compare node list to nodes in rule
- for i in range( 0, len(nodes)): # range( a, b ) = (a, a+1, a+2 ... b-2, b-1)
- if (nodes[i] == rule[i][0]):
- pass
- else: return 0
- return 1
-
-def findrule( state, nodes ): # find the rule which generated this subtree
- ruleset = syntax[state][1]
- nodelen = len(nodes)
- for rule in ruleset:
- rulelen = len(rule)
- if ((rulelen == nodelen) and (comparerule( rule, nodes ))):
- return rule
- return
-
-def generate( state ): # generate a random tree (in stack form)
- stack = [ state ]
- if ( len(syntax[state]) == 1 ): # if this is a stop symbol
- return stack
- else:
- stack.append( "[" )
- path = random.randint(0, (len(syntax[state][1])-1)) # choose randomly from next states
- for symbol in syntax[state][1][path]: # recurse down each non-terminal
- if ( symbol != 0 ): # 0 denotes end of list ###
- substack = generate( symbol[0] ) # get subtree
- for elt in substack:
- stack.append( elt )
- if (symbol[3]):stack.append( "-" ) # top-bottom flip
- if (symbol[4]):stack.append( "|" ) # left-right flip
- #else:
- #inkex.debug("found end of list in generate( state =", state, ")") # this should be deprecated/never happen
- stack.append("]")
- return stack
-
-def draw( stack ): # draw a character based on a tree stack
- state = stack.pop(0)
- #print state,
-
- image, width, height = loadPath( font+syntax[state][0] ) # load the image
- if (stack[0] != "["): # terminal stack element
- if (len(syntax[state]) == 1): # this state is a terminal node
- return image, width, height
- else:
- substack = generate( state ) # generate random substack
- return draw( substack ) # draw random substack
- else:
- #inkex.debug("[")
- stack.pop(0)
- images = [] # list of daughter images
- nodes = [] # list of daughter names
- while (stack[0] != "]"): # for all nodes in stack
- newstate = stack[0] # the new state
- newimage, width, height = draw( stack ) # draw the daughter state
- if (newimage):
- tfimage = mxfm( newimage, width, height, stack ) # maybe transform daughter state
- images.append( [tfimage, width, height] ) # list of daughter images
- nodes.append( newstate ) # list of daughter nodes
- else:
- #inkex.debug(("recurse on",newstate,"failed")) # this should never happen
- return None, 0, 0
- rule = findrule( state, nodes ) # find the rule for this subtree
-
- for i in range( 0, len(images)):
- currimg, width, height = images[i]
-
- if currimg:
- #box = getPathBoundingBox(currimg)
- dx = rule[i][1]*units
- dy = rule[i][2]*units
- #newbox = ((box[0]+dx),(box[1]+dy),(box[2]+dx),(box[3]+dy))
- simplepath.translatePath(currimg, dx, dy)
- image = combinePaths( image, currimg )
-
- stack.pop( 0 )
- return image, width, height
-
-def draw_crop_scale( stack, zoom ): # draw, crop and scale letter image
- image, width, height = draw(stack)
- bbox = getPathBoundingBox(image)
- simplepath.translatePath(image, -bbox[0], 0)
- simplepath.scalePath(image, zoom/units, zoom/units)
- return image, bbox[1] - bbox[0], bbox[3] - bbox[2]
-
-def randomize_input_string( str, zoom ): # generate list of images based on input string
- imagelist = []
-
- for i in range(0,len(str)):
- char = str[i]
- #if ( re.match("[a-zA-Z0-9?]", char)):
- if ( alphabet.has_key(char)):
- if ((i > 0) and (char == str[i-1])): # if this letter matches previous letter
- imagelist.append(imagelist[len(stack)-1])# make them the same image
- else: # generate image for letter
- stack = string.split( alphabet[char][random.randint(0,(len(alphabet[char])-1))] , "." )
- #stack = string.split( alphabet[char][random.randint(0,(len(alphabet[char])-2))] , "." )
- imagelist.append( draw_crop_scale( stack, zoom ))
- elif( char == " "): # add a " " space to the image list
- imagelist.append( " " )
- else: # this character is not in config.alphabet, skip it
- print "bad character", char
- return imagelist
-
-def optikern( image, width, zoom ): # optical kerning algorithm
- left = []
- right = []
-
- for i in range( 0, 36 ):
- y = 0.5 * (i + 0.5) * zoom
- xmin = None
- xmax = None
-
- for cmd,params in image:
-
- segmentBox = None
-
- if cmd == 'M':
- # A move cannot contribute to the bounding box
- last = params[:]
- lastctrl = params[:]
- elif cmd == 'L':
- if (y >= last[1] and y <= params[1]) or (y >= params[1] and y <= last[1]):
- if params[0] == last[0]:
- x = params[0]
- else:
- a = (params[1] - last[1]) / (params[0] - last[0])
- b = last[1] - a * last[0]
- if a != 0:
- x = (y - b) / a
- else: x = None
-
- if x:
- if xmin == None or x < xmin: xmin = x
- if xmax == None or x > xmax: xmax = x
-
- last = params[:]
- lastctrl = params[:]
- elif cmd == 'C':
- if last:
- bx0, by0 = last[:]
- bx1, by1, bx2, by2, bx3, by3 = params[:]
-
- d = by0 - y
- c = -3*by0 + 3*by1
- b = 3*by0 - 6*by1 + 3*by2
- a = -by0 + 3*by1 - 3*by2 + by3
-
- ts = findRealRoots(a, b, c, d)
-
- for t in ts:
- if t >= 0 and t <= 1:
- x = (-bx0 + 3*bx1 - 3*bx2 + bx3)*(t**3) + \
- (3*bx0 - 6*bx1 + 3*bx2)*(t**2) + \
- (-3*bx0 + 3*bx1)*t + \
- bx0
- if xmin == None or x < xmin: xmin = x
- if xmax == None or x > xmax: xmax = x
-
- last = params[-2:]
- lastctrl = params[2:4]
-
- elif cmd == 'Q':
- # Quadratic beziers are ignored
- last = params[-2:]
- lastctrl = params[2:4]
-
- elif cmd == 'A':
- # Arcs are ignored
- last = params[-2:]
- lastctrl = params[2:4]
-
-
- if xmin != None and xmax != None:
- left.append( xmin ) # distance from left edge of region to left edge of bbox
- right.append( width - xmax ) # distance from right edge of region to right edge of bbox
- else:
- left.append( width )
- right.append( width )
-
- return (left, right)
-
-def layoutstring( imagelist, zoom ): # layout string of letter-images using optical kerning
- kernlist = []
- length = zoom
- for entry in imagelist:
- if (entry == " "): # leaving room for " " space characters
- length = length + (zoom * render_alphabetsoup_config.space)
- else:
- image, width, height = entry
- length = length + width + zoom # add letter length to overall length
- kernlist.append( optikern(image, width, zoom) ) # append kerning data for this image
-
- workspace = None
-
- position = zoom
- for i in range(0, len(kernlist)):
- while(imagelist[i] == " "):
- position = position + (zoom * render_alphabetsoup_config.space )
- imagelist.pop(i)
- image, width, height = imagelist[i]
-
- # set the kerning
- if i == 0: kern = 0 # for first image, kerning is zero
- else:
- kerncompare = [] # kerning comparison array
- for j in range( 0, len(kernlist[i][0])):
- kerncompare.append( kernlist[i][0][j]+kernlist[i-1][1][j] )
- kern = min( kerncompare )
-
- position = position - kern # move position back by kern amount
- thisimage = copy.deepcopy(image)
- simplepath.translatePath(thisimage, position, 0)
- workspace = combinePaths(workspace, thisimage)
- position = position + width + zoom # advance position by letter width
-
- return workspace
-
-class AlphabetSoup(inkex.Effect):
- def __init__(self):
- inkex.Effect.__init__(self)
- self.OptionParser.add_option("-t", "--text",
- action="store", type="string",
- dest="text", default="Inkscape",
- help="The text for alphabet soup")
- self.OptionParser.add_option("-z", "--zoom",
- action="store", type="float",
- dest="zoom", default="8.0",
- help="The zoom on the output graphics")
- self.OptionParser.add_option("-s", "--seed",
- action="store", type="int",
- dest="seed", default="0",
- help="The random seed for the soup")
-
- def effect(self):
- zoom = self.options.zoom
- random.seed(self.options.seed)
-
- imagelist = randomize_input_string(self.options.text, zoom)
- image = layoutstring( imagelist, zoom )
-
- if image:
- s = { 'stroke': 'none', 'fill': '#000000' }
-
- new = inkex.etree.Element(inkex.addNS('path','svg'))
- new.set('style', simplestyle.formatStyle(s))
-
- new.set('d', simplepath.formatPath(image))
- self.current_layer.append(new)
-
-if __name__ == '__main__':
- e = AlphabetSoup()
- e.affect()
-
+#!/usr/bin/env python \r
+'''\r
+Copyright (C) 2001-2002 Matt Chisholm matt@theory.org\r
+Copyright (C) 2008 Joel Holdsworth joel@airwebreathe.org.uk\r
+ for AP\r
+\r
+This program is free software; you can redistribute it and/or modify\r
+it under the terms of the GNU General Public License as published by\r
+the Free Software Foundation; either version 2 of the License, or\r
+(at your option) any later version.\r
+\r
+This program is distributed in the hope that it will be useful,\r
+but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+GNU General Public License for more details.\r
+\r
+You should have received a copy of the GNU General Public License\r
+along with this program; if not, write to the Free Software\r
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+'''\r
+\r
+import copy\r
+import inkex\r
+import simplestyle\r
+import math\r
+import cmath\r
+import string\r
+import random\r
+import render_alphabetsoup_config\r
+import bezmisc\r
+import simplepath\r
+import os\r
+import sys\r
+import gettext\r
+_ = gettext.gettext\r
+\r
+syntax = render_alphabetsoup_config.syntax\r
+alphabet = render_alphabetsoup_config.alphabet\r
+units = render_alphabetsoup_config.units\r
+font = render_alphabetsoup_config.font\r
+\r
+# Loads a super-path from a given SVG file\r
+def loadPath( svgPath ):\r
+ extensionDir = os.path.normpath(\r
+ os.path.join( os.getcwd(), os.path.dirname(__file__) )\r
+ )\r
+ # __file__ is better then sys.argv[0] because this file may be a module\r
+ # for another one.\r
+ tree = inkex.etree.parse( extensionDir + "/" + svgPath )\r
+ root = tree.getroot()\r
+ pathElement = root.find('{http://www.w3.org/2000/svg}path')\r
+ if pathElement == None:\r
+ return None, 0, 0\r
+ d = pathElement.get("d")\r
+ width = float(root.get("width"))\r
+ height = float(root.get("height"))\r
+ return simplepath.parsePath(d), width, height # Currently we only support a single path\r
+\r
+def combinePaths( pathA, pathB ):\r
+ if pathA == None and pathB == None:\r
+ return None\r
+ elif pathA == None:\r
+ return pathB\r
+ elif pathB == None:\r
+ return pathA\r
+ else:\r
+ return pathA + pathB\r
+\r
+def flipLeftRight( sp, width ):\r
+ for cmd,params in sp:\r
+ defs = simplepath.pathdefs[cmd]\r
+ for i in range(defs[1]):\r
+ if defs[3][i] == 'x':\r
+ params[i] = width - params[i]\r
+\r
+def flipTopBottom( sp, height ):\r
+ for cmd,params in sp:\r
+ defs = simplepath.pathdefs[cmd]\r
+ for i in range(defs[1]):\r
+ if defs[3][i] == 'y':\r
+ params[i] = height - params[i]\r
+\r
+def solveQuadratic(a, b, c):\r
+ det = b*b - 4.0*a*c\r
+ if det >= 0: # real roots\r
+ sdet = math.sqrt(det)\r
+ else: # complex roots\r
+ sdet = cmath.sqrt(det)\r
+ return (-b + sdet) / (2*a), (-b - sdet) / (2*a)\r
+\r
+def cbrt(x): \r
+ if x >= 0:\r
+ return x**(1.0/3.0)\r
+ else:\r
+ return -((-x)**(1.0/3.0))\r
+\r
+def findRealRoots(a,b,c,d):\r
+ if a != 0:\r
+ a, b, c, d = 1, b/float(a), c/float(a), d/float(a) # Divide through by a\r
+ t = b / 3.0\r
+ p, q = c - 3 * t**2, d - c * t + 2 * t**3\r
+ u, v = solveQuadratic(1, q, -(p/3.0)**3)\r
+ if type(u) == type(0j): # Complex Cubic Root\r
+ r = math.sqrt(u.real**2 + u.imag**2)\r
+ w = math.atan2(u.imag, u.real)\r
+ y1 = 2 * cbrt(r) * math.cos(w / 3.0)\r
+ else: # Complex Real Root\r
+ y1 = cbrt(u) + cbrt(v)\r
+ \r
+ y2, y3 = solveQuadratic(1, y1, p + y1**2)\r
+\r
+ if type(y2) == type(0j): # Are y2 and y3 complex?\r
+ return [y1 - t]\r
+ return [y1 - t, y2 - t, y3 - t]\r
+ elif b != 0:\r
+ det=c*c - 4.0*b*d\r
+ if det >= 0:\r
+ return [(-c + math.sqrt(det))/(2.0*b),(-c - math.sqrt(det))/(2.0*b)]\r
+ elif c != 0:\r
+ return [-d/c]\r
+ return []\r
+\r
+def getPathBoundingBox( sp ):\r
+ \r
+ box = None\r
+ last = None\r
+ lostctrl = None\r
+\r
+ for cmd,params in sp:\r
+\r
+ segmentBox = None\r
+\r
+ if cmd == 'M':\r
+ # A move cannot contribute to the bounding box\r
+ last = params[:]\r
+ lastctrl = params[:]\r
+ elif cmd == 'L':\r
+ if last:\r
+ segmentBox = (min(params[0], last[0]), max(params[0], last[0]), min(params[1], last[1]), max(params[1], last[1]))\r
+ last = params[:]\r
+ lastctrl = params[:]\r
+ elif cmd == 'C':\r
+ if last: \r
+ segmentBox = (min(params[4], last[0]), max(params[4], last[0]), min(params[5], last[1]), max(params[5], last[1]))\r
+ \r
+ bx0, by0 = last[:]\r
+ bx1, by1, bx2, by2, bx3, by3 = params[:]\r
+\r
+ # Compute the x limits\r
+ a = (-bx0 + 3*bx1 - 3*bx2 + bx3)*3\r
+ b = (3*bx0 - 6*bx1 + 3*bx2)*2\r
+ c = (-3*bx0 + 3*bx1)\r
+ ts = findRealRoots(0, a, b, c)\r
+ for t in ts:\r
+ if t >= 0 and t <= 1: \r
+ x = (-bx0 + 3*bx1 - 3*bx2 + bx3)*(t**3) + \\r
+ (3*bx0 - 6*bx1 + 3*bx2)*(t**2) + \\r
+ (-3*bx0 + 3*bx1)*t + \\r
+ bx0\r
+ segmentBox = (min(segmentBox[0], x), max(segmentBox[1], x), segmentBox[2], segmentBox[3])\r
+\r
+ # Compute the y limits\r
+ a = (-by0 + 3*by1 - 3*by2 + by3)*3\r
+ b = (3*by0 - 6*by1 + 3*by2)*2\r
+ c = (-3*by0 + 3*by1)\r
+ ts = findRealRoots(0, a, b, c)\r
+ for t in ts:\r
+ if t >= 0 and t <= 1: \r
+ y = (-by0 + 3*by1 - 3*by2 + by3)*(t**3) + \\r
+ (3*by0 - 6*by1 + 3*by2)*(t**2) + \\r
+ (-3*by0 + 3*by1)*t + \\r
+ by0\r
+ segmentBox = (segmentBox[0], segmentBox[1], min(segmentBox[2], y), max(segmentBox[3], y))\r
+\r
+ last = params[-2:]\r
+ lastctrl = params[2:4]\r
+\r
+ elif cmd == 'Q':\r
+ # Provisional\r
+ if last:\r
+ segmentBox = (min(params[0], last[0]), max(params[0], last[0]), min(params[1], last[1]), max(params[1], last[1]))\r
+ last = params[-2:]\r
+ lastctrl = params[2:4]\r
+\r
+ elif cmd == 'A':\r
+ # Provisional\r
+ if last:\r
+ segmentBox = (min(params[0], last[0]), max(params[0], last[0]), min(params[1], last[1]), max(params[1], last[1]))\r
+ last = params[-2:]\r
+ lastctrl = params[2:4]\r
+\r
+ if segmentBox:\r
+ if box:\r
+ box = (min(segmentBox[0],box[0]), max(segmentBox[1],box[1]), min(segmentBox[2],box[2]), max(segmentBox[3],box[3]))\r
+ else:\r
+ box = segmentBox \r
+ return box\r
+\r
+def mxfm( image, width, height, stack ): # returns possibly transformed image\r
+ tbimage = image \r
+ if ( stack[0] == "-" ): # top-bottom flip\r
+ flipTopBottom(tbimage, height)\r
+ stack.pop( 0 )\r
+\r
+ lrimage = tbimage\r
+ if ( stack[0] == "|" ): # left-right flip\r
+ flipLeftRight(tbimage, width)\r
+ stack.pop( 0 )\r
+ return lrimage\r
+\r
+def comparerule( rule, nodes ): # compare node list to nodes in rule\r
+ for i in range( 0, len(nodes)): # range( a, b ) = (a, a+1, a+2 ... b-2, b-1)\r
+ if (nodes[i] == rule[i][0]):\r
+ pass\r
+ else: return 0\r
+ return 1\r
+\r
+def findrule( state, nodes ): # find the rule which generated this subtree\r
+ ruleset = syntax[state][1]\r
+ nodelen = len(nodes)\r
+ for rule in ruleset:\r
+ rulelen = len(rule)\r
+ if ((rulelen == nodelen) and (comparerule( rule, nodes ))):\r
+ return rule\r
+ return \r
+\r
+def generate( state ): # generate a random tree (in stack form)\r
+ stack = [ state ]\r
+ if ( len(syntax[state]) == 1 ): # if this is a stop symbol\r
+ return stack\r
+ else:\r
+ stack.append( "[" )\r
+ path = random.randint(0, (len(syntax[state][1])-1)) # choose randomly from next states\r
+ for symbol in syntax[state][1][path]: # recurse down each non-terminal\r
+ if ( symbol != 0 ): # 0 denotes end of list ###\r
+ substack = generate( symbol[0] ) # get subtree\r
+ for elt in substack: \r
+ stack.append( elt )\r
+ if (symbol[3]):stack.append( "-" ) # top-bottom flip\r
+ if (symbol[4]):stack.append( "|" ) # left-right flip\r
+ #else:\r
+ #inkex.debug("found end of list in generate( state =", state, ")") # this should be deprecated/never happen\r
+ stack.append("]")\r
+ return stack\r
+\r
+def draw( stack ): # draw a character based on a tree stack\r
+ state = stack.pop(0)\r
+ #print state,\r
+\r
+ image, width, height = loadPath( font+syntax[state][0] ) # load the image\r
+ if (stack[0] != "["): # terminal stack element\r
+ if (len(syntax[state]) == 1): # this state is a terminal node\r
+ return image, width, height\r
+ else:\r
+ substack = generate( state ) # generate random substack\r
+ return draw( substack ) # draw random substack\r
+ else:\r
+ #inkex.debug("[")\r
+ stack.pop(0)\r
+ images = [] # list of daughter images\r
+ nodes = [] # list of daughter names\r
+ while (stack[0] != "]"): # for all nodes in stack\r
+ newstate = stack[0] # the new state\r
+ newimage, width, height = draw( stack ) # draw the daughter state\r
+ if (newimage):\r
+ tfimage = mxfm( newimage, width, height, stack ) # maybe transform daughter state\r
+ images.append( [tfimage, width, height] ) # list of daughter images\r
+ nodes.append( newstate ) # list of daughter nodes\r
+ else:\r
+ #inkex.debug(("recurse on",newstate,"failed")) # this should never happen\r
+ return None, 0, 0\r
+ rule = findrule( state, nodes ) # find the rule for this subtree\r
+\r
+ for i in range( 0, len(images)):\r
+ currimg, width, height = images[i]\r
+\r
+ if currimg:\r
+ #box = getPathBoundingBox(currimg)\r
+ dx = rule[i][1]*units\r
+ dy = rule[i][2]*units\r
+ #newbox = ((box[0]+dx),(box[1]+dy),(box[2]+dx),(box[3]+dy))\r
+ simplepath.translatePath(currimg, dx, dy)\r
+ image = combinePaths( image, currimg )\r
+\r
+ stack.pop( 0 )\r
+ return image, width, height\r
+\r
+def draw_crop_scale( stack, zoom ): # draw, crop and scale letter image\r
+ image, width, height = draw(stack)\r
+ bbox = getPathBoundingBox(image) \r
+ simplepath.translatePath(image, -bbox[0], 0) \r
+ simplepath.scalePath(image, zoom/units, zoom/units)\r
+ return image, bbox[1] - bbox[0], bbox[3] - bbox[2]\r
+\r
+def randomize_input_string( str, zoom ): # generate list of images based on input string\r
+ imagelist = []\r
+\r
+ for i in range(0,len(str)):\r
+ char = str[i]\r
+ #if ( re.match("[a-zA-Z0-9?]", char)):\r
+ if ( alphabet.has_key(char)):\r
+ if ((i > 0) and (char == str[i-1])): # if this letter matches previous letter\r
+ imagelist.append(imagelist[len(stack)-1])# make them the same image\r
+ else: # generate image for letter\r
+ stack = string.split( alphabet[char][random.randint(0,(len(alphabet[char])-1))] , "." )\r
+ #stack = string.split( alphabet[char][random.randint(0,(len(alphabet[char])-2))] , "." ) \r
+ imagelist.append( draw_crop_scale( stack, zoom ))\r
+ elif( char == " "): # add a " " space to the image list\r
+ imagelist.append( " " )\r
+ else: # this character is not in config.alphabet, skip it\r
+ inkex.errormsg(_("bad character") + " = 0x%x" % ord(char))\r
+ return imagelist\r
+\r
+def optikern( image, width, zoom ): # optical kerning algorithm\r
+ left = []\r
+ right = []\r
+\r
+ for i in range( 0, 36 ):\r
+ y = 0.5 * (i + 0.5) * zoom\r
+ xmin = None\r
+ xmax = None\r
+\r
+ for cmd,params in image:\r
+\r
+ segmentBox = None\r
+\r
+ if cmd == 'M':\r
+ # A move cannot contribute to the bounding box\r
+ last = params[:]\r
+ lastctrl = params[:]\r
+ elif cmd == 'L':\r
+ if (y >= last[1] and y <= params[1]) or (y >= params[1] and y <= last[1]):\r
+ if params[0] == last[0]:\r
+ x = params[0]\r
+ else:\r
+ a = (params[1] - last[1]) / (params[0] - last[0])\r
+ b = last[1] - a * last[0]\r
+ if a != 0:\r
+ x = (y - b) / a\r
+ else: x = None\r
+ \r
+ if x:\r
+ if xmin == None or x < xmin: xmin = x\r
+ if xmax == None or x > xmax: xmax = x\r
+\r
+ last = params[:]\r
+ lastctrl = params[:]\r
+ elif cmd == 'C':\r
+ if last: \r
+ bx0, by0 = last[:]\r
+ bx1, by1, bx2, by2, bx3, by3 = params[:]\r
+\r
+ d = by0 - y\r
+ c = -3*by0 + 3*by1\r
+ b = 3*by0 - 6*by1 + 3*by2\r
+ a = -by0 + 3*by1 - 3*by2 + by3\r
+ \r
+ ts = findRealRoots(a, b, c, d)\r
+\r
+ for t in ts:\r
+ if t >= 0 and t <= 1: \r
+ x = (-bx0 + 3*bx1 - 3*bx2 + bx3)*(t**3) + \\r
+ (3*bx0 - 6*bx1 + 3*bx2)*(t**2) + \\r
+ (-3*bx0 + 3*bx1)*t + \\r
+ bx0\r
+ if xmin == None or x < xmin: xmin = x\r
+ if xmax == None or x > xmax: xmax = x\r
+\r
+ last = params[-2:]\r
+ lastctrl = params[2:4]\r
+\r
+ elif cmd == 'Q':\r
+ # Quadratic beziers are ignored\r
+ last = params[-2:]\r
+ lastctrl = params[2:4]\r
+\r
+ elif cmd == 'A':\r
+ # Arcs are ignored\r
+ last = params[-2:]\r
+ lastctrl = params[2:4]\r
+\r
+\r
+ if xmin != None and xmax != None:\r
+ left.append( xmin ) # distance from left edge of region to left edge of bbox\r
+ right.append( width - xmax ) # distance from right edge of region to right edge of bbox\r
+ else:\r
+ left.append( width )\r
+ right.append( width )\r
+\r
+ return (left, right)\r
+\r
+def layoutstring( imagelist, zoom ): # layout string of letter-images using optical kerning\r
+ kernlist = []\r
+ length = zoom\r
+ for entry in imagelist:\r
+ if (entry == " "): # leaving room for " " space characters\r
+ length = length + (zoom * render_alphabetsoup_config.space)\r
+ else:\r
+ image, width, height = entry\r
+ length = length + width + zoom # add letter length to overall length\r
+ kernlist.append( optikern(image, width, zoom) ) # append kerning data for this image \r
+\r
+ workspace = None\r
+\r
+ position = zoom\r
+ for i in range(0, len(kernlist)):\r
+ while(imagelist[i] == " "):\r
+ position = position + (zoom * render_alphabetsoup_config.space )\r
+ imagelist.pop(i)\r
+ image, width, height = imagelist[i]\r
+\r
+ # set the kerning\r
+ if i == 0: kern = 0 # for first image, kerning is zero\r
+ else:\r
+ kerncompare = [] # kerning comparison array\r
+ for j in range( 0, len(kernlist[i][0])):\r
+ kerncompare.append( kernlist[i][0][j]+kernlist[i-1][1][j] )\r
+ kern = min( kerncompare )\r
+\r
+ position = position - kern # move position back by kern amount\r
+ thisimage = copy.deepcopy(image) \r
+ simplepath.translatePath(thisimage, position, 0)\r
+ workspace = combinePaths(workspace, thisimage)\r
+ position = position + width + zoom # advance position by letter width\r
+\r
+ return workspace\r
+\r
+class AlphabetSoup(inkex.Effect):\r
+ def __init__(self):\r
+ inkex.Effect.__init__(self)\r
+ self.OptionParser.add_option("-t", "--text",\r
+ action="store", type="string", \r
+ dest="text", default="Inkscape",\r
+ help="The text for alphabet soup")\r
+ self.OptionParser.add_option("-z", "--zoom",\r
+ action="store", type="float", \r
+ dest="zoom", default="8.0",\r
+ help="The zoom on the output graphics")\r
+ self.OptionParser.add_option("-s", "--seed",\r
+ action="store", type="int", \r
+ dest="seed", default="0",\r
+ help="The random seed for the soup")\r
+\r
+ def effect(self):\r
+ zoom = self.options.zoom\r
+ random.seed(self.options.seed)\r
+\r
+ imagelist = randomize_input_string(self.options.text, zoom)\r
+ image = layoutstring( imagelist, zoom )\r
+\r
+ if image:\r
+ s = { 'stroke': 'none', 'fill': '#000000' }\r
+\r
+ new = inkex.etree.Element(inkex.addNS('path','svg'))\r
+ new.set('style', simplestyle.formatStyle(s))\r
+\r
+ new.set('d', simplepath.formatPath(image))\r
+ self.current_layer.append(new)\r
+\r
+if __name__ == '__main__':\r
+ e = AlphabetSoup()\r
+ e.affect()\r
+\r