| Server IP : 93.86.61.54 / Your IP : 216.73.216.206 Web Server : Apache/2.4.62 (Ubuntu) System : Linux rasin.ddns.net 6.8.0-124-generic #124~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 21:05:19 UTC x86_64 User : www-data ( 33) PHP Version : 8.4.22 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /usr/share/inkscape/extensions/ |
Upload File : |
#!/usr/bin/env python
# coding=utf-8
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
import cmath
import copy
import math
import os
import random
import re
import sys
import inkex
from inkex import Vector2d, load_svg
import render_alphabetsoup_config
syntax = render_alphabetsoup_config.syntax
alphabet = render_alphabetsoup_config.alphabet
units = render_alphabetsoup_config.units
font = render_alphabetsoup_config.font
def load_path(filename):
"""Loads a super-path from a given SVG file"""
base = 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.
fullpath = os.path.join(base, filename)
tree = load_svg(fullpath)
root = tree.getroot()
elem = root.findone('svg:path')
if elem is None:
return None, 0, 0
width = float(root.get("width"))
height = float(root.get("height"))
return elem.path.to_arrays(), width, height # Currently we only support a single path
def combinePaths(pathA, pathB):
if pathA is None and pathB is None:
return None
elif pathA is None:
return pathB
elif pathB is None:
return pathA
else:
return pathA + pathB
def reverseComponent(c):
nc = []
last = c.pop()
nc.append(['M', last[1][-2:]])
while c:
this = c.pop()
cmd = last[0]
if cmd == 'C':
nc.append([last[0], last[1][2:4] + last[1][:2] + this[1][-2:]])
else:
nc.append([last[0], this[1][-2:]])
last = this
return nc
def reversePath(sp):
rp = []
component = []
for p in sp:
cmd, params = p
if cmd == 'Z':
rp.extend(reverseComponent(component))
rp.append(['Z', []])
component = []
else:
component.append(p)
return rp
def _lr_cb(p, width):
p.scale(-1, 1)
p.translate(width, 0)
def _tp_cb(p, height):
p.scale(1, -1)
p.translate(0, height)
def flip(sp, cb, param):
# print('flip before +' + str(sp))
p = inkex.Path(sp)
cb(p, param)
del sp[:]
prev = Vector2d()
prev_prev = Vector2d()
first = Vector2d()
for i, seg in enumerate(p):
if i == 0:
first = seg.end_point(first, prev)
cps = []
for cp in seg.control_points(first, prev, prev_prev):
prev_prev = prev
prev = cp
cps.extend(cp)
sp.append([seg.letter, cps])
# print('flip after +' + str(sp))
def flipLeftRight(sp, width):
return flip(sp, _lr_cb, width)
def flipTopBottom(sp, height):
return flip(sp, _tp_cb, height)
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 isinstance(u, complex): # 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 isinstance(y2, complex): # 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 mxfm(image, width, height, stack): # returns possibly transformed image
tbimage = image
if stack[0] == "-": # top-bottom flip
flipTopBottom(tbimage, height)
tbimage = reversePath(tbimage)
stack.pop(0)
lrimage = tbimage
if stack[0] == "|": # left-right flip
flipLeftRight(tbimage, width)
lrimage = reversePath(lrimage)
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 = load_path(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 = inkex.Path(currimg).bounding_box()
dx = rule[i][1] * units
dy = rule[i][2] * units
# newbox = ((box[0]+dx),(box[1]+dy),(box[2]+dx),(box[3]+dy))
currimg = (inkex.Path(currimg).translate(dx, dy)).to_arrays()
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 = inkex.Path(image).bounding_box()
image = (inkex.Path(image).translate(-bbox.x.minimum, 0)).to_arrays()
image = (inkex.Path(image).scale (zoom / units, zoom / units)).to_arrays()
return image, bbox.width, bbox.height
def randomize_input_string(tokens, zoom): # generate a glyph starting from each token in the input string
imagelist = []
stack = None
for i in range(0, len(tokens)):
char = tokens[i]
# if ( re.match("[a-zA-Z0-9?]", char)):
if char in alphabet:
if (i > 0) and (char == tokens[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 = alphabet[char][random.randint(0, (len(alphabet[char]) - 1))].split(".")
# 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
sys.stderr.write('bad character "{}"\n'.format(char))
return imagelist
def generate_random_string(tokens, zoom): # generate a totally random glyph for each glyph in the input string
imagelist = []
for char in tokens:
if char == " ": # add a " " space to the image list
imagelist.append(" ")
else:
if re.match("[a-z]", char): # generate lowercase letter
stack = generate("lc")
elif re.match("[A-Z]", char): # generate uppercase letter
stack = generate("UC")
else: # this character is not in config.alphabet, skip it
sys.stderr.write('bad character"{}"\n'.format(char))
stack = generate("start")
imagelist.append(draw_crop_scale(stack, zoom))
return imagelist
def optikern(image, width, zoom): # optical kerning algorithm
left = []
right = []
resolution = 8
for i in range(0, 18 * resolution):
y = 1.0 / resolution * (i + 0.5) * zoom
xmin = None
xmax = None
for cmd, params in image:
if cmd == 'M':
# A move cannot contribute to the bounding box
last = params[:]
lastctrl = params[:]
elif cmd == 'L':
if (last[1] <= y <= params[1]) or (params[1] <= 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 is None or x < xmin:
xmin = x
if xmax is 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 0 <= 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 is None or x < xmin:
xmin = x
if xmax is 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 is not None and xmax is not 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)
thisimage = (inkex.Path(thisimage).translate(position, 0)).to_arrays()
workspace = combinePaths(workspace, thisimage)
position = position + width + zoom # advance position by letter width
return workspace
def tokenize(text):
"""Tokenize the string, looking for LaTeX style, multi-character tokens in the string, like \\yogh."""
tokens = []
i = 0
while i < len(text):
c = text[i]
i += 1
if c == '\\': # found the beginning of an escape
t = ''
while i < len(text): # gobble up content of the escape
c = text[i]
if c == '\\': # found another escape, stop this one
break
i += 1
if c == ' ': # a space terminates this escape
break
t += c # stick this character onto the token
if t:
tokens.append(t)
else:
tokens.append(c)
return tokens
class AlphabetSoup(inkex.EffectExtension):
def add_arguments(self, pars):
pars.add_argument("-t", "--text", default="Inkscape", help="The text for alphabet soup")
pars.add_argument("-z", "--zoom", type=float, default=8.0, help="The zoom on the output")
pars.add_argument("-r", "--randomize", type=inkex.Boolean, default=False,\
help="Generate random (unreadable) text")
def effect(self):
zoom = self.svg.unittouu(str(self.options.zoom) + 'px')
if self.options.randomize:
imagelist = generate_random_string(self.options.text, zoom)
else:
tokens = tokenize(self.options.text)
imagelist = randomize_input_string(tokens, zoom)
image = layoutstring(imagelist, zoom)
if image:
s = {'stroke': 'none', 'fill': '#000000'}
new = inkex.PathElement(
style=str(inkex.Style(s)),
d=str(inkex.Path(image)))
layer = self.svg.get_current_layer()
layer.append(new)
# compensate preserved transforms of parent layer
if layer.getparent() is not None:
mat = (self.svg.get_current_layer().transform * inkex.Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])).matrix
new.transform *= -inkex.Transform(mat)
if __name__ == '__main__':
AlphabetSoup().run()