Chris Rathman / ChrisRath@aol.com

PDF Hello World from Python


import time

PAGE_WIDTH           = 612
PAGE_HEIGHT          = 792

out_file             = open("hello.pdf", "wb")
index_byte           = 0
index_xref           = 0
page_index           = -1
content_index        = -1

dictionary_object    = {}
dictionary_info      = {}
dictionary_outlines  = {}
dictionary_procset   = {}
dictionary_font      = {}
dictionary_content   = {}
dictionary_page      = {}
dictionary_pages     = {}
dictionary_root      = {}

# rgb values for named html colors
color_table = {
   "aliceblue":            "0.94 0.97 1",
   "antiquewhite":         "0.98 0.92 0.84",
   "aqua":                 "0 1 1",
   "aquamarine":           "0.5 1 0.83",
   "azure":                "0.94 1 1",
   "beige":                "0.96 0.96 0.86",
   "bisque":               "1 0.89 0.77",
   "black":                "0 0 0",
   "blanchedalmond":       "1 0.92 0.8",
   "blue":                 "0 0 1",
   "blueviolet":           "0.54 0.17 0.88",
   "brown":                "0.64 0.16 0.16",
   "burlywood":            "0.87 0.72 0.53",
   "cadetblue":            "0.37 0.62 0.63",
   "chartreuse":           "0.5 1 0",
   "chocolate":            "0.82 0.41 0.12",
   "coral":                "1 0.5 0.31",
   "cornflowerblue":       "0.39 0.58 0.93",
   "cornsilk":             "1 0.97 0.86",
   "crimson":              "0.86 0.08 0.23",
   "cyan":                 "0 1 1",
   "darkblue":             "0 0 0.54",
   "darkcyan":             "0 0.54 0.54",
   "darkgoldenrod":        "0.72 0.52 0.04",
   "darkgray":             "0.66 0.66 0.66",
   "darkgreen":            "0 0.39 0",
   "darkkhaki":            "0.74 0.71 0.42",
   "darkmagenta":          "0.54 0 0.54",
   "darkolivegreen":       "0.33 0.42 0.18",
   "darkorange":           "1 0.55 0",
   "darkorchid":           "0.6 0.2 0.8",
   "darkred":              "0.54 0 0",
   "darksalmon":           "0.91 0.59 0.48",
   "darkseagreen":         "0.56 0.73 0.56",
   "darkslateblue":        "0.28 0.24 0.54",
   "darkslategray":        "0.18 0.31 0.31",
   "darkturquoise":        "0 0.8 0.82",
   "darkviolet":           "0.58 0 0.82",
   "deeppink":             "1 0.08 0.57",
   "deepskyblue":          "0 0.75 1",
   "dimgray":              "0.41 0.41 0.41",
   "dodgerblue":           "0.12 0.56 1",
   "firebrick":            "0.7 0.13 0.13",
   "floralwhite":          "1 0.98 0.94",
   "forestgreen":          "0.13 0.54 0.13",
   "fuchsia":              "1 0 1",
   "gainsboro":            "0.86 0.86 0.86",
   "ghostwhite":           "0.97 0.97 1",
   "gold":                 "1 0.84 0",
   "goldenrod":            "0.85 0.64 0.13",
   "gray":                 "0.5 0.5 0.5",
   "green":                "0 0.5 0",
   "greenyellow":          "0.68 1 0.18",
   "honeydew":             "0.94 1 0.94",
   "hotpink":              "1 0.41 0.7",
   "indianred":            "0.8 0.36 0.36",
   "indigo":               "0.29 0 0.51",
   "ivory":                "1 1 0.94",
   "khaki":                "0.94 0.9 0.55",
   "lavender":             "0.9 0.9 0.98",
   "lavenderblush":        "1 0.94 0.96",
   "lawngreen":            "0.48 0.98 0",
   "lemonchiffon":         "1 0.98 0.8",
   "lightblue":            "0.68 0.84 0.9",
   "lightcoral":           "0.94 0.5 0.5",
   "lightcyan":            "0.88 1 1",
   "lightgoldenrodyellow": "0.98 0.98 0.82",
   "lightgreen":           "0.56 0.93 0.56",
   "lightgrey":            "0.82 0.82 0.82",
   "lightpink":            "1 0.71 0.75",
   "lightsalmon":          "1 0.63 0.48",
   "lightseagreen":        "0.13 0.7 0.66",
   "lightskyblue":         "0.53 0.8 0.98",
   "lightslategray":       "0.46 0.53 0.6",
   "lightsteelblue":       "0.69 0.77 0.87",
   "lightyellow":          "1 1 0.88",
   "lime":                 "0 1 0",
   "limegreen":            "0.2 0.8 0.2",
   "linen":                "0.98 0.94 0.9",
   "magenta":              "1 0 1",
   "maroon":               "0.5 0 0",
   "mediumaquamarine":     "0.4 0.8 0.66",
   "mediumblue":           "0 0 0.8",
   "mediumorchid":         "0.73 0.33 0.82",
   "mediumpurple":         "0.57 0.44 0.86",
   "mediumseagreen":       "0.23 0.7 0.44",
   "mediumslateblue":      "0.48 0.41 0.93",
   "mediumspringgreen":    "0 0.98 0.6",
   "mediumturquoise":      "0.28 0.82 0.8",
   "mediumvioletred":      "0.78 0.08 0.52",
   "midnightblue":         "0.1 0.1 0.44",
   "mintcream":            "0.96 1 0.98",
   "mistyrose":            "1 0.89 0.88",
   "moccasin":             "1 0.89 0.71",
   "navajowhite":          "1 0.87 0.68",
   "navy":                 "0 0 0.5",
   "navyblue":             "0.62 0.68 0.87",
   "oldlace":              "0.99 0.96 0.9",
   "olive":                "0.5 0.5 0",
   "olivedrab":            "0.42 0.55 0.14",
   "orange":               "1 0.64 0",
   "orangered":            "1 0.27 0",
   "orchid":               "0.85 0.44 0.84",
   "palegoldenrod":        "0.93 0.91 0.66",
   "palegreen":            "0.59 0.98 0.59",
   "paleturquoise":        "0.68 0.93 0.93",
   "palevioletred":        "0.86 0.44 0.57",
   "papayawhip":           "1 0.93 0.83",
   "peachpuff":            "1 0.85 0.72",
   "peru":                 "0.8 0.52 0.25",
   "pink":                 "1 0.75 0.79",
   "plum":                 "0.86 0.63 0.86",
   "powderblue":           "0.69 0.88 0.9",
   "purple":               "0.5 0 0.5",
   "red":                  "1 0 0",
   "rosybrown":            "0.73 0.56 0.56",
   "royalblue":            "0.25 0.41 0.88",
   "saddlebrown":          "0.54 0.27 0.07",
   "salmon":               "0.98 0.5 0.45",
   "sandybrown":           "0.95 0.64 0.38",
   "seagreen":             "0.18 0.54 0.34",
   "seashell":             "1 0.96 0.93",
   "sienna":               "0.63 0.32 0.18",
   "silver":               "0.75 0.75 0.75",
   "skyblue":              "0.53 0.8 0.92",
   "slateblue":            "0.41 0.35 0.8",
   "slategray":            "0.44 0.5 0.56",
   "snow":                 "1 0.98 0.98",
   "springgreen":          "0 1 0.5",
   "steelblue":            "0.27 0.51 0.7",
   "tan":                  "0.82 0.7 0.55",
   "teal":                 "0 0.5 0.5",
   "thistle":              "0.84 0.75 0.84",
   "tomato":               "1 0.39 0.28",
   "turquoise":            "0.25 0.88 0.81",
   "violet":               "0.93 0.51 0.93",
   "wheat":                "0.96 0.87 0.7",
   "white":                "1 1 1",
   "whitesmoke":           "0.96 0.96 0.96",
   "yellow":               "1 1 0",
   "yellowgreen":          "0.54 0.8 0.2"}

