src/reportlab/pdfbase/pdfdoc.py
changeset 3198 683ca9eb6b18
parent 3172 425aa215185f
child 3226 cd06c1172678
--- a/src/reportlab/pdfbase/pdfdoc.py	Tue Aug 18 13:18:26 2009 +0000
+++ b/src/reportlab/pdfbase/pdfdoc.py	Tue Aug 18 13:36:52 2009 +0000
@@ -82,6 +82,10 @@
 
 # for % substitutions
 LINEENDDICT = {"LINEEND": LINEEND, "PERCENT": "%"}
+PDF_VERSION_DEFAULT = (1, 3)
+PDF_SUPPORT_VERSION = dict(     #map keyword to min version that supports it
+    transparency = (1, 4),
+    )
 
 from types import InstanceType
 def format(element, document, toplevel=0, InstanceType=InstanceType):
@@ -146,7 +150,9 @@
                  dummyoutline=0,
                  compression=rl_config.pageCompression,
                  invariant=rl_config.invariant,
-                 filename=None):
+                 filename=None,
+                 pdfVersion=PDF_VERSION_DEFAULT,
+                 ):
 
         # allow None value to be passed in to mean 'give system defaults'
         if invariant is None:
@@ -154,6 +160,7 @@
         else:
             self.invariant = invariant
         self.setCompression(compression)
+        self._pdfVersion = pdfVersion
         # signature for creating PDF ID
         sig = self.signature = md5()
         sig.update("a reportlab document")
@@ -194,6 +201,11 @@
         # XXX: maybe this should also set self.defaultStreamFilters?
         self.compression = onoff
 
+    def ensureMinPdfVersion(self, *keys):
+        "Ensure that the pdf version is greater than or equal to that specified by the keys"
+        for k in keys:
+            self._pdfVersion = max(self._pdfVersion, PDF_SUPPORT_VERSION[k])
+
     def updateSignature(self, thing):
         "add information to the signature"
         if self._ID: return # but not if its used already!
@@ -306,6 +318,13 @@
         internalname = self.annotationName(name)
         return PDFObjectReference(internalname)
 
+    def addColor(self,cmyk):
+        name = PDFName(cmyk.spotName)[1:]
+        if name not in self.idToObject:
+            sep = PDFSeparationCMYKColor(cmyk).value()  #PDFArray([/Separation /name /DeviceCMYK tint_tf])
+            self.Reference(sep,name)
+        return name
+
     def setTitle(self, title):
         "embeds in PDF file"
         if title is None:
@@ -375,7 +394,7 @@
         idToOf = self.idToOffset
         ### note that new entries may be "appended" DURING FORMATTING
         done = None
-        File = PDFFile() # output collector
+        File = PDFFile(self._pdfVersion) # output collector
         while done is None:
             counter += 1 # do next object...
             if numbertoid.has_key(counter):
@@ -592,7 +611,7 @@
                 raise ValueError("cannot escape %s %s" % (s, repr(s)))
             if escape&2:
                 es = es.replace('\\012','\n')
-            if escape&4 and _isbalanced(s): 
+            if escape&4 and _isbalanced(s):
                 es = es.replace('\\(','(').replace('\\)',')')
             return es
         else:
@@ -629,7 +648,11 @@
         self.dict[name] = document.Reference(self.dict[name])
     def format(self, document,IND=LINEEND+' '):
         dict = self.dict
-        keys = dict.keys()
+        try:
+            keys = dict.keys()
+        except:
+            print repr(dict)
+            raise
         keys.sort()
         L = [(format(PDFName(k),document)+" "+format(dict[k],document)) for k in keys]
         if self.multiline:
@@ -686,7 +709,7 @@
                 PrintClip=checkPDFNames(*'MediaBox CropBox BleedBox TrimBox ArtBox'.split()),
                 PrintScaling=checkPDFNames(*'None AppDefault'.split()),
                 )
-    
+
 # stream filters are objects to support round trip and
 # possibly in the future also support parameters
 class PDFStreamFilterZCompress:
@@ -782,9 +805,7 @@
         content = teststreamcontent
     content = string.strip(content)
     content = string.replace(content, "\n", LINEEND) + LINEEND
-    S = PDFStream()
-    S.content = content
-    S.filters = [PDFBase85Encode, PDFZCompress]
+    S = PDFStream(content = content,filters=[PDFBase85Encode,PDFZCompress])
     # nothing else needed...
     S.__Comment__ = "test stream"
     return S
@@ -821,7 +842,7 @@
 class PDFArrayCompact(PDFArray):
     multiline=False
 
