55"""
66from __future__ import division
77
8+ import codecs
89import os
910import re
1011import sys
@@ -149,6 +150,16 @@ def pdfRepr(obj):
149150 elif isinstance (obj , (int , long )):
150151 return "%d" % obj
151152
153+ # Unicode strings are encoded in UTF-16BE with byte-order mark.
154+ elif isinstance (obj , unicode ):
155+ try :
156+ # But maybe it's really ASCII?
157+ s = obj .encode ('ASCII' )
158+ return pdfRepr (s )
159+ except UnicodeEncodeError :
160+ s = codecs .BOM_UTF16_BE + obj .encode ('UTF-16BE' )
161+ return pdfRepr (s )
162+
152163 # Strings are written in parentheses, with backslashes and parens
153164 # escaped. Actually balanced parens are allowed, but it is
154165 # simpler to escape them all. TODO: cut long strings into lines;
@@ -374,7 +385,6 @@ def __init__(self, filename):
374385 fh .write ("%\254 \334 \253 \272 \n " )
375386
376387 self .rootObject = self .reserveObject ('root' )
377- self .infoObject = self .reserveObject ('info' )
378388 self .pagesObject = self .reserveObject ('pages' )
379389 self .pageList = []
380390 self .fontObject = self .reserveObject ('fonts' )
@@ -388,13 +398,12 @@ def __init__(self, filename):
388398 'Pages' : self .pagesObject }
389399 self .writeObject (self .rootObject , root )
390400
391- info = { 'Creator' : 'matplotlib ' + __version__ \
392- + ', http://matplotlib.sf.net' ,
393- 'Producer' : 'matplotlib pdf backend' ,
394- 'CreationDate' : datetime .today () }
395-
396- # Possible TODO: Title, Author, Subject, Keywords
397- self .writeObject (self .infoObject , info )
401+ revision = '$Rev$' .strip ('$' ).split (':' )[1 ].strip ()
402+ self .infoDict = {
403+ 'Creator' : 'matplotlib %s, http://matplotlib.sf.net' % __version__ ,
404+ 'Producer' : 'matplotlib pdf backend r%s' % revision ,
405+ 'CreationDate' : datetime .today ()
406+ }
398407
399408 self .fontNames = {} # maps filenames to internal font names
400409 self .nextFont = 1 # next free internal font name
@@ -471,6 +480,7 @@ def close(self):
471480 { 'Type' : Name ('Pages' ),
472481 'Kids' : self .pageList ,
473482 'Count' : len (self .pageList ) })
483+ self .writeInfoDict ()
474484
475485 # Finalize the file
476486 self .writeXref ()
@@ -1280,6 +1290,31 @@ def writeXref(self):
12801290 if borken :
12811291 raise AssertionError , 'Indirect object does not exist'
12821292
1293+ def writeInfoDict (self ):
1294+ """Write out the info dictionary, checking it for good form"""
1295+
1296+ is_date = lambda x : isinstance (x , datetime )
1297+ check_trapped = lambda x : isinstance (x , Name ) and x .name in \
1298+ ('True' , 'False' , 'Unknown' )
1299+ keywords = {'Title' : is_string_like ,
1300+ 'Author' : is_string_like ,
1301+ 'Subject' : is_string_like ,
1302+ 'Keywords' : is_string_like ,
1303+ 'Creator' : is_string_like ,
1304+ 'Producer' : is_string_like ,
1305+ 'CreationDate' : is_date ,
1306+ 'ModDate' : is_date ,
1307+ 'Trapped' : check_trapped }
1308+ for k in self .infoDict .keys ():
1309+ if k not in keywords :
1310+ warnings .warn ('Unknown infodict keyword: %s' % k )
1311+ else :
1312+ if not keywords [k ](self .infoDict [k ]):
1313+ warnings .warn ('Bad value for infodict keyword %s' % k )
1314+
1315+ self .infoObject = self .reserveObject ('info' )
1316+ self .writeObject (self .infoObject , self .infoDict )
1317+
12831318 def writeTrailer (self ):
12841319 """Write out the PDF trailer."""
12851320
@@ -2052,6 +2087,14 @@ def close(self):
20522087 self ._file .close ()
20532088 self ._file = None
20542089
2090+ def infodict (self ):
2091+ """
2092+ Return a modifiable information dictionary object
2093+ (see PDF reference section 10.2.1 'Document Information
2094+ Dictionary').
2095+ """
2096+ return self ._file .infoDict
2097+
20552098 def savefig (self , figure = None , ** kwargs ):
20562099 """
20572100 Save the Figure instance *figure* to this file as a new page.
0 commit comments