# these are the font-widths for the standard fonts
# useful for text alignment and text justify
# print font_width["Times-Roman"][ord("A")] * 12 / 1000.0
# font width values were borrowed from DelphiPDF by Stefan Menten
# (see http://www.torry.net/unitsconversion.htm)
font_width = {
   "Times-Roman": [
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
        250,  333,  408,  500,  500,  833,  778,  333,  333,  333,  500,  564,  250,  333,  250,  278,
        500,  500,  500,  500,  500,  500,  500,  500,  500,  500,  278,  278,  564,  564,  564,  444,
        921,  722,  667,  667,  722,  611,  556,  722,  722,  333,  389,  722,  611,  889,  722,  722,
        556,  722,  667,  556,  611,  722,  722,  944,  722,  722,  611,  333,  278,  333,  469,  500,
        333,  444,  500,  444,  500,  444,  333,  500,  500,  278,  278,  500,  278,  778,  500,  500,
        500,  500,  333,  389,  278,  500,  500,  722,  500,  500,  444,  480,  200,  480,  541,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,  333,  500,    0,  167,  500,  500,  500,  500,  180,  444,  500,  333,  333,  556,  556,
          0,  500,  500,  500,  250,    0,  453,  350,  333,  444,  444,  500,  100,  100,    0,  444,
          0,  333,  333,  333,  333,  333,  333,  333,  333,    0,  333,  333,  333,  333,  333,  100,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
        889,    0,  276,    0,    0,    0,    0,  611,  722,  889,  310,    0,    0,    0,    0,    0,
        667,    0,    0,    0,  278,    0,    0,  278,  500,  722,  500,    0,    0,    0,    0,    0
   ],
   "Times-Bold": [
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
        250,  333,  555,  500,  500,  100,  833,  333,  333,  333,  500,  570,  250,  333,  250,  278,
        500,  500,  500,  500,  500,  500,  500,  500,  500,  500,  333,  333,  570,  570,  570,  500,
        930,  722,  667,  722,  722,  667,  611,  778,  778,  389,  500,  778,  667,  944,  722,  778,
        611,  778,  722,  556,  667,  722,  722,  100,  722,  722,  667,  333,  278,  333,  581,  500,
        333,  500,  556,  444,  556,  444,  333,  500,  556,  278,  333,  556,  278,  833,  556,  500,
        556,  556,  444,  389,  333,  556,  500,  722,  500,  500,  444,  394,  220,  394,  520,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,  333,  500,  500,  167,  500,  500,  500,  500,  278,  500,  500,  333,  333,  556,  556,
          0,  500,  500,  500,  250,    0,  540,  350,  333,  500,  500,  500,  100,  100,    0,  500,
          0,  333,  333,  333,  333,  333,  333,  333,  333,    0,  333,  333,    0,  333,  333,  333,
        100,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,  100,    0,  300,    0,    0,    0,    0,  667,  778,  100,  330,    0,    0,    0,    0,
          0,  722,    0,    0,    0,  278,    0,    0,  278,  500,  722,  556,    0,    0,    0,    0
   ],
   "Times-Italic": [
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
        250,  333,  420,  500,  500,  833,  778,  333,  333,  333,  500,  675,  250,  333,  250,  278,
        500,  500,  500,  500,  500,  500,  500,  500,  500,  500,  333,  333,  675,  675,  675,  500,
        920,  611,  611,  667,  722,  611,  611,  722,  722,  333,  444,  667,  556,  833,  667,  722,
        611,  722,  611,  500,  556,  722,  611,  833,  611,  556,  556,  389,  278,  389,  422,  500,
        333,  500,  500,  444,  500,  444,  278,  500,  500,  278,  278,  444,  278,  722,  500,  500,
        500,  500,  389,  389,  278,  500,  444,  667,  444,  444,  389,  400,  275,  400,  541,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,  389,  500,  500,  167,  500,  500,  500,  500,  214,  556,  500,  333,  333,  500,  500,
          0,  500,  500,  500,  250,    0,  523,  350,  333,  556,  556,  500,  889,  100,    0,  500,
          0,  333,  333,  333,  333,  333,  333,  333,  333,    0,  333,  333,    0,  333,  333,  333,
        889,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,  889,    0,  276,    0,    0,    0,    0,  556,  722,  944,  310,    0,    0,    0,    0,
          0,  667,    0,    0,    0,  278,    0,    0,  278,  500,  667,  500,    0,    0,    0,    0
   ],
   "Helvetica": [
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
        278,  278,  355,  556,  556,  889,  667,  222,  333,  333,  389,  584,  278,  333,  278,  278,
        556,  556,  556,  556,  556,  556,  556,  556,  556,  556,  278,  278,  584,  584,  584,  556,
        102,  667,  667,  722,  722,  667,  611,  778,  722,  278,  500,  667,  556,  833,  722,  778,
        667,  778,  722,  667,  611,  722,  667,  944,  667,  667,  611,  278,  278,  278,  469,  556,
        222,  556,  556,  500,  556,  556,  278,  556,  556,  222,  222,  500,  222,  833,  556,  556,
        556,  556,  333,  500,  278,  556,  500,  722,  500,  500,  500,  334,  260,  334,  584,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,  333,  556,  556,  167,  556,  556,  556,  556,  191,  333,  556,  333,  333,  500,  500,
          0,  556,  556,  556,  278,    0,  537,  350,  222,  333,  333,  556,  100,  100,    0,  611,
          0,  333,  333,  333,  333,  333,  333,  333,  333,    0,  333,  333,  333,  333,  333,  100,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
        100,    0,  370,    0,    0,    0,    0,    0,  556,  778,  100,  365,    0,    0,    0,    0,
          0,  889,    0,    0,    0,  278,    0,    0,  222,  611,  944,  611,    0,    0,    0,    0
   ],
   "Helvetica-Bold": [
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
        278,  333,  474,  556,  556,  889,  722,  278,  333,  333,  389,  584,  278,  333,  278,  278,
        556,  556,  556,  556,  556,  556,  556,  556,  556,  556,  333,  333,  584,  584,  584,  611,
        975,  722,  722,  722,  722,  667,  611,  778,  722,  278,  556,  722,  611,  833,  722,  778,
        667,  778,  722,  667,  611,  722,  667,  944,  667,  667,  611,  333,  278,  333,  584,  556,
        278,  556,  611,  556,  611,  556,  333,  611,  611,  278,  278,  556,  278,  889,  611,  611,
        611,  611,  389,  556,  333,  611,  556,  778,  556,  556,  500,  389,  228,  389,  584,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,  333,  556,  556,  167,  556,  556,  556,  556,  238,  500,  556,  333,  333,  611,  611,
          0,  556,  556,  556,  278,    0,  556,  350,  278,  500,  500,  556,  100,  100,    0,  611,
          0,  333,  333,  333,  333,  333,  333,  333,  333,    0,  333,  333,  333,  333,  333,  100,
          0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
        100,    0,  370,    0,    0,    0,    0,    0,  611,  778,  100,  365,    0,    0,    0,    0,
          0,  889,    0,    0,    0,  278,    0,    0,  278,  611,  944,  611,    0,    0,    0,    0
   ]
}