-INDIRECTOBFMT = "%(n)s %(v)s obj%(LINEEND)s%(content)s%(LINEEND)sendobj%(LINEEND)s"
+INDIRECTOBFMT = "%(n)s %(v)s obj%(LINEEND)s%(content)s%(CLINEEND)sendobj%(LINEEND)s"
 class PDFIndirectObject:
     __RefOnly__ = 1
     def __init__(self, name, content):
@@ -829,16 +850,16 @@
         self.content = content
     def format(self, document):
         name = self.name
-        (n, v) = document.idToObjectNumberAndVersion[name]
+        n, v = document.idToObjectNumberAndVersion[name]
         # set encryption parameters
         document.encrypt.register(n, v)
-        content = self.content
-        fcontent = format(content, document, toplevel=1) # yes this is at top level
-        sdict = LINEENDDICT.copy()
-        sdict["n"] = n
-        sdict["v"] = v
-        sdict["content"] = fcontent
-        return INDIRECTOBFMT % sdict
+        fcontent = format(self.content, document, toplevel=1) # yes this is at top level
+        D = LINEENDDICT.copy()
+        D["n"] = n
+        D["v"] = v
+        D["content"] = fcontent
+        D['CLINEEND'] = (LINEEND,'')[fcontent.endswith(LINEEND)]
+        return INDIRECTOBFMT % D
 
 class PDFObjectReference:
     __PDFObject__ = True
@@ -857,17 +878,17 @@
 # any other encoding, and we'll be able to tell if something
 # has run our PDF files through a dodgy Unicode conversion.
 PDFHeader = (
-"%PDF-1.3"+LINEEND+
-"%\223\214\213\236 ReportLab Generated PDF document http://www.reportlab.com"+LINEEND)
+"%%PDF-%s.%s"+LINEEND+
+"%%\223\214\213\236 ReportLab Generated PDF document http://www.reportlab.com"+LINEEND)
 
 class PDFFile:
     __PDFObject__ = True
     ### just accumulates strings: keeps track of current offset
-    def __init__(self):
+    def __init__(self,pdfVersion=PDF_VERSION_DEFAULT):
         self.strings = []
         self.write = self.strings.append
         self.offset = 0
-        self.add(PDFHeader)
+        self.add(PDFHeader % pdfVersion)
 
     def closeOrReset(self):
         pass
@@ -1058,7 +1079,7 @@
         kids.References(document)
         self.Kids = kids
         self.Count = len(pages)
- 
+
 class PDFPage(PDFCatalog):
     __Comment__ = "Page dictionary"
     # all PDF attributes can be set explicitly
@@ -1082,6 +1103,7 @@
     hasImages = 0
     compression = 0
     XObjects = None
+    _colorsUsed = []
     Trans = None
     # transitionstring?
     # xobjects?
@@ -1137,12 +1159,16 @@
             if self.XObjects:
                 #print "XObjects", self.XObjects.dict
                 resources.XObject = self.XObjects
+            if self.ExtGState:
+                resources.ExtGState = self.ExtGState
+            resources.setColorSpace(self._colorsUsed)
+
             self.Resources = resources
         if not self.Parent:
             pages = document.Pages
             self.Parent = document.Reference(pages)
 
-#this code contributed by  Christian Jacobs <cljacobsen@gmail.com> 
+#this code contributed by  Christian Jacobs <cljacobsen@gmail.com>
 class PDFPageLabels(PDFCatalog):
     __comment__ = None
     __RefOnly__ = 0
@@ -1170,7 +1196,7 @@
         self.labels.append((page, label))
 
     def format(self, document):
-        self.labels.sort() 
+        self.labels.sort()
         labels = []
         for page, label in self.labels:
             labels.append(page)
@@ -1213,25 +1239,25 @@
         out or set to None.
 
         * style:
-        
+
             - None:                       No numbering, can be used to display the prefix only.
             - PDFPageLabel.ARABIC:        Use arabic numbers: 1, 2, 3, 4...
             - PDFPageLabel.ROMAN_UPPER:   Use upper case roman numerals: I, II, III...
             - PDFPageLabel.ROMAN_LOWER:   Use lower case roman numerals: i, ii, iii...
             - PDFPageLabel.LETTERS_UPPER: Use upper case letters: A, B, C, D...
             - PDFPageLabel.LETTERS_LOWER: Use lower case letters: a, b, c, d...
-            
+
         * start:
-        
+
             -   An integer specifying the starting number for this PDFPageLabel. This
                 can be used when numbering style changes to reset the page number back
                 to one, ie from roman to arabic, or from arabic to appendecies. Can be
                 any positive integer or None. I'm not sure what the effect of
                 specifying None is, probably that page numbering continues with the
                 current sequence, I'd have to check the spec to clarify though.
