Welcome, guest | Sign In | My Account | Store | Cart
"""
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()

History