# output a string to the pdf file
def pdf_write(output_string):
   global index_byte
   out_file.write(output_string)
   index_byte += len(output_string)

# allocate a pdf object
def obj_allocate():
   index_obj = len(dictionary_object) + 1
   dictionary_object[index_obj] = index_byte
   return index_obj

# escape out a pdf string
def escape_pdf(s):
   return s.replace("\\","\\\\").replace("(","\\(").replace(")","\\)")

# find the length of a text string
def text_width(s, font_name, font_size):
   rval = 0
   for c in s:
      rval = rval + font_width[font_name][ord(c)] * font_size / 1000.0
   return rval


# pdf version identifier
pdf_write("%PDF-1.4\n")

# indicate binary content for viewer
pdf_write("%рсту\n")

# document information dictionary
dictionary_info[0] = obj_allocate()
pdf_write("%s 0 obj\n" % dictionary_info[0])
pdf_write("<<\n")
pdf_write("/Title (%s)\n"        % escape_pdf("a titilating title"))
pdf_write("/Producer (%s)\n"     % escape_pdf("Critter's Underground Software"))
pdf_write("/Creator (%s)\n"      % escape_pdf("Chris Rathman"))
pdf_write("/Version (0.01)\n")
pdf_write("/Subject (%s)\n"      % escape_pdf("Subject Matter"))
pdf_write("/Author (%s)\n"       % escape_pdf("Chris Rathman"))
pdf_write("/Keywords (%s)\n"     % escape_pdf("How to write PDF from Python"))
pdf_write("/CreationDate (%s)\n" % time.strftime("%Y%m%d%H%M%S-05'00'"))
pdf_write(">>\n")
pdf_write("endobj\n")

