Index: setup.py
===================================================================
--- setup.py (revision 129885)
+++ setup.py (working copy)
@@ -57,6 +57,7 @@
'pyPdf',
'reportlab',
'setuptools',
+ 'svg2rlg',
'zope.interface',
'zope.schema',
],
Index: src/z3c/rml/attr.py
===================================================================
--- src/z3c/rml/attr.py (revision 129885)
+++ src/z3c/rml/attr.py (working copy)
@@ -337,12 +337,26 @@
self.onlyOpen = onlyOpen
def fromUnicode(self, value):
+ if value.lower().endswith('.svg') or value.lower().endswith('.svgz'):
+ return self._load_svg(value)
fileObj = super(Image, self).fromUnicode(value)
if self.onlyOpen:
return fileObj
return reportlab.lib.utils.ImageReader(fileObj)
+ def _load_svg(self, value):
+ from gzip import GzipFile
+ from svg2rlg import Renderer
+ from xml.etree import cElementTree
+ fileObj = super(Image, self).fromUnicode(value)
+ svg = fileObj.getvalue()
+ if svg[:2] == '\037\213':
+ svg = GzipFile(fileobj=fileObj).read()
+ svg = cElementTree.fromstring(svg)
+ return Renderer(value).render(svg)
+
+
class Color(RMLAttribute):
"""Requires the input of a color. There are several supported formats.
@@ -362,7 +376,7 @@
manager = getManager(self.context)
if value.startswith('rml:'):
- value = manager.names[value[4:]]
+ value = manager.get_name[value[4:]]
if value in manager.colors:
return manager.colors[value]
Index: src/z3c/rml/canvas.py
===================================================================
--- src/z3c/rml/canvas.py (revision 129885)
+++ src/z3c/rml/canvas.py (working copy)
@@ -104,18 +104,27 @@
return str(canvas.getPageNumber() + int(elem.get('countingFrom', 1)) - 1)
def getName(self, elem, canvas):
- return attr.getManager(self).names[elem.get('id')]
+ return attr.getManager(self).get_name(elem.get('id'), elem.get('default'))
+ def evalString(self, elem, canvas):
+ value = self._getText(elem, canvas)
+ if elem.tail:
+ value = value[:len(elem.tail)]
+ # Maybe still not safe
+ return unicode(eval(value.strip(), {'__builtins__': None}, {}))
+
handleElements = {'pageNumber': getPageNumber,
- 'getName': getName}
+ 'getName': getName,
+ 'evalString': evalString}
def _getText(self, node, canvas):
text = node.text or u''
- for sub in node.iterdescendants():
+ for sub in node.getchildren():
if sub.tag in self.handleElements:
text += self.handleElements[sub.tag](self, sub, canvas)
else:
self._getText(sub, canvas)
+ text += sub.tail or u''
text += node.tail or u''
return text
@@ -368,13 +377,20 @@
callable = 'drawImage'
attrMapping = {'file': 'image'}
+ def _getSize(self, image):
+ from reportlab.graphics.shapes import Drawing
+ if not isinstance(image, Drawing):
+ return image.getSize()
+ else:
+ return (image.width, image.height)
+
def process(self):
kwargs = dict(self.getAttributeValues(attrMapping=self.attrMapping))
preserve = kwargs.pop('preserveAspectRatio')
show = kwargs.pop('showBoundary')
if preserve:
- imgX, imgY = kwargs['image'].getSize()
+ imgX, imgY = self._getSize(kwargs['image'])
# Scale image correctly, if width and/or height were specified
if 'width' in kwargs and 'height' not in kwargs:
@@ -388,11 +404,20 @@
kwargs['height'] = imgY * kwargs['width'] / imgX
canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
- getattr(canvas, self.callable)(**kwargs)
+ if not hasattr(kwargs['image'], 'drawOn'):
+ getattr(canvas, self.callable)(**kwargs)
+ else:
+ width, height = self._getSize(kwargs['image'])
+ kwargs['image'].scale(
+ kwargs.get('width', width) / width,
+ kwargs.get('height', height) / height
+ )
+ kwargs['image'].drawOn(canvas, kwargs['x'], kwargs['y'])
+
if show:
- width = kwargs.get('width', kwargs['image'].getSize()[0])
- height = kwargs.get('height', kwargs['image'].getSize()[1])
+ width = kwargs.get('width', self._getSize(kwargs['image'])[0])
+ height = kwargs.get('height', self._getSize(kwargs['image'])[1])
canvas.rect(kwargs['x'], kwargs['y'], width, height)
Index: src/z3c/rml/document.py
===================================================================
--- src/z3c/rml/document.py (revision 129885)
+++ src/z3c/rml/document.py (working copy)
@@ -24,6 +24,7 @@
from reportlab.pdfbase import pdfmetrics, ttfonts, cidfonts
from reportlab.lib import colors, fonts
from reportlab.platypus import tableofcontents
+from reportlab.platypus.doctemplate import IndexingFlowable
from z3c.rml import attr, canvas, directive, doclogic, interfaces, list
from z3c.rml import occurence, pdfinclude, special, storyplace, stylesheet
@@ -34,14 +35,16 @@
class IRegisterType1Face(interfaces.IRMLDirectiveSignature):
"""Register a new Type 1 font face."""
- afmFile = attr.String(
+ afmFile = attr.File(
title=u'AFM File',
description=u'Path to AFM file used to register the Type 1 face.',
+ doNotOpen=True,
required=True)
- pfbFile = attr.String(
+ pfbFile = attr.File(
title=u'PFB File',
description=u'Path to PFB file used to register the Type 1 face.',
+ doNotOpen=True,
required=True)
class RegisterType1Face(directive.RMLDirective):
@@ -122,9 +125,10 @@
u'be previously registered.'),
required=True)
- fileName = attr.String(
+ fileName = attr.File(
title=u'File Name',
description=u'File path of the of the TrueType font.',
+ doNotOpen=True,
required=True)
class RegisterTTFont(directive.RMLDirective):
@@ -442,7 +446,14 @@
description=u'A flag when set shows crop marks on the page.',
required=False)
+ printScaling = attr.Choice(
+ title=u'Print Scaling',
+ description=(u'The print scaling mode in which the document is opened '
+ u'in the viewer.'),
+ choices=('None', 'AppDefault'),
+ required=False)
+
class DocInit(directive.RMLDirective):
signature = IDocInit
factories = {
@@ -463,6 +474,7 @@
self.parent.cropMarks = kwargs.get('useCropMarks', False)
self.parent.pageMode = kwargs.get('pageMode')
self.parent.pageLayout = kwargs.get('pageLayout')
+ self.parent.printScaling = kwargs.get('printScaling')
super(DocInit, self).process()
@@ -527,6 +539,7 @@
self.pageLayout = None
self.pageMode = None
self.logger = None
+ self.printScaling = None
def _indexAdd(self, canvas, name, label):
self.indexes[name](canvas, name, label)
@@ -542,6 +555,10 @@
canvas._doc._catalog.setPageLayout(self.pageLayout)
if self.pageMode:
canvas._doc._catalog.setPageMode(self.pageMode)
+ if self.printScaling:
+ canvas.setViewerPreference(
+ 'PrintScaling', self.printScaling
+ )
def process(self, outputFile=None):
"""Process document"""
@@ -581,7 +598,7 @@
elif self.element.find('template') is not None:
self.processSubDirectives(select=('template', 'story'))
self.doc.beforeDocument = self._beforeDocument
- self.doc.multiBuild(self.flowables)
+ self.doc.multiBuild(self.flowables, maxPasses=2)
# Process all post processors
for name, processor in self.postProcessors:
@@ -595,3 +612,26 @@
# Cleanup.
colors.toColor.setExtraColorsNameSpace({})
+ def get_name(self, name, default=None):
+
+ if name not in self.names:
+ if self.doc._indexingFlowables and \
+ isinstance(self.doc._indexingFlowables[-1], DummyIndexingFlowable):
+ del self.doc._indexingFlowables[-1]
+ self.doc._indexingFlowables.append(DummyIndexingFlowable())
+
+ if default is None:
+ default = u''
+ return self.names.get(name, default)
+
+
+class DummyIndexingFlowable(IndexingFlowable):
+ """A dummy flowable to trick multiBuild into performing +1 pass."""
+
+ def __init__(self):
+ self.i = -1
+
+ def isSatisfied(self):
+ self.i += 1
+ return self.i
+
Index: src/z3c/rml/flowable.py
===================================================================
--- src/z3c/rml/flowable.py (revision 129885)
+++ src/z3c/rml/flowable.py (working copy)
@@ -1271,9 +1271,40 @@
return (0, 0)
def draw(self):
- self.manager.names[self.id] = self.value
+ if self.id not in self.manager.names:
+ text = self._getText(self.value, self.manager.canvas)
+ self.manager.names[self.id] = text
+ self.value = u''
+ def getPageNumber(self, elem, canvas):
+ return str(canvas.getPageNumber() + int(elem.get('countingFrom', 1)) - 1)
+ def getName(self, elem, canvas):
+ return self.manager.get_name(elem.get('id'), elem.get('default'))
+
+ def evalString(self, elem, canvas):
+ value = self._getText(elem, canvas, 2)
+ if elem.tail:
+ value = value[:len(elem.tail)]
+ # Maybe still not safe
+ return unicode(eval(value.strip(), {'__builtins__': None}, {}))
+
+ handleElements = {'pageNumber': getPageNumber,
+ 'getName': getName,
+ 'evalString': evalString}
+
+ def _getText(self, node, canvas, depth=1):
+ text = node.text or u''
+ for sub in node.getchildren():
+ if sub.tag in self.handleElements:
+ text += self.handleElements[sub.tag](self, sub, canvas)
+ else:
+ self._getText(sub, canvas)
+ text += sub.tail or u''
+ text += node.tail or u''
+ return text
+
+
class INamedString(interfaces.IRMLDirectiveSignature):
"""Defines a name for a string."""
@@ -1294,7 +1325,7 @@
id, value = self.getAttributeValues(valuesOnly=True)
manager = attr.getManager(self)
# We have to delay assigning values, otherwise the last one wins.
- self.parent.flow.append(NamedStringFlowable(manager, id, value))
+ self.parent.flow.append(NamedStringFlowable(manager, id, self.element))
class IShowIndex(interfaces.IRMLDirectiveSignature):
Index: src/z3c/rml/paraparser.py
===================================================================
--- src/z3c/rml/paraparser.py (revision 129885)
+++ src/z3c/rml/paraparser.py (working copy)
@@ -41,6 +41,10 @@
canvas = frame.f_locals.get('canv', None)
if canvas is None:
+ # Is it always available here?
+ canvas = getattr(frame.f_locals.get('self', None), 'canv', None)
+
+ if canvas is None:
raise Exception("Can't use in this location.")
return str(canvas.getPageNumber())
@@ -52,6 +56,7 @@
def __init__(self, attributes):
reportlab.platypus.paraparser.ParaFrag.__init__(self)
self.id = attributes['id']
+ self.default = attributes.get('default')
@property
def text(self):
@@ -64,24 +69,53 @@
canvas = frame.f_locals.get('canv', None)
if canvas is None:
+ # Is it always available here?
+ canvas = getattr(frame.f_locals.get('self', None), 'canv', None)
+
+ if canvas is None:
raise Exception("Can't use in this location.")
- return canvas.manager.names[self.id]
+ return canvas.manager.get_name(self.id, self.default)
+class EvalStringFragment(reportlab.platypus.paraparser.ParaFrag):
+ """A fragment whose `text` is evaluated at access time."""
+
+ def __init__(self, attributes):
+ reportlab.platypus.paraparser.ParaFrag.__init__(self)
+ self.frags = []
+
+ @property
+ def text(self):
+ text = u''
+ for frag in self.frags:
+ if isinstance(frag, basestring):
+ text += frag
+ else:
+ text += frag.text
+ # Maybe still not safe
+ return unicode(eval(text.strip(), {'__builtins__': None}, {}))
+
+
class Z3CParagraphParser(reportlab.platypus.paraparser.ParaParser):
"""Extensions to paragraph-internal XML parsing."""
+ in_eval = False
+
def startDynamic(self, attributes, klass):
frag = klass(attributes)
frag.__dict__.update(self._stack[-1].__dict__)
frag.fontName = reportlab.lib.fonts.tt2ps(
frag.fontName, frag.bold, frag.italic)
- self.fragList.append(frag)
- self._stack.append(frag)
+ if self.in_eval:
+ self._stack[-1].frags.append(frag)
+ else:
+ self.fragList.append(frag)
+ self._stack.append(frag)
def endDynamic(self):
- self._pop()
+ if not self.in_eval:
+ self._pop()
def start_pageNumber(self, attributes):
self.startDynamic(attributes, PageNumberFragment)
@@ -95,7 +129,21 @@
def end_getName(self):
self.endDynamic()
+ def start_evalString(self, attributes):
+ self.startDynamic(attributes, EvalStringFragment)
+ self.in_eval = True
+ def end_evalString(self):
+ self.in_eval = False
+ self.endDynamic()
+
+ def handle_data(self, data):
+ if self.in_eval:
+ self._stack[-1].frags.append(data)
+ else:
+ reportlab.platypus.paraparser.ParaParser.handle_data(self, data)
+
+
# Monkey-patch reportlabs global parser instance. Wah.
import reportlab.platypus.paragraph
reportlab.platypus.paragraph._parser = Z3CParagraphParser()