-            
+
         * prefix:
-        
+
             -   A string which is prefixed to the page numbers. Can be used to display
                 appendecies in the format: A.1, A.2, ..., B.1, B.2, ... where a
                 PDFPageLabel is used to set the properties for the first page of each
@@ -1240,14 +1266,14 @@
                 display text only, if the 'style' is set to None. This can be used to
                 display strings such as 'Front', 'Back', or 'Cover' for the covers on
                 books.
-            
+
         """
         if style:
             if style.upper() in self.__convertible__: style = getattr(self,style.upper())
             self.S = PDFName(style)
         if start: self.St = PDFnumber(start)
         if prefix: self.P = PDFString(prefix)
-#ends code contributed by  Christian Jacobs <cljacobsen@gmail.com> 
+#ends code contributed by  Christian Jacobs <cljacobsen@gmail.com>
 
 def testpage(document):
     P = PDFPage()
@@ -1297,7 +1323,7 @@
 class PDFOutlines:
     """
     takes a recursive list of outline destinations like::
-    
+
         out = PDFOutline1()
         out.setNames(canvas, # requires canvas for name resolution
         "chapter1dest",
@@ -1309,7 +1335,7 @@
         "chapter3dest",
         ("chapter4dest", ["c4s1", "c4s2"])
         )
-             
+
     Higher layers may build this structure incrementally. KISS at base level.
     """
     __PDFObject__ = True
@@ -1539,7 +1565,7 @@
         D["Producer"] = PDFString(self.producer)
         D["Subject"] = PDFString(self.subject)
         D["Keywords"] = PDFString(self.keywords)
-            
+
         PD = PDFDictionary(D)
         return PD.format(document)
 
@@ -1693,21 +1719,20 @@
 
 class Destination:
     """
-    
+
     not a pdfobject!  This is a placeholder that can delegates
     to a pdf object only after it has been defined by the methods
     below.
-    
+
     EG a Destination can refer to Appendix A before it has been
     defined, but only if Appendix A is explicitly noted as a destination
     and resolved before the document is generated...
-    
+
     For example the following sequence causes resolution before doc generation.
         d = Destination()
         d.fit() # or other format defining method call
         d.setPage(p)
         (at present setPageRef is called on generation of the page).
-    
     """
     __PDFObject__ = True
     representation = format = page = None
@@ -1823,13 +1848,21 @@
         self.basicProcs()
     stdprocs = map(PDFName, string.split("PDF Text ImageB ImageC ImageI"))
     dict_attributes = ("ColorSpace", "XObject", "ExtGState", "Font", "Pattern", "Properties", "Shading")
+
     def allProcs(self):
         # define all standard procsets
         self.ProcSet = self.stdprocs
+
     def basicProcs(self):
         self.ProcSet = self.stdprocs[:2] # just PDF and Text
+
     def basicFonts(self):
         self.Font = PDFObjectReference(BasicFonts)
+
+    def setColorSpace(self,colorsUsed):
+        for c in colorsUsed:
+            self.ColorSpace[c] = PDFObjectReference(c)
+
     def format(self, document):
         D = {}
         from types import ListType, DictType
@@ -2107,8 +2140,7 @@
             self._checkTransparency(im)
 
     def format(self, document):
-        S = PDFStream()
-        S.content = self.streamContent
+        S = PDFStream(content = self.streamContent)
         dict = S.dictionary
         dict["Type"] = PDFName("XObject")
         dict["Subtype"] = PDFName("Image")
@@ -2126,8 +2158,7 @@
         if getattr(self,'smask',None): dict["SMask"] = self.smask
         return S.format(document)
 
-class PDFSeparationCMYKColorSpace:
-    __PDFObject__ = True
+class PDFSeparationCMYKColor:
     def __init__(self, cmyk):
         from reportlab.lib.colors import CMYKColor
         if not isinstance(cmyk,CMYKColor):
@@ -2159,7 +2190,7 @@
                 R('exch')
         return '{%s}' % (' '.join(R.__self__))
 
-    def format(self, document):
+    def value(self):
         return PDFArrayCompact((
                     PDFName('Separation'),
                     PDFName(self.cmyk.spotName),
@@ -2173,7 +2204,7 @@
                         content=self._makeFuncPS(),
                         filters=None,#[PDFBase85Encode, PDFZCompress],
                         )
-                    )).format(document)
+                    ))
 
 if __name__=="__main__":
     print "There is no script interpretation for pdfdoc."