""" 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 <> 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()