# outline dictionary
dictionary_outlines[0] = obj_allocate()
pdf_write("%d 0 obj\n" % dictionary_outlines[0])
pdf_write("<<\n")
pdf_write("/Type /Outlines\n")
pdf_write("/Count 0\n")
pdf_write(">>\n")
pdf_write("endobj\n")

# procedure set - array of predefined procedure set names
dictionary_procset[0] = obj_allocate()
pdf_write("%d 0 obj\n" % dictionary_procset[0])
pdf_write("[/PDF /Text]\n")
pdf_write("endobj\n")

# font dictionary
dictionary_font["Times-Roman"] = obj_allocate()
pdf_write("%d 0 obj\n" % dictionary_font["Times-Roman"])
pdf_write("<<\n")
pdf_write("/Type /Font\n")
pdf_write("/Subtype /Type1\n")
pdf_write("/Name /Times-Roman\n")
pdf_write("/BaseFont /Times-Roman\n")
pdf_write("/Encoding /MacRomanEncoding\n")
pdf_write(">>\n")
pdf_write("endobj\n")

dictionary_font["Times-Bold"] = obj_allocate()
pdf_write("%d 0 obj\n" % dictionary_font["Times-Bold"])
pdf_write("<<\n")
pdf_write("/Type /Font\n")
pdf_write("/Subtype /Type1\n")
pdf_write("/Name /Times-Bold\n")
pdf_write("/BaseFont /Times-Bold\n")
pdf_write("/Encoding /MacRomanEncoding\n")
pdf_write(">>\n")
pdf_write("endobj\n")

