This recipe provides a mini-framework for creating custom Postscript PS and PDF files from scratch. It includes sample code for a personalized business index card.
Recipe does not use any Python PDF libraries. However, Ghostscript and a PDF viewer are useful for displaying/debugging output.
It's easier than you might think to roll your own Postscript code!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | """
recipe_custom_ps.py
Template with sample code creating for custom Postscript PS files.
Sample code is a business card based on Levenger's personalized
3x5 card.
Ghostscript provides easiest way to view PS output, but not essential.
Utilities included.
Download Ghostscript:
http://www.ghostscript.com/download/gsdnld.html
"""
__author__ = "Jack Trainor"
__date__ = "2015-12-09"
import sys
import os.path
import subprocess
######################################################################
"""
String constants for business index card.
"""
NAME = "John Doe"
ADDRESS = "123 Main Street"
CITY = "Anytown, USA 01234"
EMAIL = "johndoe123@gmail.com"
PHONE = "1.555.123.4567"
FAX = "1.555.123.4568"
CELL = "1.555.123.4569"
PHONE_FIELD = "Phone"
FAX_FIELD = "Fax"
CELL_FIELD = "Cell"
######################################################################
"""
Auxiliary utilities.
*** REPLACE WITH VALID PATHS ON YOUR MACHINE. ***
"""
PDF_VIEWER = r"C:\Program Files\Foxit Software\Foxit Reader\FoxitReader.exe"
GSC = r"C:\Program Files\gs\gs9.16\bin\gswin32c.exe"
def pdfview(path):
""" Use PDF viewer to display pdf file. """
if os.path.exists(PDF_VIEWER):
subprocess.call([PDF_VIEWER, path])
else:
println("%s not installed." % PDF_VIEWER)
def gs_ps2pdf(input_ps, output_pdf):
""" Use Ghostscript to convert PS file to PDF file """
if os.path.exists(GSC):
println("gs_ps2pdf: %s -> %s" % (input_ps, output_pdf))
args =[GSC,
"-o", output_pdf,
"-sDEVICE=pdfwrite",
"-dPDFSETTINGS=/prepress",
"-dEmbedAllFonts=true",
"-dSubsetFonts=false",
# "-sFONTPATH=%s" % CUSTOM_FONTS # for custom fonts if any
"-dBATCH",
"-dQUIET",
"-c", ".setpdfwrite <</NeverEmbed [ ]>> setdistillerparams",
"-f"]
args.append(input_ps)
subprocess.call(args)
else:
println("%s not installed." % GSC)
def println(line):
sys.stdout.write(line + "\n")
######################################################################
"""
Postscript boilerplate for prolog and epilog of PS file.
Add your own Postscript procedures in prolog.
"""
PS_PROLOG = """%!PS-Adobe-2.0
%--------- Procedures ----------------
% Optimize without dict variables later, if at all
/rectPath % stk: width height left top => --
{ /t exch def
/l exch def
/h exch def
/w exch def
newpath
l t moveto
w 0 rlineto
0 h neg rlineto
w neg 0 rlineto
0 h rlineto
} def
/centershow % stk: y leftmargin rightmargin string => --
{ /s exch def
/rm exch def
/lm exch def
/y exch def
rm lm sub
s stringwidth pop sub
2 div
lm add y moveto
s show } def
/rightshow % stk: y rightmargin string => --
{ /s exch def
/rm exch def
/y exch def
s stringwidth pop
rm exch sub
y moveto
s show } def
/gridPath % stk: rows cols cellside left top => --
{ /top exch def
/left exch def
/cellside exch def
/cols exch def
/rows exch def
/width cellside cols mul def
/height cellside rows mul def
newpath
top /y exch def
left /x exch def
0 1 rows {
x y moveto
width 0 rlineto
y cellside sub /y exch def
} for
top /y exch def
left /x exch def
0 1 cols {
x y moveto
0 height neg rlineto
x cellside add /x exch def
} for
} def
%---------- PS Card --------------------
"""
PS_EPILOG = """
%---------- Epilog ---------------------
% done with this page
showpage
"""
######################################################################
"""
Constants for PS page and PS card
"""
DPI = 72.0
def inches_to_dots(inches):
return inches * DPI
PAGE_WIDTH = inches_to_dots(8.5)
PAGE_HEIGHT = inches_to_dots(11.0)
CARD_WIDTH = inches_to_dots(3.0)
CARD_HEIGHT = inches_to_dots(5.0)
CARD_LEFT = inches_to_dots(0.0)
CARD_TOP = CARD_HEIGHT
CARD_MARGIN_X = inches_to_dots(.25)
CARD_MARGIN_Y = inches_to_dots(.25)
GRID_CELLSIZE = inches_to_dots(.25)
GRID_ROWS = 15
GRID_COLS = 10
GRID_LEFT = CARD_MARGIN_X
GRID_TOP = CARD_MARGIN_Y + GRID_ROWS * GRID_CELLSIZE
NAME_FONT = "Palatino-bold"
NAME_FONTSIZE = 13.0
FIELDS_FONT = "Palatino-medium"
FIELDS_FONTSIZE = 8.0
TITLE_HEIGHT = inches_to_dots(.25)
FIELDS_HEIGHT = inches_to_dots(.145)
TITLE_Y = CARD_TOP - CARD_MARGIN_Y + inches_to_dots(.07)
NAME_Y = TITLE_Y - TITLE_HEIGHT
ADDRESS_Y = NAME_Y - FIELDS_HEIGHT
CITY_Y = ADDRESS_Y - FIELDS_HEIGHT
EMAIL_Y = CITY_Y - FIELDS_HEIGHT
FIELD_NAME_X = inches_to_dots(2.0)
FIELD_VAL_X = inches_to_dots(2.05)
LINEWIDTH = 0.5
TEMPLATE_ROWS = 1
TEMPLATE_COLS = 1
TEMPLATE_MARGIN_BOTTOM = inches_to_dots(0.5)
TEMPLATE_MARGIN_LEFT = inches_to_dots(0.75)
######################################################################
"""
Python wrappers for PS calls. Extend as necessary. Avoid using
raw Postscript in PsCard and PsPage calls.
"""
def setFont(font, fontsize):
return "/%s findfont %f scalefont setfont" % (font, fontsize)
def rectPath( width, height, left, top):
return "%.3f %.3f %.3f %.3f rectPath" % (width, height, left, top)
def gridPath(rows, cols, cellside, left, top):
return "%d %d %.3f %.3f %.3f gridPath" % (rows, cols, cellside, left, top)
def leftShow(x, y, s):
return "%.3f %.3f moveto (%s) show" % (x, y, s)
def rightShow(x, y, s):
return "%.3f %.3f (%s) rightshow" % (y, x, s)
def linewidth(lw):
return "%.3f setlinewidth" % lw
def setgray(percent):
return " %.3f setgray" % percent
def translate(x, y):
return "%.3f %.3f translate" % (x, y)
def gsave():
return "gsave"
def grestore():
return "grestore"
def draw_grid(lines, rows, cols, cellside, left, top):
lines.append(gridPath(rows, cols, cellside, left, top))
lines.append("stroke")
def draw_rect(lines, width, height, left, top):
lines.append(rectPath(width, height, left, top))
lines.append("stroke")
######################################################################
class PsRect(object):
""" Uitility class for Poscript rect """
def __init__(self, left, top, width, height):
self.top = top
self.left = left
self.width = width
self.height = height
self.calc_fields()
def calc_fields(self):
self.bottom = self.top-self.height
self.right = self.left+self.width
self.center_x = self.left+self.width/2.0
self.center_y = self.top-self.height/2.0
def copy(self):
return PsRect(self.left, self.top, self.width, self.height)
def inset(self, inset_x, inset_y):
self.left += inset_x
self.top -= inset_y
self.width -= 2*inset_y
self.height -= 2*inset_y
return self
def to_ps(self, lw):
lines = []
lines.append(linewidth(lw))
draw_rect(lines, self.width, self.height, self.left, self.top)
return "\n".join(lines)
######################################################################
class PsCard(object):
""" PsCard supports Postscript description of a single card. """
def __init__(self):
pass
def to_ps(self, lines):
rect = PsRect(CARD_LEFT, CARD_TOP, CARD_WIDTH, CARD_HEIGHT)
lines.append(rect.to_ps(LINEWIDTH))
lines.append(setFont(NAME_FONT, NAME_FONTSIZE))
lines.append(leftShow(CARD_MARGIN_X, NAME_Y, NAME))
lines.append(setFont(FIELDS_FONT, FIELDS_FONTSIZE))
lines.append(leftShow(CARD_MARGIN_X, ADDRESS_Y, ADDRESS))
lines.append(leftShow(CARD_MARGIN_X, CITY_Y, CITY))
lines.append(leftShow(CARD_MARGIN_X, EMAIL_Y, EMAIL))
lines.append(rightShow(FIELD_NAME_X, ADDRESS_Y, PHONE_FIELD))
lines.append(rightShow(FIELD_NAME_X, CITY_Y, FAX_FIELD))
lines.append(rightShow(FIELD_NAME_X, EMAIL_Y, CELL_FIELD))
lines.append(leftShow(FIELD_VAL_X, ADDRESS_Y, PHONE))
lines.append(leftShow(FIELD_VAL_X, CITY_Y, FAX))
lines.append(leftShow(FIELD_VAL_X, EMAIL_Y, CELL))
lines.append(setgray(0.8))
lines.append(linewidth(0.5))
draw_grid(lines, GRID_ROWS, GRID_COLS, GRID_CELLSIZE, GRID_LEFT, GRID_TOP)
######################################################################
class PsPage(object):
""" Supports Postscript description of a single page. """
def __init__(self):
self.page_ps = ""
self.page_path = ""
self.card = PsCard()
def cards_to_ps(self, lines):
""" Can tile cards to page if TEMPLATE_ROWS and TEMPLATE_COLS > 1 """
for row in range(0, TEMPLATE_ROWS):
for col in range(0, TEMPLATE_COLS):
x = TEMPLATE_MARGIN_LEFT + col * CARD_WIDTH
y = TEMPLATE_MARGIN_BOTTOM + row * CARD_HEIGHT
self.card_to_ps(lines, x, y)
def card_to_ps(self, lines, x, y):
lines.append(gsave())
lines.append(translate(x, y))
self.card.to_ps(lines)
lines.append(grestore())
lines.append("\n")
def to_ps(self):
lines = []
lines.append(PS_PROLOG)
self.cards_to_ps(lines)
lines.append(PS_EPILOG)
return "\n".join(lines)
######################################################################
OUTPUT_DIR = "C:\\"
PSCARD_PS = "ps_indexcard.ps"
PSCARD_PDF = "ps_indexcard.pdf"
def test_page():
page = PsPage()
ps = page.to_ps()
ps_path = os.path.join(OUTPUT_DIR, PSCARD_PS)
with open(ps_path, "w") as out:
out.write(ps)
pdf_path = os.path.join(OUTPUT_DIR, PSCARD_PDF)
gs_ps2pdf(ps_path, pdf_path)
pdfview(pdf_path)
######################################################################
if __name__ == "__main__":
test_page()
|
Sometime ago I admired a friend's personalized index cards which he used for business. They were custom-made for about a dime apiece by Levenger:
I decided it would be simple enough to create an equivalent document file and print cards out on my own printer, but that turned out more difficult to get the precise same results.
So I shifted my approach to writing Postscript directly and it wasn't as hard as I expected. Since Postscript is a stack-based language like Forth, it's tedious to get Postscript code right. But other than that, it's just a matter of following the Adobe guidelines for a Postscript file.
By all means, download the free Adobe books on Postscript:
https://www.adobe.com/products/postscript/pdfs/PLRM.pdf http://www-cdf.fnal.gov/offline/PostScript/BLUEBOOK.PDF
When writing Postscript, I recommend binding function arguments to variables in the procedure rather than manipulating everything on the stack as some recommend because the latter is more memory efficient.
There are various ways to view the results of your Postscript file. The most productive method I found was to convert the PS file to a PDF file using Ghostscript and use Foxit to view that file. I have included utilities in the recipe for doing so.
Download Ghostscript at http://www.ghostscript.com/download/gsdnld.html .
Thanks to Peter Deutsch for Ghostscript. Read about him in Peter Seibel's "Coders at Work."