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
39 self
.merged
= {} # contains merged resources, where refno's need to be injected
40 # (this is necessary, since some components, which depend on a
41 # given resource might want to lookup the refno, so it needs to
42 # be available even for objects, which have been removed due to
45 def add(self
, resource
):
46 resources
= self
.types
.setdefault(resource
.type, {})
47 if resources
.has_key(resource
.id):
48 resources
[resource
.id].merge(resource
)
49 self
.merged
.setdefault(resource
.type, []).append(resource
)
51 self
.resources
.append(resource
)
52 resources
[resource
.id] = resource
54 def merge(self
, registry
):
55 for resource
in registry
.resources
:
58 def setrefno(self
, refno
):
59 for resource
in self
.resources
:
60 refno
= resource
.setrefno(refno
)
61 for type, resources
in self
.merged
.items():
62 for resource
in resources
:
63 resource
.refno
= self
.types
[type][resource
.id].refno
66 def outputPDFobjects(self
, file, writer
, context
):
67 for resource
in self
.resources
:
68 resource
.outputPDFobjects(file, writer
, context
)
70 def outputPDFxref(self
, file):
71 for resource
in self
.resources
:
72 resource
.outputPDFxref(file)
77 def __init__(self
, childs
=[]):
80 def merge(self
, other
):
83 def setrefno(self
, refno
):
86 for child
in self
.childs
:
87 refno
= child
.setrefno(refno
)
90 def outputPDFobjects(self
, file, writer
, context
):
91 self
.outputPDFobject(file, writer
, context
)
92 for child
in self
.childs
:
93 child
.outputPDFobjects(file, writer
, context
)
95 def outputPDFobject(self
, file, writer
, context
):
96 self
.filepos
= file.tell()
97 file.write("%i 0 obj\n" % self
.refno
)
98 self
.outputPDF(file, writer
, context
)
99 file.write("endobj\n")
101 def outputPDF(self
, file, writer
, context
):
102 raise NotImplementedError("outputPDF method has to be provided by PDFobject subclass")
104 def outputPDFxref(self
, file):
105 file.write("%010i 00000 n \n" % self
.filepos
)
106 for child
in self
.childs
:
107 child
.outputPDFxref(file)
110 class PDFcatalog(PDFobject
):
112 def __init__(self
, document
):
113 self
.PDFpages
= PDFpages(document
)
114 self
.registry
= PDFregistry()
115 for page
in self
.PDFpages
.PDFpagelist
:
116 self
.registry
.merge(page
.registry
)
117 PDFobject
.__init
__(self
, [self
.PDFpages
])
119 def setrefno(self
, refno
):
120 refno
= PDFobject
.setrefno(self
, refno
)
121 return self
.registry
.setrefno(refno
)
123 def outputPDFobjects(self
, file, writer
, context
):
124 PDFobject
.outputPDFobjects(self
, file, writer
, context
)
125 self
.registry
.outputPDFobjects(file, writer
, context
)
127 def outputPDF(self
, file, writer
, context
):
131 ">>\n" % self
.PDFpages
.refno
)
133 def outputPDFxref(self
, file):
134 PDFobject
.outputPDFxref(self
, file)
135 self
.registry
.outputPDFxref(file)
138 class PDFpages(PDFobject
):
140 def __init__(self
, document
):
141 self
.PDFpagelist
= []
142 for page
in document
.pages
:
143 self
.PDFpagelist
.append(PDFpage(page
, self
))
144 PDFobject
.__init
__(self
, self
.PDFpagelist
)
146 def outputPDF(self
, file, writer
, context
):
151 ">>\n" % (" ".join(["%i 0 R" % page
.refno
152 for page
in self
.PDFpagelist
]),
153 len(self
.PDFpagelist
)))
156 class PDFpage(PDFobject
):
158 def __init__(self
, page
, PDFpages
):
159 self
.PDFpages
= PDFpages
161 self
.bbox
= page
.canvas
.bbox()
162 self
.bbox
.enlarge(page
.bboxenlarge
)
163 self
.pagetrafo
= page
.pagetrafo(self
.bbox
)
165 self
.bbox
.transform(self
.pagetrafo
)
166 self
.PDFcontent
= PDFcontent(page
.canvas
, self
.pagetrafo
)
167 self
.registry
= PDFregistry()
168 for resource
in page
.canvas
.resources():
169 resource
.PDFregister(self
.registry
)
170 PDFobject
.__init
__(self
, [self
.PDFcontent
])
172 def outputPDF(self
, file, writer
, context
):
175 "/Parent %i 0 R\n" % self
.PDFpages
.refno
)
176 paperformat
= self
.page
.paperformat
177 file.write("/MediaBox [0 0 %d %d]\n" % (unit
.topt(paperformat
.width
), unit
.topt(paperformat
.height
)))
178 file.write("/CropBox " )
179 self
.bbox
.outputPDF(file, writer
, context
)
180 if self
.registry
.types
["font"]:
181 file.write("/Resources << /ProcSet [ /PDF /Text ]\n")
183 file.write("/Resources << /ProcSet [ /PDF ]\n")
184 if self
.registry
.types
.has_key("font"):
185 file.write("/Font << %s >>" % " ".join(["/%s %i 0 R" % (font
.fontname
, font
.refno
)
186 for font
in self
.registry
.types
["font"].values()]))
188 file.write("/Contents %i 0 R\n"
189 ">>\n" % (self
.PDFcontent
.refno
))
192 class _compressstream
:
194 def __init__(self
, file, compresslevel
):
196 self
.compressobj
= zlib
.compressobj(writer
.compresslevel
)
198 def write(self
, string
):
199 self
.file.write(self
.compressobj
.compress(string
))
202 self
.file.write(self
.compressobj
.flush())
205 class PDFcontent(PDFobject
):
207 def __init__(self
, canvas
, pagetrafo
):
209 self
.pagetrafo
= pagetrafo
210 self
.PDFcontentlength
= PDFcontentlength()
211 PDFobject
.__init
__(self
, [self
.PDFcontentlength
])
213 def outputPDF(self
, file, writer
, context
):
215 "/Length %i 0 R\n" % (self
.PDFcontentlength
.refno
))
217 file.write("/Filter /FlateDecode\n")
219 file.write("stream\n")
220 beginstreampos
= file.tell()
223 stream
= _compressstream(file, writer
.compresslevel
)
227 # apply a possible global transformation
229 self
.pagetrafo
.outputPDF(stream
, writer
, context
)
230 style
.linewidth
.normal
.outputPDF(stream
, writer
, context
)
232 self
.canvas
.outputPDF(stream
, writer
, context
)
236 self
.PDFcontentlength
.contentlength
= file.tell() - beginstreampos
237 file.write("endstream\n")
240 class PDFcontentlength(PDFobject
):
242 def outputPDF(self
, file, writer
, context
):
243 # initially we do not know about the content length
244 # -> it has to be written into the instance later on
245 file.write("%d\n" % self
.contentlength
)
248 class PDFfont(PDFobject
):
250 def __init__(self
, fontname
, basepsname
, font
):
252 self
.id = self
.fontname
= fontname
253 self
.basepsname
= basepsname
254 self
.fontwidths
= PDFfontwidths(font
)
255 self
.fontdescriptor
= PDFfontdescriptor(font
)
256 PDFobject
.__init
__(self
, [self
.fontwidths
, self
.fontdescriptor
])
258 def register(self
, registry
):
259 registry
.addresource(registry
.fonts
, self
)
261 def outputPDF(self
, file, writer
, context
):
270 "/FontDescriptor %d 0 R\n"
271 "/Encoding /StandardEncoding\n" # FIXME
272 ">>\n" % (self
.fontname
, self
.basepsname
,
273 self
.fontwidths
.refno
, self
.fontdescriptor
.refno
))
275 class PDFfontwidths(PDFobject
):
277 def __init__(self
, font
):
278 self
.type = "fontwidth"
280 PDFobject
.__init
__(self
)
282 def register(self
, registry
):
283 registry
.addresource(registry
.fontwidths
, self
)
285 def outputPDF(self
, file, writer
, context
):
289 width
= self
.font
.getwidth_pt(i
)*1000/self
.font
.getsize_pt()
292 file.write("%f\n" % width
)
296 class PDFfontdescriptor(PDFobject
):
298 def __init__(self
, font
):
299 self
.type = "fontdescriptor"
301 path
= pykpathsea
.find_file(font
.getfontfile(), pykpathsea
.kpse_type1_format
)
302 self
.fontfile
= PDFfontfile(path
)
303 PDFobject
.__init
__(self
, [self
.fontfile
])
305 def register(self
, registry
):
306 registry
.addresource(registry
.fontdescriptors
, self
)
308 def arrange(self
, refno
):
309 return PDFobject
.arrangeselfandchilds(self
, refno
, self
.fontfile
)
311 def outputPDF(self
, file, writer
, context
):
313 "/Type /FontDescriptor\n"
316 "/FontBBox [-10 -10 1000 1000]\n" # FIXME
317 "/ItalicAngle 0\n" # FIXME
318 "/Ascent 20\n" # FIXME
319 "/Descent -5\n" # FIXME
320 "/CapHeight 15\n" # FIXME
322 "/FontFile %d 0 R\n" # FIXME
323 # "/CharSet \n" # fill in when stripping
324 ">>\n" % (self
.font
.getbasepsname(), self
.fontfile
.refno
))
326 class PDFfontfile(PDFobject
):
328 def __init__(self
, path
):
329 self
.type = "fontfile"
331 PDFobject
.__init
__(self
)
333 def register(self
, registry
):
334 registry
.addresource(registry
.fontfiles
, self
)
336 def outputPDF(self
, file, writer
, context
):
337 fontfile
= open(self
.path
)
338 fontdata
= fontfile
.read()
340 if fontdata
[0:2] != fullfont
._PFB
_ASCII
:
341 raise RuntimeError("PFB_ASCII mark expected")
342 length1
= fullfont
.pfblength(fontdata
[2:6])
343 if fontdata
[6+length1
:8+length1
] != fullfont
._PFB
_BIN
:
344 raise RuntimeError("PFB_BIN mark expected")
345 length2
= fullfont
.pfblength(fontdata
[8+length1
:12+length1
])
346 if fontdata
[12+length1
+length2
:14+length1
+length2
] != fullfont
._PFB
_ASCII
:
347 raise RuntimeError("PFB_ASCII mark expected")
348 length3
= fullfont
.pfblength(fontdata
[14+length1
+length2
:18+length1
+length2
])
349 if fontdata
[18+length1
+length2
+length3
:20+length1
+length2
+length3
] != fullfont
._PFB
_DONE
:
350 raise RuntimeError("PFB_DONE mark expected")
351 if len(fontdata
) != 20 + length1
+ length2
+ length3
:
352 raise RuntimeError("end of pfb file expected")
354 # we might be allowed to skip the third part ...
355 if fontdata
[18+length1
+length2
:18+length1
+length2
+length3
].replace("\n", "").replace("\r", "").replace("\t", "").replace(" ", "") == "0"*512 + "cleartomark":
359 data
= fontdata
[6:6+length1
] + fontdata
[12+length1
:12+length1
+length2
] + fontdata
[18+length1
+length2
:18+length1
+length2
+length3
]
361 data
= fontdata
[6:6+length1
] + fontdata
[12+length1
:12+length1
+length2
]
363 data
= zlib
.compress(data
)
369 "/Length3 %d\n" % (len(data
), length1
, length2
, length3
))
371 file.write("/Filter /FlateDecode\n")
375 file.write("endstream\n")
380 def __init__(self
, document
, filename
, compress
=1, compresslevel
=6):
381 warnings
.warn("writePDFfile is experimental and supports only a subset of PyX's features")
383 if filename
[-4:] != ".pdf":
384 filename
= filename
+ ".pdf"
386 self
.file = open(filename
, "wb")
388 raise IOError("cannot open output file")
390 if compress
and not haszlib
:
392 warnings
.warn("compression disabled due to missing zlib module")
393 self
.compress
= compress
394 self
.compresslevel
= compresslevel
396 self
.file.write("%%PDF-1.4\n%%%s%s%s%s\n" % (chr(195), chr(182), chr(195), chr(169)))
398 # the PDFcatalog class automatically builds up the pdfobjects from a document
399 catalog
= PDFcatalog(document
)
400 pdfobjects
= catalog
.setrefno(1)
403 catalog
.outputPDFobjects(self
.file, self
, context())
406 xrefpos
= self
.file.tell()
407 self
.file.write("xref\n"
409 "0000000000 65535 f \n" % pdfobjects
)
410 catalog
.outputPDFxref(self
.file)
413 self
.file.write("trailer\n"
420 "%%%%EOF\n" % (pdfobjects
, catalog
.refno
, xrefpos
))
427 self
.linewidth_pt
= None
428 self
.colorspace
= None
432 def __call__(self
, **kwargs
):
433 newcontext
= copy
.copy(self
)
434 for key
, value
in kwargs
.items():
435 setattr(newcontext
, key
, value
)