dictionary_font["Times-Italic"] = obj_allocate()
pdf_write("%d 0 obj\n" % dictionary_font["Times-Italic"])
pdf_write("<<\n")
pdf_write("/Type /Font\n")
pdf_write("/Subtype /Type1\n")
pdf_write("/Name /Times-Italic\n")
pdf_write("/BaseFont /Times-Italic\n")
pdf_write("/Encoding /MacRomanEncoding\n")
pdf_write(">>\n")
pdf_write("endobj\n")

dictionary_font["Helvetica"] = obj_allocate()
pdf_write("%d 0 obj\n" % dictionary_font["Helvetica"])
pdf_write("<<\n")
pdf_write("/Type /Font\n")
pdf_write("/Subtype /Type1\n")
pdf_write("/Name /Helvetica\n")
pdf_write("/BaseFont /Helvetica\n")
pdf_write("/Encoding /MacRomanEncoding\n")
pdf_write(">>\n")
pdf_write("endobj\n")

dictionary_font["Helvetica-Bold"] = obj_allocate()
pdf_write("%d 0 obj\n" % dictionary_font["Helvetica-Bold"])
pdf_write("<<\n")
pdf_write("/Type /Font\n")
pdf_write("/Subtype /Type1\n")
pdf_write("/Name /Helvetica-Bold\n")
pdf_write("/BaseFont /Helvetica-Bold\n")
pdf_write("/Encoding /MacRomanEncoding\n")
pdf_write(">>\n")
pdf_write("endobj\n")

# hello world string (page #1)
page_index += 1
content_index += 1
dictionary_content[page_index] = {}
dictionary_content[page_index][content_index] = obj_allocate()
pdf_write("%d 0 obj\n" % dictionary_content[page_index][content_index])
pdf_write("<< /Length %d 0 R >>\n" % (dictionary_content[page_index][content_index]+1))
pdf_write("stream\n")
first_byte = index_byte
pdf_write("BT\n")
pdf_write("%s rg\n" % color_table["black"])
pdf_write("/%s %d Tf\n" % ("Times-Roman", 12))
pdf_write("%d %d Td\n" % (100, PAGE_HEIGHT-100))
pdf_write("(%s) Tj\n" % escape_pdf("Hello World"))
pdf_write("ET\n")
last_byte = index_byte
pdf_write("endstream\n")
pdf_write("endobj\n")
pdf_write("%d 0 obj\n" % obj_allocate())
pdf_write("%d\n" % (last_byte-first_byte))
pdf_write("endobj\n")

# draw some lines & a rectangle (page #1)
content_index += 1
dictionary_content[page_index][content_index] = obj_allocate()
pdf_write("%d 0 obj\n" % dictionary_content[page_index][content_index])
pdf_write("<< /Length %d 0 R >>\n" % (dictionary_content[page_index][content_index]+1))
pdf_write("stream\n")
first_byte = index_byte
pdf_write("%s rg\n" % color_table["red"])
pdf_write("%s RG\n" % color_table["blue"])
pdf_write("1 j\n")
pdf_write("2 w\n")
pdf_write("%d %d m\n" % (200, PAGE_HEIGHT-200))
pdf_write("%d %d l\n" % (250, PAGE_HEIGHT-200))
pdf_write("S\n")
pdf_write("%d %d %d %d re\n" % (300, PAGE_HEIGHT-300, 25, 50))
pdf_write("B\n")
last_byte = index_byte
pdf_write("endstream\n")
pdf_write("endobj\n")
pdf_write("%d 0 obj\n" % obj_allocate())
pdf_write("%d\n" % (last_byte-first_byte))
pdf_write("endobj\n")

