2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2005 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2005 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
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.
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.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 import pykpathsea
, unit
, resource
, style
26 from t1strip
import fullfont
38 # we need to keep the original order of the resources (for PDFcontentlength)
41 def add(self
, resource
):
42 """ register resource, merging it with an already registered resource of the same type and id"""
43 resources
= self
.types
.setdefault(resource
.type, {})
44 if resources
.has_key(resource
.id):
45 resources
[resource
.id].merge(resource
)
47 self
.resources
.append(resource
)
48 resources
[resource
.id] = resource
50 def getrefno(self
, resource
):
51 return self
.types
[resource
.type][resource
.id].refno
53 def mergeregistry(self
, registry
):
54 for resource
in registry
.resources
:
57 def write(self
, file, writer
, catalog
):
58 # first we set all refnos
61 # we recursively inserted the resources such that the topmost resources in
62 # the dependency tree of the resources come last. Hence, we need to
63 # reverse the resources list before writing the output
64 self
.resources
.reverse()
65 for resource
in self
.resources
:
66 resource
.refno
= refno
69 # second, all objects are written, keeping the positions in the output file
71 for resource
in self
.resources
:
72 fileposes
.append(file.tell())
73 file.write("%i 0 obj\n" % resource
.refno
)
74 resource
.outputPDF(file, writer
, self
)
75 file.write("endobj\n")
81 "0000000000 65535 f \n" % refno
)
83 for filepos
in fileposes
:
84 file.write("%010i 00000 n \n" % filepos
)
87 file.write("trailer\n"
94 "%%%%EOF\n" % (refno
, catalog
.refno
, xrefpos
))
99 def __init__(self
, type, _id
=None):
107 def merge(self
, other
):
110 def outputPDF(self
, file, writer
, registry
):
111 raise NotImplementedError("outputPDF method has to be provided by PDFobject subclass")
114 class PDFcatalog(PDFobject
):
116 def __init__(self
, document
, registry
):
117 PDFobject
.__init
__(self
, "catalog")
118 self
.PDFpages
= PDFpages(document
, registry
)
119 registry
.add(self
.PDFpages
)
121 def outputPDF(self
, file, writer
, registry
):
125 ">>\n" % registry
.getrefno(self
.PDFpages
))
128 class PDFpages(PDFobject
):
130 def __init__(self
, document
, registry
):
131 PDFobject
.__init
__(self
, "pages")
132 self
.PDFpagelist
= []
133 for pageno
, page
in enumerate(document
.pages
):
134 page
= PDFpage(page
, pageno
, self
, registry
)
136 self
.PDFpagelist
.append(page
)
138 def outputPDF(self
, file, writer
, registry
):
143 ">>\n" % (" ".join(["%i 0 R" % registry
.getrefno(page
)
144 for page
in self
.PDFpagelist
]),
145 len(self
.PDFpagelist
)))
148 class PDFpage(PDFobject
):
150 def __init__(self
, page
, pageno
, PDFpages
, registry
):
151 PDFobject
.__init
__(self
, "page", pageno
)
152 self
.PDFpages
= PDFpages
155 # every page uses its own registry in order to find out which
156 # resources are used within the page. However, the
157 # pageregistry is also merged in the global registry
158 self
.pageregistry
= PDFregistry()
160 self
.bbox
= page
.canvas
.bbox()
161 self
.bbox
.enlarge(page
.bboxenlarge
)
162 self
.pagetrafo
= page
.pagetrafo(self
.bbox
)
164 self
.bbox
.transform(self
.pagetrafo
)
165 self
.PDFcontent
= PDFcontent(page
.canvas
, self
.pagetrafo
, self
.pageregistry
)
166 self
.pageregistry
.add(self
.PDFcontent
)
167 self
.page
.canvas
.registerPDF(self
.pageregistry
)
168 registry
.mergeregistry(self
.pageregistry
)
170 def outputPDF(self
, file, writer
, registry
):
173 "/Parent %i 0 R\n" % registry
.getrefno(self
.PDFpages
))
174 paperformat
= self
.page
.paperformat
175 file.write("/MediaBox [0 0 %d %d]\n" % (unit
.topt(paperformat
.width
), unit
.topt(paperformat
.height
)))
176 file.write("/CropBox " )
177 self
.bbox
.outputPDF(file, writer
)
178 if self
.pageregistry
.types
.has_key("font"):
179 file.write("/Resources << /ProcSet [ /PDF /Text ]\n")
180 file.write("/Font << %s >>" % " ".join(["/%s %i 0 R" % (font
.font
.getpsname(), registry
.getrefno(font
))
181 for font
in self
.pageregistry
.types
["font"].values()]))
183 file.write("/Resources << /ProcSet [ /PDF ]\n")
186 file.write("/Contents %i 0 R\n"
187 ">>\n" % registry
.getrefno(self
.PDFcontent
))
190 class _compressstream
:
192 def __init__(self
, file, compresslevel
):
194 self
.compressobj
= zlib
.compressobj(compresslevel
)
196 def write(self
, string
):
197 self
.file.write(self
.compressobj
.compress(string
))
200 self
.file.write(self
.compressobj
.flush())
203 class PDFcontent(PDFobject
):
205 def __init__(self
, canvas
, pagetrafo
, registry
):
206 PDFobject
.__init
__(self
, "content")
208 self
.pagetrafo
= pagetrafo
209 self
.PDFcontentlength
= PDFcontentlength()
210 registry
.add(self
.PDFcontentlength
)
212 def outputPDF(self
, file, writer
, registry
):
214 "/Length %i 0 R\n" % registry
.getrefno(self
.PDFcontentlength
))
216 file.write("/Filter /FlateDecode\n")
218 file.write("stream\n")
219 beginstreampos
= file.tell()
222 stream
= _compressstream(file, writer
.compresslevel
)
227 # apply a possible global transformation
229 self
.pagetrafo
.outputPDF(stream
, writer
, acontext
)
230 style
.linewidth
.normal
.outputPDF(stream
, writer
, acontext
)
232 self
.canvas
.outputPDF(stream
, writer
, acontext
)
236 self
.PDFcontentlength
.contentlength
= file.tell() - beginstreampos
237 file.write("\nendstream\n")
240 class PDFcontentlength(PDFobject
):
243 PDFobject
.__init
__(self
, "_contentlength")
245 def outputPDF(self
, file, writer
, registry
):
246 # initially we do not know about the content length
247 # -> it has to be written into the instance later on
248 file.write("%d\n" % self
.contentlength
)
251 class PDFfont(PDFobject
):
253 def __init__(self
, font
, registry
):
254 PDFobject
.__init
__(self
, "font", font
.getpsname())
256 self
.fontwidths
= PDFfontwidths(self
.font
)
257 registry
.add(self
.fontwidths
)
258 self
.fontdescriptor
= PDFfontdescriptor(self
.font
, registry
)
259 registry
.add(self
.fontdescriptor
)
261 def outputPDF(self
, file, writer
, registry
):
270 "/FontDescriptor %d 0 R\n"
271 "/Encoding /StandardEncoding\n" # FIXME
272 ">>\n" % (self
.font
.getpsname(), self
.font
.getbasepsname(),
273 registry
.getrefno(self
.fontwidths
),
274 registry
.getrefno(self
.fontdescriptor
)))
276 class PDFfontwidths(PDFobject
):
278 def __init__(self
, font
):
279 PDFobject
.__init
__(self
, "fontwidths")
282 def outputPDF(self
, file, writer
, registry
):
286 width
= self
.font
.getwidth_pt(i
)*1000/self
.font
.getsize_pt()
289 file.write("%f\n" % width
)
293 class PDFfontdescriptor(PDFobject
):
295 def __init__(self
, font
, registry
):
296 PDFobject
.__init
__(self
, "fontdescriptor")
298 self
.fontfile
= PDFfontfile(self
.font
)
299 registry
.add(self
.fontfile
)
301 def outputPDF(self
, file, writer
, registry
):
303 "/Type /FontDescriptor\n"
306 "/FontBBox [-10 -10 1000 1000]\n" # FIXME
307 "/ItalicAngle 0\n" # FIXME
308 "/Ascent 20\n" # FIXME
309 "/Descent -5\n" # FIXME
310 "/CapHeight 15\n" # FIXME
312 "/FontFile %d 0 R\n" # FIXME
313 # "/CharSet \n" # fill in when stripping
314 ">>\n" % (self
.font
.getbasepsname(), registry
.getrefno(self
.fontfile
)))
316 class PDFfontfile(PDFobject
):
318 def __init__(self
, font
):
319 PDFobject
.__init
__(self
, "fontfile", font
.getfontfile())
322 def outputPDF(self
, file, writer
, registry
):
323 fontfile
= open(self
.font
.getfontfile())
324 fontdata
= fontfile
.read()
326 if fontdata
[0:2] != fullfont
._PFB
_ASCII
:
327 raise RuntimeError("PFB_ASCII mark expected")
328 length1
= fullfont
.pfblength(fontdata
[2:6])
329 if fontdata
[6+length1
:8+length1
] != fullfont
._PFB
_BIN
:
330 raise RuntimeError("PFB_BIN mark expected")
331 length2
= fullfont
.pfblength(fontdata
[8+length1
:12+length1
])
332 if fontdata
[12+length1
+length2
:14+length1
+length2
] != fullfont
._PFB
_ASCII
:
333 raise RuntimeError("PFB_ASCII mark expected")
334 length3
= fullfont
.pfblength(fontdata
[14+length1
+length2
:18+length1
+length2
])
335 if fontdata
[18+length1
+length2
+length3
:20+length1
+length2
+length3
] != fullfont
._PFB
_DONE
:
336 raise RuntimeError("PFB_DONE mark expected")
337 if len(fontdata
) != 20 + length1
+ length2
+ length3
:
338 raise RuntimeError("end of pfb file expected")
340 # we might be allowed to skip the third part ...
341 if fontdata
[18+length1
+length2
:18+length1
+length2
+length3
].replace("\n", "").replace("\r", "").replace("\t", "").replace(" ", "") == "0"*512 + "cleartomark":
345 data
= fontdata
[6:6+length1
] + fontdata
[12+length1
:12+length1
+length2
] + fontdata
[18+length1
+length2
:18+length1
+length2
+length3
]
347 data
= fontdata
[6:6+length1
] + fontdata
[12+length1
:12+length1
+length2
]
349 data
= zlib
.compress(data
)
355 "/Length3 %d\n" % (len(data
), length1
, length2
, length3
))
357 file.write("/Filter /FlateDecode\n")
361 file.write("\nendstream\n")
366 def __init__(self
, document
, filename
, compress
=0, compresslevel
=6):
367 warnings
.warn("writePDFfile is experimental and supports only a subset of PyX's features")
369 if filename
[-4:] != ".pdf":
370 filename
= filename
+ ".pdf"
372 file = open(filename
, "wb")
374 raise IOError("cannot open output file")
376 if compress
and not haszlib
:
378 warnings
.warn("compression disabled due to missing zlib module")
379 self
.compress
= compress
380 self
.compresslevel
= compresslevel
382 file.write("%%PDF-1.4\n%%%s%s%s%s\n" % (chr(195), chr(182), chr(195), chr(169)))
384 # the PDFcatalog class automatically builds up the pdfobjects from a document
385 registry
= PDFregistry()
386 catalog
= PDFcatalog(document
, registry
)
387 registry
.add(catalog
)
388 registry
.write(file, self
, catalog
)
395 self
.linewidth_pt
= None
396 self
.colorspace
= None
400 def __call__(self
, **kwargs
):
401 newcontext
= copy
.copy(self
)
402 for key
, value
in kwargs
.items():
403 setattr(newcontext
, key
, value
)