1+ # -*- coding: iso-8859-1 -*-
12"""
23A PDF matplotlib backend
4+ Author: Jouni K Seppänen <[email protected] > 5+
6+ As of yet, this implements a small subset of the backend protocol, but
7+ enough to get some output from simple plots. Alpha is supported.
38"""
49from __future__ import division
510
611import md5
712import re
813import sys
914import time
15+ import zlib
1016
17+ from matplotlib import __version__ , rcParams
1118from matplotlib ._pylab_helpers import Gcf
1219from matplotlib .backend_bases import RendererBase , GraphicsContextBase ,\
1320 FigureManagerBase , FigureCanvasBase
@@ -89,8 +96,7 @@ def pdfRepr(self):
8996 return "%d 0 R" % self .id
9097
9198 def write (self , contents , file ):
92- file .recordXref (self .id )
93- write = file .fh .write
99+ write = file .write
94100 write ("%d 0 obj\n " % self .id )
95101 write (pdfRepr (contents ))
96102 write ("\n endobj\n " )
@@ -113,43 +119,75 @@ class Stream:
113119 """PDF stream object.
114120
115121 This has no pdfRepr method. Instead, call begin(), then output the
116- contents of the stream, and finally call end().
122+ contents of the stream by calling write() , and finally call end().
117123 """
118124
119- # TODO: compression
120-
121125 def __init__ (self , id , len , file ):
122126 """id: object id of stream; len: an unused Reference object
123127 for the length of the stream; file: a PdfFile
124128 """
125- self .id = id
126- self .len = len
127- self .file = file
129+ self .id = id # object id
130+ self .len = len # id of length object
131+ self .file = file # file to which the stream is written
132+ self .compressobj = None # compression object
128133
129134 def begin (self ):
130- write = self .file .write
135+ """Initialize stream."""
136+
137+ write = self .file .fh .write
131138 self .file .recordXref (self .id )
132139 write ("%d 0 obj\n " % self .id )
133- write (pdfRepr ({ 'Length' : self .len }))
140+ dict = { 'Length' : self .len }
141+ if rcParams ['pdf.compression' ]:
142+ dict ['Filter' ] = Name ('FlateDecode' )
143+ write (pdfRepr (dict ))
134144 write ("\n stream\n " )
135145 self .pos = self .file .fh .tell ()
146+ if rcParams ['pdf.compression' ]:
147+ self .compressobj = zlib .compressobj (rcParams ['pdf.compression' ])
136148
137149 def end (self ):
150+ """Finalize stream."""
151+
152+ self ._flush ()
138153 length = self .file .fh .tell () - self .pos
139154 self .file .write ("\n endstream\n endobj\n " )
140- self .len .write (length , self .file )
155+ self .file .writeObject (self .len , length )
156+
157+ def write (self , data ):
158+ """Write some data on the stream."""
159+
160+ if self .compressobj is None :
161+ self .file .fh .write (data )
162+ else :
163+ compressed = self .compressobj .compress (data )
164+ self .file .fh .write (compressed )
165+
166+ def _flush (self ):
167+ """Flush the compression object."""
168+
169+ if self .compressobj is not None :
170+ compressed = self .compressobj .flush ()
171+ self .file .fh .write (compressed )
172+ self .compressobj = None
141173
142174class PdfFile :
143175 """PDF file with one page."""
144176
145177 def __init__ (self , width , height , filename ):
146- self .nextObject = 1
178+ self .nextObject = 1 # next free object id
147179 self .xrefTable = [ [0 , 65535 , 'the zero object' ] ]
148180 fh = file (filename , 'w' )
149181 self .fh = fh
150- fh .write ("%PDF-1.4\n " )
182+ self .currentstream = None # stream object to write to, if any
183+ fh .write ("%PDF-1.4\n " ) # 1.4 is the first version to have alpha
184+ # Output some binary chars as a comment so various utilities
185+ # recognize the file as binary by looking at the first few
186+ # lines (see note in section 3.4.1 of the PDF reference).
187+ fh .write ("%\254 \334 \253 \272 \n " )
151188
152189 self .rootObject = self .reserveObject ('root' )
190+ self .infoObject = self .reserveObject ('info' )
153191 pagesObject = self .reserveObject ('pages' )
154192 thePageObject = self .reserveObject ('page 0' )
155193 contentObject = self .reserveObject ('contents of page 0' )
@@ -161,6 +199,11 @@ def __init__(self, width, height, filename):
161199 'Pages' : pagesObject }
162200 self .writeObject (self .rootObject , root )
163201
202+ info = { 'Producer' : 'matplotlib version ' + __version__ \
203+ + ', http://matplotlib.sourceforge.net' , }
204+ # Possible TODO: Title, Author, Subject, Keywords, CreationDate
205+ self .writeObject (self .infoObject , info )
206+
164207 pages = { 'Type' : Name ('Pages' ),
165208 'Kids' : [ thePageObject ],
166209 'Count' : 1 }
@@ -173,26 +216,37 @@ def __init__(self, width, height, filename):
173216 'Contents' : contentObject }
174217 self .writeObject (thePageObject , thePage )
175218
219+ # self.fonts has font objects keyed by internal font names (/F1 etc)
220+ # self.fontnames maps external to internal names
176221 self .fonts , self .fontNames = {}, {}
177- self .nextFont = 1
178- self .alphaStates = {}
222+ self .nextFont = 1 # next free internal font name
223+
224+ self .alphaStates = {} # maps alpha values to graphics state objects
179225 self .nextAlphaState = 1
226+
227+ # The PDF spec recommends to include every procset
180228 procsets = [ Name (x )
181229 for x in "PDF Text ImageB ImageC ImageI" .split () ]
230+
231+ # Write resource dictionary.
232+ # Possibly TODO: more general ExtGState (graphics state dictionaries)
233+ # ColorSpace Pattern Shading XObject Properties
182234 resources = { 'Font' : self .fontObject ,
183235 'ExtGState' : self .alphaStateObject ,
184236 'ProcSet' : procsets }
185- # Other resources: more general ExtGState (graphics state dictionaries)
186- # ColorSpace Pattern Shading XObject Properties
187237 self .writeObject (resourceObject , resources )
188238
239+ # Start the content stream of the page
189240 self .contents = \
190241 Stream (contentObject .id ,
191242 self .reserveObject ('length of content stream' ),
192243 self )
193244 self .contents .begin ()
245+ self .currentstream = self .contents
194246
195247 def close (self ):
248+ # End the content stream and write out the various deferred
249+ # objects
196250 self .contents .end ()
197251 self .writeObject (self .fontObject , self .fonts )
198252 self .writeObject (self .alphaStateObject ,
@@ -203,7 +257,10 @@ def close(self):
203257 self .fh .close ()
204258
205259 def write (self , data ):
206- self .fh .write (data )
260+ if self .currentstream is None :
261+ self .fh .write (data )
262+ else :
263+ self .currentstream .write (data )
207264
208265 def fontName (self , font ):
209266 # TODO: the hard parts (i.e., this only does the Base 14 fonts)
@@ -258,7 +315,7 @@ def writeXref(self):
258315 """Write out the xref table."""
259316
260317 self .startxref = self .fh .tell ()
261- self .fh . write ("xref\n 0 %d\n " % self .nextObject )
318+ self .write ("xref\n 0 %d\n " % self .nextObject )
262319 i = 0
263320 borken = False
264321 for offset , generation , name in self .xrefTable :
@@ -267,20 +324,21 @@ def writeXref(self):
267324 'No offset for object %d (%s)' % (i , name )
268325 borken = True
269326 else :
270- self .fh . write ("%010d %05d n \n " % (offset , generation ))
327+ self .write ("%010d %05d n \n " % (offset , generation ))
271328 i += 1
272329 if borken :
273330 raise AssertionError , 'Indirect object does not exist'
274331
275332 def writeTrailer (self ):
276333 """Write out the PDF trailer."""
277334
278- self .fh . write ("trailer\n " )
279- self .fh . write (pdfRepr (
335+ self .write ("trailer\n " )
336+ self .write (pdfRepr (
280337 {'Size' : self .nextObject ,
281- 'Root' : self .rootObject }))
338+ 'Root' : self .rootObject ,
339+ 'Info' : self .infoObject }))
282340 # Could add 'Info' and 'ID'
283- self .fh . write ("\n startxref\n %d\n %%%%EOF\n " % self .startxref )
341+ self .write ("\n startxref\n %d\n %%%%EOF\n " % self .startxref )
284342
285343
286344
0 commit comments