# hello world string (page #2)
page_index += 1
content_index += 1
dictionary_content[page_index] = {}
dictionary_content[page_index][content_index] = obj_allocate()
pdf_write("%d 0 obj\n" % dictionary_content[page_index][content_index])
pdf_write("<< /Length %d 0 R >>\n" % (dictionary_content[page_index][content_index]+1))
pdf_write("stream\n")
first_byte = index_byte
pdf_write("BT\n")
pdf_write("%s rg\n" % color_table["black"])
pdf_write("/%s %d Tf\n" % ("Times-Roman", 12))
pdf_write("%d %d Td\n" % ((PAGE_WIDTH - text_width("Center This Text", "Times-Roman", 12))/2, PAGE_HEIGHT-100))
pdf_write("(%s) Tj\n" % escape_pdf("Center This Text"))
pdf_write("ET\n")
last_byte = index_byte
pdf_write("endstream\n")
pdf_write("endobj\n")
pdf_write("%d 0 obj\n" % obj_allocate())
pdf_write("%d\n" % (last_byte-first_byte))
pdf_write("endobj\n")

# page objects
for key in dictionary_content.keys():
   dictionary_page[key] = obj_allocate()
   pdf_write("%d 0 obj\n" % dictionary_page[key])
   pdf_write("<<\n")
   pdf_write("/Type /Page\n")
   pdf_write("/Parent %d 0 R\n" % (dictionary_page[key] + len(dictionary_content) - key))
   pdf_write("/MediaBox [0 0 %d %d]\n" % (PAGE_WIDTH, PAGE_HEIGHT))
   pdf_write("/Contents [\n")
   for item in dictionary_content[key].values():
      pdf_write("%d 0 R\n" % item)
   pdf_write("]\n")
   pdf_write("/Resources\n")
   pdf_write("<<\n")
   pdf_write("/ProcSet %d 0 R\n" % dictionary_procset[0])
   pdf_write("/Font << \n")
   for key, val in dictionary_font.items():
      pdf_write("/%s %d 0 R\n" % (key, val))
   pdf_write(">>\n")
   pdf_write(">>\n")
   pdf_write(">>\n")
   pdf_write("endobj\n")

# pages object
dictionary_pages[0] = obj_allocate()
pdf_write("%d 0 obj\n" % dictionary_pages[0])
pdf_write("<<\n")
pdf_write("/Type /Pages\n")
pdf_write("/Parent null\n")
pdf_write("/Kids [\n")
for item in dictionary_page.values():
   pdf_write("%d 0 R\n" % item)
pdf_write("]\n")
pdf_write("/Count %d\n" % len(dictionary_page))
pdf_write(">>\n")
pdf_write("endobj\n")

# root object
dictionary_root[0] = obj_allocate()
pdf_write("%d 0 obj\n" % dictionary_root[0])
pdf_write("<<\n")
pdf_write("/Type /Catalog\n")
pdf_write("/Outlines %d 0 R\n" % dictionary_outlines[0])
pdf_write("/Pages %d 0 R\n" % dictionary_pages[0])
pdf_write(">>\n")
pdf_write("endobj\n")

# cross reference tables
index_xref = index_byte
pdf_write("xref\n")
pdf_write("0 %d\n" % (len(dictionary_object)+1))
pdf_write("0000000000 65535 f \n")
for item in dictionary_object.values():
   pdf_write("%s 00000 n \n" % str(item).zfill(10))

# trailer
pdf_write("trailer\n")
pdf_write("<<\n")
pdf_write("/Size " + str(len(dictionary_object)+1) + "\n")
pdf_write("/Root %d 0 R\n" % dictionary_root[0])
pdf_write("/Info %d 0 R\n" % dictionary_info[0])
pdf_write(">>\n")
pdf_write("startxref\n")
pdf_write("%d\n" % index_xref)
pdf_write("%%EOF\n")

Chris Rathman / ChrisRath@aol.com