Created
November 21, 2011 16:52
-
-
Save zeffii/1383205 to your computer and use it in GitHub Desktop.
PostScript to javascript/paperjs using Python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
20 Nov, Dealga McArdle. 2011 | |
This script is released under the MIT license. With no warranty/support of any kind. | |
PostScript to javascript/paperjs converter written in Python. It should be | |
obvious to anyone with a brain that Adobe and PostScript are registered trademarks. | |
The Adobe PostScript References, PLRM.pdf, is available for free from their site. | |
Their TOC implies that it's OK to write programs that parse their .ps filetype. I plan | |
to support commands as i encounter them. | |
20 Nov, Basic Parsing and javascript writer for commands (m/l/c/h) | |
- [todo] experiment with list sorting for the compound path, paperjs expects the outeroutline | |
as the first argument for compoundpath creation, postscript natively doesn't seem to care. | |
- [todo] fix coordinate system, currently the Y coordinate is upside down. | |
''' | |
import re | |
rectHeight = 0 | |
def get_postscript(filename): | |
''' | |
minimal error checking, until the script progresses | |
input: A valid file, at a valid path | |
output: A multidimensional List of commands and coordinates. | |
my understanding is this, | |
- PostScript has the values first, then the function names. | |
- the usuable data starts after the line '0 g' and ends at Q Q | |
- thereafter the delimiter is f (f is fill) or h (to close path) | |
- i will be ignoring colour. don't expect this parser to deal | |
with anything other than black typography, kind of like a model T Ford. | |
''' | |
recordOn = False | |
joinString = "" | |
# it's not worth optimizing this for speed on non body text like headings | |
# but if this was parsing body text, i would consider splitting the data | |
# finding (begin and end token) and data extraction (returning joinString) | |
filedata = open(filename) | |
for line in filedata: | |
# checking, slightly redundant if statement, but useful for debug | |
if not recordOn and line.startswith("%%EndPageSetup\n"): | |
print("found " + line[:-1] + " start parsing") | |
continue | |
# coding in the dark, this hopefully determines the height of the job. | |
if line.endswith("rectclip q\n"): | |
print(line[:-1]) | |
rectClipList = line.split() | |
global rectHeight | |
rectHeight = rectClipList[-3] | |
continue | |
# this is not pretty, but it forces black! :) | |
if line.endswith("0 g\n"): | |
recordOn = True | |
continue | |
# end token | |
if line == "Q Q\n": | |
print("found Q Q, ending parser") | |
break | |
# assimilate | |
if recordOn: | |
if line.endswith("h\n"): | |
line = line[:-1] + " \n" | |
joinString += line[:-1] | |
filedata.close() | |
return joinString.rstrip() | |
def regex_this_string(subString): | |
''' | |
Takes an unsplit string, and splits by m/l/c/h commands respectively | |
intput: String, in the form of a split ps file, (split by f character) | |
output: Array of coordinates and command they are associated with. | |
''' | |
# my pattern is a little dirty, | |
p = re.compile('(([-0-9.]+\s)+[mlc])|[h]') | |
m = p.findall(subString) | |
# adding the [0] strips the extra data, | |
# but the pattern should be repaired instead. | |
cleanStringList = [] | |
for i in m: | |
if i == ('',''): | |
cleanStringList.append("h") | |
else: | |
cleanStringList.append(i[0]) | |
return cleanStringList | |
def parse_postscript(fullString): | |
''' | |
Takes the full concatenated version of the ps file and chops it up. | |
intput: takes full string from get_postscript function | |
output: generates a list of discrete commands for every object found. | |
''' | |
commandList = [] | |
# because the last possible parsable character is also an f | |
# split creates one extra empty list member, [:-1] drops it. | |
commandListString = fullString.split(" f")[:-1] | |
for item in commandListString: | |
commandList.append(regex_this_string(item)) | |
# print(item) | |
return commandList | |
def write_postscript_functions(newPath, functionName, writefile): | |
''' | |
Create separated functions for each path/compound path, probably a backwards way.. | |
input: parsed List of path commands for each glyph ( one glyph may contain 1 or more paths) | |
output: adds functions to the currently open file. | |
''' | |
writefile.write("function "+functionName+"(){\n") | |
numPaths = 0 | |
lineCounter = 0 | |
pathNames = [] | |
indent = " " | |
# helper function, | |
def pointify_coordinates(coordinates): | |
myX = float(coordinates[0]) | |
myY = float(coordinates[1]) * -1.0 | |
return "point + ["+str(myX)+ ', ' + str(myY) + "]" | |
writefile.write(indent + "var point = new Point(0, "+rectHeight+");\n") | |
for line in newPath: | |
lineArray = line.split() | |
# print(lineCounter, len(newPath)) # for debug | |
pathname = "path" + str(numPaths) | |
if lineCounter == 0: | |
lineToPrint = indent + "var " + pathname + " = new Path();\n" | |
pathNames.append(pathname) | |
writefile.write(lineToPrint) | |
# todo , deal with additional paths. | |
foundChar = lineArray[-1] | |
if foundChar == 'm': | |
command = ".moveTo(" | |
elif foundChar == 'l': | |
command = ".lineTo(" | |
elif foundChar == 'c': | |
command = ".cubicCurveTo(" | |
if foundChar == 'm' or foundChar == 'l': | |
# modifiy this for point + [ ] or new Point formats. | |
coordinates = pointify_coordinates(lineArray[0:2]) | |
if lineCounter == len(newPath)-1: | |
pathname = indent + "path" + str(numPaths-1) | |
lineToPrint = pathname + command + coordinates + ");\n" | |
writefile.write(lineToPrint) | |
break | |
else: | |
lineToPrint = indent + pathname + command + coordinates + ");\n" | |
writefile.write(lineToPrint) | |
lineCounter += 1 | |
continue | |
if foundChar == 'c': | |
# print(lineArray) | |
handle1 = pointify_coordinates(lineArray[:2]) | |
handle2 = pointify_coordinates(lineArray[2:4]) | |
destination = pointify_coordinates(lineArray[4:6]) | |
coordinates = handle1 + ", " + handle2 + ", " + destination | |
lineToPrint = indent + pathname + command + coordinates + ");\n" | |
lineCounter += 1 | |
writefile.write(lineToPrint) | |
continue | |
if foundChar == 'h': | |
lineToPrint = indent + pathname + ".closePath();\n" | |
numPaths += 1 | |
lineCounter += 1 | |
writefile.write(lineToPrint) | |
if lineCounter is not len(newPath)-1: | |
writefile.write("\n") | |
pathname = "path" + str(numPaths) | |
lineToPrint = indent + "var " + pathname + " = new Path();\n" | |
pathNames.append(pathname) | |
writefile.write(lineToPrint) | |
# deal with compoundpath | |
if len(pathNames) > 1: | |
paths = ", ".join(pathNames) | |
# use the autosorting function, | |
writefile.write("\n") | |
writefile.write(indent + "var unsortedPathList = [" +paths+ "];\n") | |
writefile.write(indent + "var sortedPathList = unsortedPathList.sort(sortOnBoundsSize);\n") | |
lineToPrint = indent + "var compoundPath = new CompoundPath(sortedPathList);\n" | |
writefile.write(lineToPrint) | |
writefile.write(indent + "compoundPath.fillColor = 'black';\n") | |
writefile.write(indent + "compoundPath.strokeColor = 'black';\n") | |
else: | |
writefile.write(indent + "path0.fillColor = 'black';\n") | |
writefile.write("}\n") | |
writefile.write(functionName+"();\n") | |
writefile.write("\n\n") | |
def write_sorting_functions(writefile): | |
''' | |
unconfigurable, takes no parameters but writes path sorting function | |
function sortOnBoundsSize(p1, p2){ | |
x = (p1.bounds.width * p1.bounds.height); | |
y = (p2.bounds.width * p2.bounds.height); | |
if (x < y) | |
return 1; | |
else if (x==y) | |
return 0; | |
else | |
return -1; | |
} | |
''' | |
indent = " " | |
writefile.write("\n") | |
writefile.write("function sortOnBoundsSize(p1, p2){\n") | |
writefile.write("\n") | |
writefile.write(indent+"x = (p1.bounds.width * p1.bounds.height);\n") | |
writefile.write(indent+"y = (p2.bounds.width * p2.bounds.height);\n") | |
writefile.write("\n") | |
writefile.write(indent+"if (x < y)\n") | |
writefile.write(indent+indent+"return 1;\n") | |
writefile.write(indent+"else if (x==y)\n") | |
writefile.write(indent+indent+"return 0;\n") | |
writefile.write(indent+"else\n") | |
writefile.write(indent+indent+"return -1;\n") | |
writefile.write("\n") | |
writefile.write("}\n") | |
writefile.write("\n") | |
writefile.write("\n") | |
def create_html(commandList, javaScriptFileName): | |
''' | |
Takes a List of paths found in the .ps and makes js compatible syntax | |
input: Multidimensional list of strings similar to .ps commands | |
output: Similar to input but formatted to be paperpJS readable. | |
m = moveTo, l = lineTo, c = cubicCurveTo, h = close ''' | |
title = javaScriptFileName | |
writefile = open(javaScriptFileName, 'w') | |
writefile.write("<!DOCTYPE html>\n") | |
writefile.write("<html>\n") | |
writefile.write("<head>\n") | |
writefile.write(" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n") | |
writefile.write(" <title>"+ title+ "</title>\n") | |
writefile.write(" <link rel=\"stylesheet\" href=\"../css/style2.css\">\n") | |
writefile.write(" <script type=\"text/javascript\" src=\"../../lib/paper.js\"></script>\n") | |
writefile.write(" <script type=\"text/paperscript\" canvas=\"canvas\">\n") | |
# add the auto sorting functions for the compound path array | |
write_sorting_functions(writefile) | |
# add postscript functions written in javascript | |
glyphCounter = 0 | |
for newPath in commandList: | |
functionName = "draw_glyph_" + str(glyphCounter) | |
write_postscript_functions(newPath, functionName, writefile) | |
glyphCounter += 1 | |
writefile.write(" </script>\n") | |
writefile.write("</head>\n") | |
writefile.write("<body>\n") | |
writefile.write(" <canvas id=\"canvas\" width=\"1200\" height=\"900\"></canvas>\n") | |
writefile.write("\n") | |
writefile.write("</body>\n") | |
writefile.write("</html>\n") | |
writefile.close() | |
javaScriptFileName = "drawing_typography3.html" | |
fullString = get_postscript("drawing_normal3.ps") | |
commandList = parse_postscript(fullString) | |
create_html(commandList, javaScriptFileName) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment