1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2005-2006 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2005-2006 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 import cStringIO
, copy
, warnings
, time
30 import bbox
, unit
, style
, type1font
, version
35 # fallback implementation for Python 2.2 and below
37 return zip(xrange(len(list)), list)
42 # fallback implementation for Python 2.1
45 for key
, value
in list:
54 # we want to keep the order of the resources
57 self
.procsets
= {"PDF": 1}
60 def add(self
, object):
61 """ register object, merging it with an already registered object of the same type and id """
62 sameobjects
= self
.types
.setdefault(object.type, {})
63 if sameobjects
.has_key(object.id):
64 sameobjects
[object.id].merge(object)
66 self
.objects
.append(object)
67 sameobjects
[object.id] = object
69 def getrefno(self
, object):
71 return self
.merged
.getrefno(object)
73 return self
.types
[object.type][object.id].refno
75 def mergeregistry(self
, registry
):
76 for object in registry
.objects
:
78 registry
.merged
= self
80 def write(self
, file, writer
, catalog
):
81 # first we set all refnos
83 for object in self
.objects
:
87 # second, all objects are written, keeping the positions in the output file
89 for object in self
.objects
:
90 fileposes
.append(file.tell())
91 file.write("%i 0 obj\n" % object.refno
)
92 object.write(file, writer
, self
)
93 file.write("endobj\n")
99 "0000000000 65535 f \n" % refno
)
101 for filepos
in fileposes
:
102 file.write("%010i 00000 n \n" % filepos
)
105 file.write("trailer\n"
107 "/Size %i\n" % refno
)
108 file.write("/Root %i 0 R\n" % self
.getrefno(catalog
))
109 file.write("/Info %i 0 R\n" % self
.getrefno(catalog
.PDFinfo
))
113 file.write("%%EOF\n")
115 def addresource(self
, resourcetype
, resourcename
, object, procset
=None):
116 self
.resources
.setdefault(resourcetype
, {})[resourcename
] = object
118 self
.procsets
[procset
] = 1
120 def writeresources(self
, file):
121 file.write("/Resources <<\n")
122 file.write("/ProcSet [ %s ]\n" % " ".join(["/%s" % p
for p
in self
.procsets
.keys()]))
124 for resourcetype
, resources
in self
.resources
.items():
125 file.write("/%s <<\n%s\n>>\n" % (resourcetype
, "\n".join(["/%s %i 0 R" % (name
, self
.getrefno(object))
126 for name
, object in resources
.items()])))
132 def __init__(self
, type, _id
=None):
133 """create a PDFobject
134 - type has to be a string describing the type of the object
135 - _id is a unique identification used for the object if it is not None.
136 Otherwise id(self) is used
144 def merge(self
, other
):
147 def write(self
, file, writer
, registry
):
148 raise NotImplementedError("write method has to be provided by PDFobject subclass")
151 class PDFcatalog(PDFobject
):
153 def __init__(self
, document
, writer
, registry
):
154 PDFobject
.__init
__(self
, "catalog")
155 self
.PDFpages
= PDFpages(document
, writer
, registry
)
156 registry
.add(self
.PDFpages
)
157 self
.PDFinfo
= PDFinfo()
158 registry
.add(self
.PDFinfo
)
160 def write(self
, file, writer
, registry
):
163 "/Pages %i 0 R\n" % registry
.getrefno(self
.PDFpages
))
164 if writer
.fullscreen
:
165 file.write("/PageMode /FullScreen\n")
169 class PDFinfo(PDFobject
):
172 PDFobject
.__init
__(self
, "info")
174 def write(self
, file, writer
, registry
):
175 if time
.timezone
< 0:
176 # divmod on positive numbers, otherwise the minutes have a different sign from the hours
177 timezone
= "-%02i'%02i'" % divmod(-time
.timezone
/60, 60)
178 elif time
.timezone
> 0:
179 timezone
= "+%02i'%02i'" % divmod(time
.timezone
/60, 60)
186 if 32 <= ord(c
) <= 127 and c
not in "()[]<>\\":
189 r
+= "\\%03o" % ord(c
)
194 file.write("/Title (%s)\n" % pdfstring(writer
.title
))
196 file.write("/Author (%s)\n" % pdfstring(writer
.author
))
198 file.write("/Subject (%s)\n" % pdfstring(writer
.subject
))
200 file.write("/Keywords (%s)\n" % pdfstring(writer
.keywords
))
201 file.write("/Creator (PyX %s)\n" % version
.version
)
202 file.write("/CreationDate (D:%s%s)\n" % (time
.strftime("%Y%m%d%H%M"), timezone
))
206 class PDFpages(PDFobject
):
208 def __init__(self
, document
, writer
, registry
):
209 PDFobject
.__init
__(self
, "pages")
210 self
.PDFpagelist
= []
211 for pageno
, page
in enumerate(document
.pages
):
212 page
= PDFpage(page
, pageno
, self
, writer
, registry
)
214 self
.PDFpagelist
.append(page
)
216 def write(self
, file, writer
, registry
):
221 ">>\n" % (" ".join(["%i 0 R" % registry
.getrefno(page
)
222 for page
in self
.PDFpagelist
]),
223 len(self
.PDFpagelist
)))
226 class PDFpage(PDFobject
):
228 def __init__(self
, page
, pageno
, PDFpages
, writer
, registry
):
229 PDFobject
.__init
__(self
, "page")
230 self
.PDFpages
= PDFpages
233 # every page uses its own registry in order to find out which
234 # resources are used within the page. However, the
235 # pageregistry is also merged in the global registry
236 self
.pageregistry
= PDFregistry()
238 self
.PDFcontent
= PDFcontent(page
, writer
, self
.pageregistry
)
239 self
.pageregistry
.add(self
.PDFcontent
)
240 registry
.mergeregistry(self
.pageregistry
)
242 def write(self
, file, writer
, registry
):
245 "/Parent %i 0 R\n" % registry
.getrefno(self
.PDFpages
))
246 paperformat
= self
.page
.paperformat
248 file.write("/MediaBox [0 0 %f %f]\n" % (unit
.topt(paperformat
.width
), unit
.topt(paperformat
.height
)))
250 file.write("/MediaBox [%f %f %f %f]\n" % self
.PDFcontent
.bbox
.highrestuple_pt())
251 if self
.PDFcontent
.bbox
and writer
.writebbox
:
252 file.write("/CropBox [%f %f %f %f]\n" % self
.PDFcontent
.bbox
.highrestuple_pt())
253 if self
.page
.rotated
:
254 file.write("/Rotate 90\n")
255 file.write("/Contents %i 0 R\n" % registry
.getrefno(self
.PDFcontent
))
256 self
.pageregistry
.writeresources(file)
260 class PDFcontent(PDFobject
):
262 def __init__(self
, page
, writer
, registry
):
263 PDFobject
.__init
__(self
, registry
, "content")
264 contentfile
= cStringIO
.StringIO()
265 self
.bbox
= bbox
.empty()
267 page
.processPDF(contentfile
, writer
, acontext
, registry
, self
.bbox
)
268 self
.content
= contentfile
.getvalue()
271 def write(self
, file, writer
, registry
):
273 content
= zlib
.compress(self
.content
)
275 content
= self
.content
277 "/Length %i\n" % len(content
))
279 file.write("/Filter /FlateDecode\n")
283 file.write("endstream\n")
286 class PDFfont(PDFobject
):
288 def __init__(self
, font
, chars
, writer
, registry
):
289 PDFobject
.__init
__(self
, "font", font
.name
)
290 registry
.addresource("Font", font
.name
, self
, procset
="Text")
292 self
.fontdescriptor
= PDFfontdescriptor(font
, chars
, writer
, registry
)
293 registry
.add(self
.fontdescriptor
)
296 self
.encoding
= PDFencoding(font
.encoding
, writer
, registry
)
297 registry
.add(self
.encoding
)
301 self
.name
= font
.name
302 self
.basefontname
= font
.basefontname
303 self
.metric
= font
.metric
305 def write(self
, file, writer
, registry
):
309 file.write("/Name /%s\n" % self
.name
)
310 file.write("/BaseFont /%s\n" % self
.basefontname
)
311 if self
.fontdescriptor
.fontfile
is not None and self
.fontdescriptor
.fontfile
.usedchars
is not None:
312 usedchars
= self
.fontdescriptor
.fontfile
.usedchars
313 firstchar
= min(usedchars
.keys())
314 lastchar
= max(usedchars
.keys())
315 file.write("/FirstChar %d\n" % firstchar
)
316 file.write("/LastChar %d\n" % lastchar
)
317 file.write("/Widths\n"
319 for i
in range(firstchar
, lastchar
+1):
320 if i
and not (i
% 8):
324 if usedchars
.has_key(i
):
325 file.write("%f" % self
.metric
.getwidth_ds(i
))
330 file.write("/FirstChar 0\n"
335 if i
and not (i
% 8):
340 width
= self
.metric
.getwidth_ds(i
)
341 except (IndexError, AttributeError):
343 file.write("%f" % width
)
345 file.write("/FontDescriptor %d 0 R\n" % registry
.getrefno(self
.fontdescriptor
))
347 file.write("/Encoding %d 0 R\n" % registry
.getrefno(self
.encoding
))
351 class PDFfontdescriptor(PDFobject
):
353 def __init__(self
, font
, chars
, writer
, registry
):
354 PDFobject
.__init
__(self
, "fontdescriptor", font
.basefontname
)
356 if font
.filename
is None:
359 self
.fontfile
= PDFfontfile(font
.basefontname
, font
.filename
, font
.encoding
, chars
, writer
, registry
)
360 registry
.add(self
.fontfile
)
362 self
.name
= font
.basefontname
363 self
.fontinfo
= font
.metric
.fontinfo()
365 def write(self
, file, writer
, registry
):
367 "/Type /FontDescriptor\n"
368 "/FontName /%s\n" % self
.name
)
369 if self
.fontfile
is None:
370 file.write("/Flags 32\n")
372 file.write("/Flags %d\n" % self
.fontfile
.getflags())
373 file.write("/FontBBox [%d %d %d %d]\n" % self
.fontinfo
.fontbbox
)
374 file.write("/ItalicAngle %d\n" % self
.fontinfo
.italicangle
)
375 file.write("/Ascent %d\n" % self
.fontinfo
.ascent
)
376 file.write("/Descent %d\n" % self
.fontinfo
.descent
)
377 file.write("/CapHeight %d\n" % self
.fontinfo
.capheight
)
378 file.write("/StemV %d\n" % self
.fontinfo
.vstem
)
379 if self
.fontfile
is not None:
380 file.write("/FontFile %d 0 R\n" % registry
.getrefno(self
.fontfile
))
384 class PDFfontfile(PDFobject
):
386 def __init__(self
, name
, filename
, encoding
, chars
, writer
, registry
):
387 PDFobject
.__init
__(self
, "fontfile", filename
)
389 self
.filename
= filename
391 self
.encodingfilename
= None
393 self
.encodingfilename
= encoding
.filename
396 self
.usedchars
[char
] = 1
401 def merge(self
, other
):
402 if self
.encodingfilename
== other
.encodingfilename
:
403 self
.usedchars
.update(other
.usedchars
)
405 # TODO: need to resolve the encoding when several encodings are in the play
408 def mkfontfile(self
):
410 self
.font
= font
.t1font
.T1pfbfont(self
.filename
)
413 if self
.font
is None:
415 return self
.font
.getflags()
417 def write(self
, file, writer
, registry
):
418 if self
.font
is None:
421 # XXX: access to the encoding file
422 if self
.encodingfilename
:
423 encodingfile
= type1font
.encodingfile(self
.encodingfilename
, self
.encodingfilename
)
424 usedglyphs
= dict([(encodingfile
.decode(char
)[1:], 1) for char
in self
.usedchars
.keys()])
426 self
.font
._encoding
()
427 usedglyphs
= dict([(self
.font
.encoding
.decode(char
), 1) for char
in self
.usedchars
.keys()])
428 strippedfont
= self
.font
.getstrippedfont(usedglyphs
)
430 strippedfont
= self
.font
431 strippedfont
.outputPDF(file, writer
)
434 class PDFencoding(PDFobject
):
436 def __init__(self
, encoding
, writer
, registry
):
437 PDFobject
.__init
__(self
, "encoding", encoding
.name
)
438 self
.encoding
= encoding
440 def write(self
, file, writer
, registry
):
441 encodingfile
= type1font
.encodingfile(self
.encoding
.name
, self
.encoding
.filename
)
442 encodingfile
.outputPDF(file, writer
)
447 def __init__(self
, document
, file,
448 title
=None, author
=None, subject
=None, keywords
=None,
449 fullscreen
=0, writebbox
=0, compress
=1, compresslevel
=6):
452 self
.subject
= subject
453 self
.keywords
= keywords
454 self
.fullscreen
= fullscreen
455 self
.writebbox
= writebbox
456 if compress
and not haszlib
:
458 warnings
.warn("compression disabled due to missing zlib module")
459 self
.compress
= compress
460 self
.compresslevel
= compresslevel
462 # the PDFcatalog class automatically builds up the pdfobjects from a document
463 registry
= PDFregistry()
464 catalog
= PDFcatalog(document
, self
, registry
)
465 registry
.add(catalog
)
467 file.write("%%PDF-1.4\n%%%s%s%s%s\n" % (chr(195), chr(182), chr(195), chr(169)))
468 registry
.write(file, self
, catalog
)
475 self
.linewidth_pt
= None
476 # XXX there are both stroke and fill color spaces
477 self
.colorspace
= None
483 def __call__(self
, **kwargs
):
484 newcontext
= copy
.copy(self
)
485 for key
, value
in kwargs
.items():
486 setattr(newcontext
, key
, value
)