1
##############################################################################
3
# Copyright (c) 2007 Zope Foundation and Contributors.
6
# This software is subject to the provisions of the Zope Public License,
7
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
8
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11
# FOR A PARTICULAR PURPOSE.
13
##############################################################################
14
"""Page Drawing Related Element Processing
16
$Id: canvas.py 114908 2010-07-22 02:23:18Z srichter $
18
__docformat__ = "reStructuredText"
20
import reportlab.pdfgen.canvas
21
from z3c.rml import attr, directive, interfaces, occurence, stylesheet
22
from z3c.rml import chart, flowable, form, page
25
class IShape(interfaces.IRMLDirectiveSignature):
26
"""A shape to be drawn on the canvas."""
29
title=u'X-Coordinate',
30
description=(u'The X-coordinate of the lower-left position of the '
35
title=u'Y-Coordinate',
36
description=(u'The Y-coordinate of the lower-left position of the '
42
description=u'A flag to specify whether the shape should be filled.',
45
stroke = attr.Boolean(
47
description=(u"A flag to specify whether the shape's outline should "
52
class CanvasRMLDirective(directive.RMLDirective):
57
kwargs = dict(self.getAttributeValues(attrMapping=self.attrMapping))
58
canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
59
getattr(canvas, self.callable)(**kwargs)
62
class IDrawString(interfaces.IRMLDirectiveSignature):
63
"""Draws a simple string (left aligned) onto the canvas at the specified
67
title=u'X-Coordinate',
68
description=(u'The X-coordinate of the lower-left position of the '
73
title=u'Y-Coordinate',
74
description=(u'The Y-coordinate of the lower-left position of the '
80
description=(u'The string/text that is put onto the canvas.'),
83
class DrawString(CanvasRMLDirective):
84
signature = IDrawString
85
callable = 'drawString'
87
class IDrawRightString(IDrawString):
88
"""Draws a simple string (right aligned) onto the canvas at the specified
91
class DrawRightString(DrawString):
92
signature = IDrawRightString
93
callable = 'drawRightString'
96
class IDrawCenteredString(IDrawString):
97
"""Draws a simple string (centered aligned) onto the canvas at the specified
100
class DrawCenteredString(DrawString):
101
signature = IDrawCenteredString
102
callable = 'drawCentredString'
105
class IDrawAlignedString(IDrawString):
106
"""Draws a simple string (aligned to the pivot character) onto the canvas
107
at the specified location."""
109
pivotChar = attr.Text(
111
description=(u'The string/text that is put onto the canvas.'),
117
class DrawAlignedString(DrawString):
118
signature = IDrawAlignedString
119
callable = 'drawAlignedString'
122
class IEllipse(IShape):
123
"""Draws an ellipse on the canvas."""
125
width = attr.Measurement(
127
description=u'The width of the ellipse.',
130
height = attr.Measurement(
132
description=u'The height of the ellipse.',
135
class Ellipse(CanvasRMLDirective):
138
attrMapping = {'x': 'x1', 'y': 'y1'}
141
kwargs = dict(self.getAttributeValues(attrMapping=self.attrMapping))
142
canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
143
# Convert width and height to end locations
144
kwargs['x2'] = kwargs['x1'] + kwargs['width']
146
kwargs['y2'] = kwargs['y1'] + kwargs['height']
148
getattr(canvas, self.callable)(**kwargs)
151
class ICircle(IShape):
152
"""Draws a circle on the canvas."""
154
radius = attr.Measurement(
156
description=u'The radius of the circle.',
159
class Circle(CanvasRMLDirective):
162
attrMapping = {'x': 'x_cen', 'y': 'y_cen', 'radius': 'r'}
165
class IRectangle(IShape):
166
"""Draws an ellipse on the canvas."""
168
width = attr.Measurement(
170
description=u'The width of the rectangle.',
173
height = attr.Measurement(
175
description=u'The height of the rectangle.',
178
round = attr.Measurement(
179
title=u'Corner Radius',
180
description=u'The radius of the rounded corners.',
183
class Rectangle(CanvasRMLDirective):
184
signature = IRectangle
186
attrMapping = {'round': 'radius'}
189
if 'round' in self.element.keys():
190
self.callable = 'roundRect'
191
super(Rectangle, self).process()
194
class IGrid(interfaces.IRMLDirectiveSignature):
195
"""A shape to be drawn on the canvas."""
198
title=u'X-Coordinates',
199
description=(u'A sequence x-coordinates that represent the vertical '
201
value_type=attr.Measurement(),
205
title=u'Y-Coordinates',
206
description=(u'A sequence y-coordinates that represent the horizontal '
208
value_type=attr.Measurement(),
212
class Grid(CanvasRMLDirective):
215
attrMapping = {'xs': 'xlist', 'ys': 'ylist'}
218
class ILines(interfaces.IRMLDirectiveSignature):
219
"""A path of connected lines drawn on the canvas."""
221
linelist = attr.TextNodeGrid(
223
description=(u'A list of lines coordinates to draw.'),
224
value_type=attr.Measurement(),
228
class Lines(CanvasRMLDirective):
233
class ICurves(interfaces.IRMLDirectiveSignature):
234
"""A path of connected bezier curves drawn on the canvas."""
236
curvelist = attr.TextNodeGrid(
238
description=(u'A list of curve coordinates to draw.'),
239
value_type=attr.Measurement(),
243
class Curves(CanvasRMLDirective):
248
argset = self.getAttributeValues(valuesOnly=True)[0]
249
canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
251
getattr(canvas, self.callable)(*args)
254
class IImage(interfaces.IRMLDirectiveSignature):
255
"""Draws an external image on the canvas."""
259
description=(u'Reference to the external file of the iamge.'),
262
x = attr.Measurement(
263
title=u'X-Coordinate',
264
description=(u'The X-coordinate of the lower-left position of the '
268
y = attr.Measurement(
269
title=u'Y-Coordinate',
270
description=(u'The Y-coordinate of the lower-left position of the '
274
width = attr.Measurement(
276
description=u'The width of the image.',
279
height = attr.Measurement(
281
description=u'The height of the image.',
284
showBoundary = attr.Boolean(
285
title=u'Show Boundary',
286
description=(u'A flag determining whether a border should be drawn '
287
u'around the image.'),
291
preserveAspectRatio = attr.Boolean(
292
title=u'Preserve Aspect Ratio',
293
description=(u"A flag determining whether the image's aspect ration "
294
u"should be conserved under any circumstances."),
298
class Image(CanvasRMLDirective):
300
callable = 'drawImage'
301
attrMapping = {'file': 'image'}
304
kwargs = dict(self.getAttributeValues(attrMapping=self.attrMapping))
305
preserve = kwargs.pop('preserveAspectRatio')
306
show = kwargs.pop('showBoundary')
309
imgX, imgY = kwargs['image'].getSize()
311
# Scale image correctly, if width and/or height were specified
312
if 'width' in kwargs and 'height' not in kwargs:
313
kwargs['height'] = imgY * kwargs['width'] / imgX
314
elif 'height' in kwargs and 'width' not in kwargs:
315
kwargs['width'] = imgX * kwargs['height'] / imgY
316
elif 'width' in kwargs and 'height' in kwargs:
317
if float(kwargs['width'])/kwargs['height'] > float(imgX)/imgY:
318
kwargs['width'] = imgX * kwargs['height'] / imgY
320
kwargs['height'] = imgY * kwargs['width'] / imgX
322
canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
323
getattr(canvas, self.callable)(**kwargs)
326
width = kwargs.get('width', kwargs['image'].getSize()[0])
327
height = kwargs.get('height', kwargs['image'].getSize()[1])
328
canvas.rect(kwargs['x'], kwargs['y'], width, height)
331
class IPlace(interfaces.IRMLDirectiveSignature):
332
"""Draws a set of flowables on the canvas within a given region."""
334
x = attr.Measurement(
335
title=u'X-Coordinate',
336
description=(u'The X-coordinate of the lower-left position of the '
340
y = attr.Measurement(
341
title=u'Y-Coordinate',
342
description=(u'The Y-coordinate of the lower-left position of the '
346
width = attr.Measurement(
348
description=u'The width of the place.',
351
height = attr.Measurement(
353
description=u'The height of the place.',
356
class Place(CanvasRMLDirective):
360
x, y, width, height = self.getAttributeValues(
361
select=('x', 'y', 'width', 'height'), valuesOnly=True)
364
flows = flowable.Flow(self.element, self.parent)
367
canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
368
for flow in flows.flow:
369
flowWidth, flowHeight = flow.wrap(width, height)
370
if flowWidth <= width and flowHeight <= height:
372
flow.drawOn(canvas, x, y)
375
raise ValueError("Not enough space")
378
class IParam(interfaces.IRMLDirectiveSignature):
379
"""Sets one paramter for the text annotation."""
383
description=u'The name of the paramter.',
386
value = attr.TextNode(
388
description=(u'The parameter value.'),
391
class Param(directive.RMLDirective):
395
args = dict(self.getAttributeValues())
396
self.parent.params[args['name']] = args['value']
399
class ITextAnnotation(interfaces.IRMLDirectiveSignature):
400
"""Writes a low-level text annotation into the PDF."""
401
occurence.containing(
402
occurence.ZeroOrMore('param', IParam))
404
contents = attr.FirstLevelTextNode(
406
description=u'The PDF commands that are inserted as annotation.',
409
class TextAnnotation(CanvasRMLDirective):
410
signature = ITextAnnotation
411
factories = {'param': Param}
413
paramTypes = {'escape': attr.Integer()}
416
contents = self.getAttributeValues(valuesOnly=True)[0]
418
self.processSubDirectives()
419
for name, type in self.paramTypes.items():
420
if name in self.params:
421
bound = type.bind(self)
422
self.params[name] = bound.fromUnicode(self.params[name])
423
canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
424
canvas.textAnnotation(contents, **self.params)
427
class IMoveTo(interfaces.IRMLDirectiveSignature):
428
"""Move the path cursor to the specified location."""
430
position = attr.TextNodeSequence(
432
description=u'Position to which the path pointer is moved to.',
433
value_type=attr.Measurement(),
438
class MoveTo(directive.RMLDirective):
442
args = self.getAttributeValues(valuesOnly=True)
443
self.parent.path.moveTo(*args[0])
446
class ICurveTo(interfaces.IRMLDirectiveSignature):
447
"""Create a bezier curve from the current location to the specified one."""
449
curvelist = attr.TextNodeGrid(
450
title=u'Curve Specification',
451
description=u'Describes the end position and the curve properties.',
452
value_type=attr.Measurement(),
456
class CurveTo(directive.RMLDirective):
460
argset = self.getAttributeValues(valuesOnly=True)[0]
462
self.parent.path.curveTo(*args)
464
class ICurvesTo(ICurveTo):
466
directive.DeprecatedDirective(
468
'Available for ReportLab RML compatibility. Please use the "curveto" '
469
'directive instead.')
473
"""Create a line path."""
474
occurence.containing(
475
occurence.ZeroOrMore('moveto', IMoveTo),
476
occurence.ZeroOrMore('curveto', ICurveTo),
477
occurence.ZeroOrMore('curvesto', ICurvesTo),
480
points = attr.TextNodeGrid(
482
description=(u'A list of coordinate points that define th path.'),
483
value_type=attr.Measurement(),
487
close = attr.Boolean(
489
description=(u"A flag specifying whether the path should be closed."),
493
class Path(CanvasRMLDirective):
501
def processPoints(self, text):
502
if text.strip() == '':
504
bound = self.signature['points'].bind(self)
505
for coords in bound.fromUnicode(text):
506
self.path.lineTo(*coords)
509
kwargs = dict(self.getAttributeValues(ignore=('points',)))
511
# Start the path and set the cursor to the start location.
512
canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
513
self.path = canvas.beginPath()
514
self.path.moveTo(kwargs.pop('x'), kwargs.pop('y'))
516
# Process the text before the first sub-directive.
517
if self.element.text is not None:
518
self.processPoints(self.element.text)
519
# Handle each sub-directive.
520
for directive in self.element.getchildren():
521
if directive.tag in self.factories:
522
self.factories[directive.tag](directive, self).process()
523
# If there is more text after sub-directive, process it.
524
if directive.tail is not None:
525
self.processPoints(directive.tail)
527
if kwargs.pop('close', False):
530
canvas.drawPath(self.path, **kwargs)
533
class IFill(interfaces.IRMLDirectiveSignature):
534
"""Set the fill color."""
538
description=(u'The color value to be set.'),
541
class Fill(CanvasRMLDirective):
543
callable = 'setFillColor'
544
attrMapping = {'color': 'aColor'}
547
class IStroke(interfaces.IRMLDirectiveSignature):
548
"""Set the stroke/line color."""
552
description=(u'The color value to be set.'),
555
class Stroke(CanvasRMLDirective):
557
callable = 'setStrokeColor'
558
attrMapping = {'color': 'aColor'}
561
class ISetFont(interfaces.IRMLDirectiveSignature):
562
"""Set the font name and/or size."""
566
description=(u'The name of the font as it was registered.'),
569
size = attr.Measurement(
571
description=(u'The font size.'),
574
leading = attr.Measurement(
576
description=(u'The font leading.'),
579
class SetFont(CanvasRMLDirective):
582
attrMapping = {'name': 'psfontname'}
585
class IScale(interfaces.IRMLDirectiveSignature):
586
"""Scale the drawing using x and y scaling factors."""
589
title=u'X-Scaling-Factor',
590
description=(u'The scaling factor applied on x-coordinates.'),
594
title=u'Y-Scaling-Factor',
595
description=(u'The scaling factor applied on y-coordinates.'),
598
class Scale(CanvasRMLDirective):
601
attrMapping = {'sx': 'x', 'sy': 'y'}
604
class ITranslate(interfaces.IRMLDirectiveSignature):
605
"""Translate the drawing coordinates by the specified x and y offset."""
607
dx = attr.Measurement(
609
description=(u'The amount to move the drawing to the right.'),
612
dy = attr.Measurement(
614
description=(u'The amount to move the drawing upward.'),
617
class Translate(CanvasRMLDirective):
618
signature = ITranslate
619
callable = 'translate'
622
class IRotate(interfaces.IRMLDirectiveSignature):
623
"""Rotate the drawing counterclockwise."""
625
degrees = attr.Measurement(
627
description=(u'The angle in degrees.'),
630
class Rotate(CanvasRMLDirective):
633
attrMapping = {'degrees': 'theta'}
636
class ISkew(interfaces.IRMLDirectiveSignature):
637
"""Skew the drawing."""
639
alpha = attr.Measurement(
641
description=(u'The amount to skew the drawing in the horizontal.'),
644
beta = attr.Measurement(
646
description=(u'The amount to skew the drawing in the vertical.'),
649
class Skew(CanvasRMLDirective):
654
class ITransform(interfaces.IRMLDirectiveSignature):
655
"""A full 2-D matrix transformation"""
657
matrix = attr.TextNodeSequence(
659
description=u'The transformation matrix.',
660
value_type=attr.Float(),
665
class Transform(CanvasRMLDirective):
666
signature = ITransform
669
args = self.getAttributeValues(valuesOnly=True)
670
canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
671
canvas.transform(*args[0])
674
class ILineMode(interfaces.IRMLDirectiveSignature):
675
"""Set the line mode for the following graphics elements."""
677
width = attr.Measurement(
679
description=(u'The line width.'),
682
dash = attr.Sequence(
683
title=u'Dash-Pattern',
684
description=(u'The dash-pattern of a line.'),
685
value_type=attr.Measurement(),
688
miterLimit = attr.Measurement(
689
title=u'Miter Limit',
690
description=(u'The ???.'),
695
description=u'The way lines are joined together.',
696
choices=interfaces.JOIN_CHOICES,
701
description=u'The cap is the desciption of how the line-endings look.',
702
choices=interfaces.CAP_CHOICES,
705
class LineMode(CanvasRMLDirective):
706
signature = ILineMode
709
kw = dict(self.getAttributeValues())
710
canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
712
canvas.setLineWidth(kw['width'])
714
canvas.setLineJoin(kw['join'])
716
canvas.setLineCap(kw['cap'])
717
if 'miterLimit' in kw:
718
canvas.setMiterLimit(kw['miterLimit'])
720
canvas.setDash(kw['dash'])
723
class IDrawing(interfaces.IRMLDirectiveSignature):
724
"""A container directive for all directives that draw directly on the
726
occurence.containing(
727
occurence.ZeroOrMore('drawString', IDrawString),
728
occurence.ZeroOrMore('drawRightString', IDrawRightString),
729
occurence.ZeroOrMore('drawCenteredString', IDrawCenteredString),
730
occurence.ZeroOrMore('drawCentredString', IDrawCenteredString),
731
occurence.ZeroOrMore('drawAlignedString', IDrawAlignedString),
733
occurence.ZeroOrMore('ellipse', IEllipse),
734
occurence.ZeroOrMore('circle', ICircle),
735
occurence.ZeroOrMore('rect', IRectangle),
736
occurence.ZeroOrMore('grid', IGrid),
737
occurence.ZeroOrMore('lines', ILines),
738
occurence.ZeroOrMore('curves', ICurves),
739
occurence.ZeroOrMore('image', IImage),
740
occurence.ZeroOrMore('place', IPlace),
741
occurence.ZeroOrMore('textAnnotation', ITextAnnotation),
742
occurence.ZeroOrMore('path', IPath),
743
# State Change Operations
744
occurence.ZeroOrMore('fill', IFill),
745
occurence.ZeroOrMore('stroke', IStroke),
746
occurence.ZeroOrMore('setFont', ISetFont),
747
occurence.ZeroOrMore('scale', IScale),
748
occurence.ZeroOrMore('translate', ITranslate),
749
occurence.ZeroOrMore('rotate', IRotate),
750
occurence.ZeroOrMore('skew', ISkew),
751
occurence.ZeroOrMore('transform', ITransform),
752
occurence.ZeroOrMore('lineMode', ILineMode),
753
# Form Field Elements
754
occurence.ZeroOrMore('barCode', form.IBarCode),
755
occurence.ZeroOrMore('textField', form.ITextField),
756
occurence.ZeroOrMore('buttonField', form.IButtonField),
757
occurence.ZeroOrMore('selectField', form.ISelectField),
759
occurence.ZeroOrMore('barChart', chart.IBarChart),
760
occurence.ZeroOrMore('barChart3D', chart.IBarChart3D),
761
occurence.ZeroOrMore('linePlot', chart.ILinePlot),
762
occurence.ZeroOrMore('linePlot3D', chart.ILinePlot3D),
763
occurence.ZeroOrMore('pieChart', chart.IPieChart),
764
occurence.ZeroOrMore('pieChart3D', chart.IPieChart3D),
765
occurence.ZeroOrMore('spiderChart', chart.ISpiderChart),
768
class Drawing(directive.RMLDirective):
772
'drawString': DrawString,
773
'drawRightString': DrawRightString,
774
'drawCenteredString': DrawCenteredString,
775
'drawCentredString': DrawCenteredString,
776
'drawAlignedString': DrawAlignedString,
786
'textAnnotation': TextAnnotation,
788
# Form Field Elements
789
'barCode': form.BarCode,
790
'textField': form.TextField,
791
'buttonField': form.ButtonField,
792
'selectField': form.SelectField,
793
# State Change Operations
798
'translate': Translate,
801
'transform': Transform,
802
'lineMode': LineMode,
804
'barChart': chart.BarChart,
805
'barChart3D': chart.BarChart3D,
806
'linePlot': chart.LinePlot,
807
'linePlot3D': chart.LinePlot3D,
808
'pieChart': chart.PieChart,
809
'pieChart3D': chart.PieChart3D,
810
'spiderChart': chart.SpiderChart
814
class IPageDrawing(IDrawing):
815
"""Draws directly on the content of one page's canvas. Every call of this
816
directive creates a new page."""
818
occurence.containing(
819
#'mergePage': IMergePage,
820
*IDrawing.getTaggedValue('directives'))
822
class PageDrawing(Drawing):
825
factories = Drawing.factories.copy()
827
'mergePage': page.MergePage
831
super(Drawing, self).process()
832
canvas = attr.getManager(self, interfaces.ICanvasManager).canvas
836
class IPageInfo(interfaces.IRMLDirectiveSignature):
837
"""Set's up page-global settings."""
839
pageSize = attr.PageSize(
841
description=(u'The page size of all pages within this document.'),
844
class PageInfo(CanvasRMLDirective):
846
callable = 'setPageSize'
847
attrMapping = {'pageSize': 'size'}