PageRenderTime 49ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/PyX-0.11.1/pyx/pdfwriter.py

#
Python | 395 lines | 278 code | 72 blank | 45 comment | 48 complexity | de072c034616b60ccd4e5edf630bb70d MD5 | raw file
Possible License(s): AGPL-1.0
  1. # -*- encoding: utf-8 -*-
  2. #
  3. #
  4. # Copyright (C) 2005-2011 J??rg Lehmann <joergl@users.sourceforge.net>
  5. # Copyright (C) 2007 Michael Schindler <m-schindler@users.sourceforge.net>
  6. # Copyright (C) 2005-2011 Andr?Š Wobst <wobsta@users.sourceforge.net>
  7. #
  8. # This file is part of PyX (http://pyx.sourceforge.net/).
  9. #
  10. # PyX is free software; you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation; either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # PyX is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with PyX; if not, write to the Free Software
  22. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. import cStringIO, copy, warnings, time
  24. try:
  25. import zlib
  26. haszlib = 1
  27. except:
  28. haszlib = 0
  29. import bbox, config, style, unit, version, trafo
  30. class PDFregistry:
  31. def __init__(self):
  32. self.types = {}
  33. # we want to keep the order of the resources
  34. self.objects = []
  35. self.resources = {}
  36. self.procsets = {"PDF": 1}
  37. self.merged = None
  38. def add(self, object):
  39. """ register object, merging it with an already registered object of the same type and id """
  40. sameobjects = self.types.setdefault(object.type, {})
  41. if sameobjects.has_key(object.id):
  42. sameobjects[object.id].merge(object)
  43. else:
  44. self.objects.append(object)
  45. sameobjects[object.id] = object
  46. def getrefno(self, object):
  47. if self.merged:
  48. return self.merged.getrefno(object)
  49. else:
  50. return self.types[object.type][object.id].refno
  51. def mergeregistry(self, registry):
  52. for object in registry.objects:
  53. self.add(object)
  54. registry.merged = self
  55. def write(self, file, writer, catalog):
  56. # first we set all refnos
  57. refno = 1
  58. for object in self.objects:
  59. object.refno = refno
  60. refno += 1
  61. # second, all objects are written, keeping the positions in the output file
  62. fileposes = []
  63. for object in self.objects:
  64. fileposes.append(file.tell())
  65. file.write("%i 0 obj\n" % object.refno)
  66. object.write(file, writer, self)
  67. file.write("endobj\n")
  68. # xref
  69. xrefpos = file.tell()
  70. file.write("xref\n"
  71. "0 %d\n"
  72. "0000000000 65535 f \n" % refno)
  73. for filepos in fileposes:
  74. file.write("%010i 00000 n \n" % filepos)
  75. # trailer
  76. file.write("trailer\n"
  77. "<<\n"
  78. "/Size %i\n" % refno)
  79. file.write("/Root %i 0 R\n" % self.getrefno(catalog))
  80. file.write("/Info %i 0 R\n" % self.getrefno(catalog.PDFinfo))
  81. file.write(">>\n"
  82. "startxref\n"
  83. "%i\n" % xrefpos)
  84. file.write("%%EOF\n")
  85. def addresource(self, resourcetype, resourcename, object, procset=None):
  86. self.resources.setdefault(resourcetype, {})[resourcename] = object
  87. if procset:
  88. self.procsets[procset] = 1
  89. def writeresources(self, file):
  90. file.write("<<\n")
  91. file.write("/ProcSet [ %s ]\n" % " ".join(["/%s" % p for p in self.procsets.keys()]))
  92. if self.resources:
  93. for resourcetype, resources in self.resources.items():
  94. file.write("/%s <<\n%s\n>>\n" % (resourcetype, "\n".join(["/%s %i 0 R" % (name, self.getrefno(object))
  95. for name, object in resources.items()])))
  96. file.write(">>\n")
  97. class PDFobject:
  98. def __init__(self, type, _id=None):
  99. """create a PDFobject
  100. - type has to be a string describing the type of the object
  101. - _id is a unique identification used for the object if it is not None.
  102. Otherwise id(self) is used
  103. """
  104. self.type = type
  105. if _id is None:
  106. self.id = id(self)
  107. else:
  108. self.id = _id
  109. def merge(self, other):
  110. pass
  111. def write(self, file, writer, registry):
  112. raise NotImplementedError("write method has to be provided by PDFobject subclass")
  113. class PDFcatalog(PDFobject):
  114. def __init__(self, document, writer, registry):
  115. PDFobject.__init__(self, "catalog")
  116. self.PDFform = PDFform(writer, registry)
  117. registry.add(self.PDFform)
  118. self.PDFpages = PDFpages(document, writer, registry)
  119. registry.add(self.PDFpages)
  120. self.PDFinfo = PDFinfo()
  121. registry.add(self.PDFinfo)
  122. def write(self, file, writer, registry):
  123. file.write("<<\n"
  124. "/Type /Catalog\n"
  125. "/Pages %i 0 R\n" % registry.getrefno(self.PDFpages))
  126. if not self.PDFform.empty():
  127. file.write("/AcroForm %i 0 R\n" % registry.getrefno(self.PDFform))
  128. if writer.fullscreen:
  129. file.write("/PageMode /FullScreen\n")
  130. file.write(">>\n")
  131. class PDFinfo(PDFobject):
  132. def __init__(self):
  133. PDFobject.__init__(self, "info")
  134. def write(self, file, writer, registry):
  135. if time.timezone < 0:
  136. # divmod on positive numbers, otherwise the minutes have a different sign from the hours
  137. timezone = "-%02i'%02i'" % divmod(-time.timezone/60, 60)
  138. elif time.timezone > 0:
  139. timezone = "+%02i'%02i'" % divmod(time.timezone/60, 60)
  140. else:
  141. timezone = "Z00'00'"
  142. def pdfstring(s):
  143. r = ""
  144. for c in s:
  145. if 32 <= ord(c) <= 127 and c not in "()[]<>\\":
  146. r += c
  147. else:
  148. r += "\\%03o" % ord(c)
  149. return r
  150. file.write("<<\n")
  151. if writer.title:
  152. file.write("/Title (%s)\n" % pdfstring(writer.title))
  153. if writer.author:
  154. file.write("/Author (%s)\n" % pdfstring(writer.author))
  155. if writer.subject:
  156. file.write("/Subject (%s)\n" % pdfstring(writer.subject))
  157. if writer.keywords:
  158. file.write("/Keywords (%s)\n" % pdfstring(writer.keywords))
  159. file.write("/Creator (PyX %s)\n" % version.version)
  160. file.write("/CreationDate (D:%s%s)\n" % (time.strftime("%Y%m%d%H%M"), timezone))
  161. file.write(">>\n")
  162. class PDFpages(PDFobject):
  163. def __init__(self, document, writer, registry):
  164. PDFobject.__init__(self, "pages")
  165. self.PDFpagelist = []
  166. for pageno, page in enumerate(document.pages):
  167. page = PDFpage(page, pageno, self, writer, registry)
  168. registry.add(page)
  169. self.PDFpagelist.append(page)
  170. def write(self, file, writer, registry):
  171. file.write("<<\n"
  172. "/Type /Pages\n"
  173. "/Kids [%s]\n"
  174. "/Count %i\n"
  175. ">>\n" % (" ".join(["%i 0 R" % registry.getrefno(page)
  176. for page in self.PDFpagelist]),
  177. len(self.PDFpagelist)))
  178. class PDFpage(PDFobject):
  179. def __init__(self, page, pageno, PDFpages, writer, registry):
  180. PDFobject.__init__(self, "page")
  181. self.PDFpages = PDFpages
  182. self.page = page
  183. # every page uses its own registry in order to find out which
  184. # resources are used within the page. However, the
  185. # pageregistry is also merged in the global registry
  186. self.pageregistry = PDFregistry()
  187. self.pageregistry.add(self)
  188. self.PDFannotations = PDFannotations()
  189. self.pageregistry.add(self.PDFannotations)
  190. # we eventually need the form dictionary to append formfields
  191. for object in registry.objects:
  192. if object.type == "form":
  193. self.pageregistry.add(object)
  194. self.PDFcontent = PDFcontent(page, writer, self.pageregistry)
  195. self.pageregistry.add(self.PDFcontent)
  196. registry.mergeregistry(self.pageregistry)
  197. def write(self, file, writer, registry):
  198. file.write("<<\n"
  199. "/Type /Page\n"
  200. "/Parent %i 0 R\n" % registry.getrefno(self.PDFpages))
  201. paperformat = self.page.paperformat
  202. if paperformat:
  203. file.write("/MediaBox [0 0 %f %f]\n" % (unit.topt(paperformat.width), unit.topt(paperformat.height)))
  204. else:
  205. file.write("/MediaBox [%f %f %f %f]\n" % self.PDFcontent.bbox.highrestuple_pt())
  206. if self.PDFcontent.bbox and writer.writebbox:
  207. file.write("/CropBox [%f %f %f %f]\n" % self.PDFcontent.bbox.highrestuple_pt())
  208. if self.page.rotated:
  209. file.write("/Rotate 90\n")
  210. if not self.PDFannotations.empty():
  211. file.write("/Annots %i 0 R\n" % registry.getrefno(self.PDFannotations))
  212. file.write("/Contents %i 0 R\n" % registry.getrefno(self.PDFcontent))
  213. file.write("/Resources ")
  214. self.pageregistry.writeresources(file)
  215. file.write(">>\n")
  216. class PDFcontent(PDFobject):
  217. def __init__(self, page, writer, registry):
  218. PDFobject.__init__(self, registry, "content")
  219. contentfile = cStringIO.StringIO()
  220. self.bbox = bbox.empty()
  221. acontext = context()
  222. page.processPDF(contentfile, writer, acontext, registry, self.bbox)
  223. self.content = contentfile.getvalue()
  224. contentfile.close()
  225. def write(self, file, writer, registry):
  226. if writer.compress:
  227. content = zlib.compress(self.content)
  228. else:
  229. content = self.content
  230. file.write("<<\n"
  231. "/Length %i\n" % len(content))
  232. if writer.compress:
  233. file.write("/Filter /FlateDecode\n")
  234. file.write(">>\n"
  235. "stream\n")
  236. file.write(content)
  237. file.write("endstream\n")
  238. class PDFwriter:
  239. def __init__(self, document, file,
  240. title=None, author=None, subject=None, keywords=None,
  241. fullscreen=False, writebbox=False, compress=True, compresslevel=6,
  242. strip_fonts=True, text_as_path=False, mesh_as_bitmap=False, mesh_as_bitmap_resolution=300):
  243. self._fontmap = None
  244. self.title = title
  245. self.author = author
  246. self.subject = subject
  247. self.keywords = keywords
  248. self.fullscreen = fullscreen
  249. self.writebbox = writebbox
  250. if compress and not haszlib:
  251. compress = 0
  252. warnings.warn("compression disabled due to missing zlib module")
  253. self.compress = compress
  254. self.compresslevel = compresslevel
  255. self.strip_fonts = strip_fonts
  256. self.text_as_path = text_as_path
  257. self.mesh_as_bitmap = mesh_as_bitmap
  258. self.mesh_as_bitmap_resolution = mesh_as_bitmap_resolution
  259. # dictionary mapping font names to dictionaries mapping encoding names to encodings
  260. # encodings themselves are mappings from glyphnames to codepoints
  261. self.encodings = {}
  262. # the PDFcatalog class automatically builds up the pdfobjects from a document
  263. registry = PDFregistry()
  264. catalog = PDFcatalog(document, self, registry)
  265. registry.add(catalog)
  266. file.write("%%PDF-1.4\n%%%s%s%s%s\n" % (chr(195), chr(182), chr(195), chr(169)))
  267. registry.write(file, self, catalog)
  268. file.close()
  269. def getfontmap(self):
  270. if self._fontmap is None:
  271. # late import due to cyclic dependency
  272. from pyx.dvi import mapfile
  273. fontmapfiles = config.getlist("text", "pdffontmaps", ["pdftex.map"])
  274. self._fontmap = mapfile.readfontmap(fontmapfiles)
  275. return self._fontmap
  276. class PDFannotations(PDFobject):
  277. def __init__(self):
  278. PDFobject.__init__(self, "annotations")
  279. self.annots = []
  280. def append(self, item):
  281. if item not in self.annots:
  282. self.annots.append(item)
  283. def empty(self):
  284. return len(self.annots) == 0
  285. def write(self, file, writer, registry):
  286. # XXX problem: This object will be written to the file even if it is useless (empty)
  287. file.write("[ %s ]\n" % " ".join(["%d 0 R" % registry.getrefno(annot) for annot in self.annots]))
  288. class PDFform(PDFobject):
  289. def __init__(self, writer, registry):
  290. PDFobject.__init__(self, "form")
  291. self.fields = []
  292. def merge(self, other):
  293. for field in other.fields:
  294. self.append(field)
  295. def append(self, field):
  296. if field not in self.fields:
  297. self.fields.append(field)
  298. def empty(self):
  299. return len(self.fields) == 0
  300. def write(self, file, writer, registry):
  301. # XXX problem: This object will be written to the file even if it is useless (empty)
  302. file.write("<<")
  303. file.write("/Fields [")
  304. for field in self.fields:
  305. file.write(" %d 0 R" % registry.getrefno(field))
  306. file.write(" ]\n")
  307. file.write(">>\n")
  308. class context:
  309. def __init__(self):
  310. self.linewidth_pt = None
  311. # XXX there are both stroke and fill color spaces
  312. self.colorspace = None
  313. self.strokeattr = 1
  314. self.fillattr = 1
  315. self.selectedfont = None
  316. self.textregion = 0
  317. self.trafo = trafo.trafo()
  318. self.fillstyles = []
  319. def __call__(self, **kwargs):
  320. newcontext = copy.copy(self)
  321. for key, value in kwargs.items():
  322. setattr(newcontext, key, value)
  323. return newcontext