1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
23 # trml2pdf - An RML to PDF converter
24 # Copyright (C) 2003, Fabien Pinckaers, UCL, FSA
26 # Richard Waid <richard@iopen.net>
28 # This library is free software; you can redistribute it and/or
29 # modify it under the terms of the GNU Lesser General Public
30 # License as published by the Free Software Foundation; either
31 # version 2.1 of the License, or (at your option) any later version.
33 # This library is distributed in the hope that it will be useful,
34 # but WITHOUT ANY WARRANTY; without even the implied warranty of
35 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
36 # Lesser General Public License for more details.
38 # You should have received a copy of the GNU Lesser General Public
39 # License along with this library; if not, write to the Free Software
40 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
44 import xml.dom.minidom
49 from reportlab.pdfgen import canvas
50 from reportlab import platypus
57 # Change this to UTF-8 if you plan tu use Reportlab's UTF-8 support
59 # reportlab use "code page 1252" encoding by default. cfr reportlab user guide p.46
63 return s.replace('&', '&').replace('<', '<').replace('>', '>')
65 def _child_get(node, childs):
67 for n in node.childNodes:
68 if (n.nodeType==n.ELEMENT_NODE) and (n.localName==childs):
72 class PageCount(platypus.Flowable):
74 self.canv.beginForm("pageCount")
75 self.canv.setFont("Helvetica", utils.unit_get(str(8)))
76 self.canv.drawString(0, 0, str(self.canv.getPageNumber()))
79 class PageReset(platypus.Flowable):
81 self.canv._pageNumber = 0
83 class _rml_styles(object):
84 def __init__(self, nodes):
87 self.table_styles = {}
89 for style in node.getElementsByTagName('blockTableStyle'):
90 self.table_styles[style.getAttribute('id')] = self._table_style_get(style)
91 for style in node.getElementsByTagName('paraStyle'):
92 self.styles[style.getAttribute('name')] = self._para_style_update(style)
93 for variable in node.getElementsByTagName('initialize'):
94 for name in variable.getElementsByTagName('name'):
95 self.names[ name.getAttribute('id')] = name.getAttribute('value')
97 def _para_style_update(self, node):
99 for attr in ['textColor', 'backColor', 'bulletColor', 'borderColor']:
100 if node.hasAttribute(attr):
101 data[attr] = color.get(node.getAttribute(attr))
102 for attr in ['fontName', 'bulletFontName', 'bulletText']:
103 if node.hasAttribute(attr):
104 data[attr] = node.getAttribute(attr)
105 for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter',
106 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading',
107 'borderWidth','borderPadding','borderRadius']:
108 if node.hasAttribute(attr):
109 data[attr] = utils.unit_get(node.getAttribute(attr))
110 if node.hasAttribute('alignment'):
112 'right':reportlab.lib.enums.TA_RIGHT,
113 'center':reportlab.lib.enums.TA_CENTER,
114 'justify':reportlab.lib.enums.TA_JUSTIFY
116 data['alignment'] = align.get(node.getAttribute('alignment').lower(), reportlab.lib.enums.TA_LEFT)
119 def _table_style_get(self, style_node):
121 for node in style_node.childNodes:
122 if node.nodeType==node.ELEMENT_NODE:
123 start = utils.tuple_int_get(node, 'start', (0,0) )
124 stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
125 if node.localName=='blockValign':
126 styles.append(('VALIGN', start, stop, str(node.getAttribute('value'))))
127 elif node.localName=='blockFont':
128 styles.append(('FONT', start, stop, str(node.getAttribute('name'))))
129 elif node.localName=='blockTextColor':
130 styles.append(('TEXTCOLOR', start, stop, color.get(str(node.getAttribute('colorName')))))
131 elif node.localName=='blockLeading':
132 styles.append(('LEADING', start, stop, utils.unit_get(node.getAttribute('length'))))
133 elif node.localName=='blockAlignment':
134 styles.append(('ALIGNMENT', start, stop, str(node.getAttribute('value'))))
135 elif node.localName=='blockSpan':
136 styles.append(('SPAN', start, stop))
137 elif node.localName=='blockLeftPadding':
138 styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
139 elif node.localName=='blockRightPadding':
140 styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
141 elif node.localName=='blockTopPadding':
142 styles.append(('TOPPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
143 elif node.localName=='blockBottomPadding':
144 styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
145 elif node.localName=='blockBackground':
146 styles.append(('BACKGROUND', start, stop, color.get(node.getAttribute('colorName'))))
147 if node.hasAttribute('size'):
148 styles.append(('FONTSIZE', start, stop, utils.unit_get(node.getAttribute('size'))))
149 elif node.localName=='lineStyle':
150 kind = node.getAttribute('kind')
151 kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
152 assert kind in kind_list
154 if node.hasAttribute('thickness'):
155 thick = float(node.getAttribute('thickness'))
156 styles.append((kind, start, stop, thick, color.get(node.getAttribute('colorName'))))
157 return platypus.tables.TableStyle(styles)
159 def para_style_get(self, node):
161 if node.hasAttribute('style'):
162 if node.getAttribute('style') in self.styles:
163 styles = reportlab.lib.styles.getSampleStyleSheet()
164 sname = node.getAttribute('style')
165 style = reportlab.lib.styles.ParagraphStyle(sname, styles["Normal"], **self.styles[sname])
167 sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.getAttribute('style'),) )
169 styles = reportlab.lib.styles.getSampleStyleSheet()
170 style = copy.deepcopy(styles['Normal'])
171 style.__dict__.update(self._para_style_update(node))
174 class _rml_doc(object):
175 def __init__(self, data, images={}, path='.', title=None):
176 self.dom = xml.dom.minidom.parseString(data)
177 self.filename = self.dom.documentElement.getAttribute('filename')
182 def docinit(self, els):
183 from reportlab.lib.fonts import addMapping
184 from reportlab.pdfbase import pdfmetrics
185 from reportlab.pdfbase.ttfonts import TTFont
188 for font in node.getElementsByTagName('registerFont'):
189 name = font.getAttribute('fontName').encode('ascii')
190 fname = font.getAttribute('fontFile').encode('ascii')
191 pdfmetrics.registerFont(TTFont(name, fname ))
192 addMapping(name, 0, 0, name) #normal
193 addMapping(name, 0, 1, name) #italic
194 addMapping(name, 1, 0, name) #bold
195 addMapping(name, 1, 1, name) #italic and bold
197 def _textual_image(self, node):
200 for n in node.childNodes:
201 if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
203 return base64.decodestring(rc)
205 def _images(self, el):
207 for node in el.getElementsByTagName('image'):
208 result[node.getAttribute('name')] = self._textual_image(node)
211 def render(self, out):
212 el = self.dom.documentElement.getElementsByTagName('docinit')
216 el = self.dom.documentElement.getElementsByTagName('stylesheet')
217 self.styles = _rml_styles(el)
219 el = self.dom.documentElement.getElementsByTagName('images')
221 self.images.update( self._images(el[0]) )
223 el = self.dom.documentElement.getElementsByTagName('template')
225 pt_obj = _rml_template(out, el[0], self, images=self.images, path=self.path, title=self.title)
226 pt_obj.render(self.dom.documentElement.getElementsByTagName('story'))
228 self.canvas = canvas.Canvas(out)
229 pd = self.dom.documentElement.getElementsByTagName('pageDrawing')[0]
230 pd_obj = _rml_canvas(self.canvas, None, self, self.images, path=self.path, title=self.title)
233 self.canvas.showPage()
236 class _rml_canvas(object):
237 def __init__(self, canvas, doc_tmpl=None, doc=None, images={}, path='.', title=None):
239 self.styles = doc.styles
240 self.doc_tmpl = doc_tmpl
246 self.canvas.setTitle(self.title)
248 def _textual(self, node, x=0, y=0):
250 for n in node.childNodes:
251 if n.nodeType == n.ELEMENT_NODE:
252 if n.localName == 'seq':
253 from reportlab.lib.sequencer import getSequencer
255 rc += str(seq.next(n.getAttribute('id')))
256 if n.localName == 'pageCount':
258 self.canvas.translate(x,y)
259 self.canvas.doForm('pageCount')
261 self.canvas.translate(-x,-y)
262 if n.localName == 'pageNumber':
263 rc += str(self.canvas.getPageNumber())
264 elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
265 # this doesn't need to be "entities" encoded like flowables need to
267 return rc.encode(encoding, 'replace')
269 def _drawString(self, node):
270 v = utils.attr_get(node, ['x','y'])
271 self.canvas.drawString(text=self._textual(node, **v), **v)
272 def _drawCenteredString(self, node):
273 v = utils.attr_get(node, ['x','y'])
274 self.canvas.drawCentredString(text=self._textual(node, **v), **v)
275 def _drawRightString(self, node):
276 v = utils.attr_get(node, ['x','y'])
277 self.canvas.drawRightString(text=self._textual(node, **v), **v)
278 def _rect(self, node):
279 if node.hasAttribute('round'):
280 self.canvas.roundRect(radius=utils.unit_get(node.getAttribute('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
282 self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
284 def _ellipse(self, node):
285 x1 = utils.unit_get(node.getAttribute('x'))
286 x2 = utils.unit_get(node.getAttribute('width'))
287 y1 = utils.unit_get(node.getAttribute('y'))
288 y2 = utils.unit_get(node.getAttribute('height'))
289 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
290 def _curves(self, node):
291 line_str = utils.text_get(node).split()
293 while len(line_str)>7:
294 self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
295 line_str = line_str[8:]
296 def _lines(self, node):
297 line_str = utils.text_get(node).split()
299 while len(line_str)>3:
300 lines.append([utils.unit_get(l) for l in line_str[0:4]])
301 line_str = line_str[4:]
302 self.canvas.lines(lines)
303 def _grid(self, node):
304 xlist = [utils.unit_get(s) for s in node.getAttribute('xs').split(',')]
305 ylist = [utils.unit_get(s) for s in node.getAttribute('ys').split(',')]
306 self.canvas.grid(xlist, ylist)
307 def _translate(self, node):
310 if node.hasAttribute('dx'):
311 dx = utils.unit_get(node.getAttribute('dx'))
312 if node.hasAttribute('dy'):
313 dy = utils.unit_get(node.getAttribute('dy'))
314 self.canvas.translate(dx,dy)
316 def _circle(self, node):
317 self.canvas.circle(x_cen=utils.unit_get(node.getAttribute('x')), y_cen=utils.unit_get(node.getAttribute('y')), r=utils.unit_get(node.getAttribute('radius')), **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
319 def _place(self, node):
320 flows = _rml_flowable(self.doc, images=self.images, path=self.path, title=self.title).render(node)
321 infos = utils.attr_get(node, ['x','y','width','height'])
323 infos['y']+=infos['height']
325 w,h = flow.wrap(infos['width'], infos['height'])
326 if w<=infos['width'] and h<=infos['height']:
328 flow.drawOn(self.canvas,infos['x'],infos['y'])
331 raise ValueError, "Not enough space"
333 def _line_mode(self, node):
334 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
335 lcap = {'default':0, 'round':1, 'square':2}
336 if node.hasAttribute('width'):
337 self.canvas.setLineWidth(utils.unit_get(node.getAttribute('width')))
338 if node.hasAttribute('join'):
339 self.canvas.setLineJoin(ljoin[node.getAttribute('join')])
340 if node.hasAttribute('cap'):
341 self.canvas.setLineCap(lcap[node.getAttribute('cap')])
342 if node.hasAttribute('miterLimit'):
343 self.canvas.setDash(utils.unit_get(node.getAttribute('miterLimit')))
344 if node.hasAttribute('dash'):
345 dashes = node.getAttribute('dash').split(',')
346 for x in range(len(dashes)):
347 dashes[x]=utils.unit_get(dashes[x])
348 self.canvas.setDash(node.getAttribute('dash').split(','))
350 def _image(self, node):
352 from reportlab.lib.utils import ImageReader
354 if not node.hasAttribute('file'):
356 if node.hasAttribute('name'):
357 image_data = self.images[node.getAttribute('name')]
358 s = StringIO.StringIO(image_data)
361 image_data = base64.decodestring(node.firstChild.nodeValue)
362 if not image_data: return False
363 s = StringIO.StringIO(image_data)
365 if node.getAttribute('file') in self.images:
366 s = StringIO.StringIO(self.images[node.getAttribute('file')])
369 u = urllib.urlopen(str(node.getAttribute('file')))
370 s = StringIO.StringIO(u.read())
372 u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
373 s = StringIO.StringIO(u.read())
375 (sx,sy) = img.getSize()
378 for tag in ('width','height','x','y'):
379 if node.hasAttribute(tag):
380 args[tag] = utils.unit_get(node.getAttribute(tag))
381 if ('width' in args) and (not 'height' in args):
382 args['height'] = sy * args['width'] / sx
383 elif ('height' in args) and (not 'width' in args):
384 args['width'] = sx * args['height'] / sy
385 elif ('width' in args) and ('height' in args):
386 if (float(args['width'])/args['height'])>(float(sx)>sy):
387 args['width'] = sx * args['height'] / sy
389 args['height'] = sy * args['width'] / sx
390 self.canvas.drawImage(img, **args)
392 def _path(self, node):
393 self.path = self.canvas.beginPath()
394 self.path.moveTo(**utils.attr_get(node, ['x','y']))
395 for n in node.childNodes:
396 if n.nodeType == node.ELEMENT_NODE:
397 if n.localName=='moveto':
398 vals = utils.text_get(n).split()
399 self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
400 elif n.localName=='curvesto':
401 vals = utils.text_get(n).split()
405 pos.append(utils.unit_get(vals.pop(0)))
406 self.path.curveTo(*pos)
407 elif (n.nodeType == node.TEXT_NODE):
408 data = n.data.split() # Not sure if I must merge all TEXT_NODE ?
410 x = utils.unit_get(data.pop(0))
411 y = utils.unit_get(data.pop(0))
412 self.path.lineTo(x,y)
413 if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')):
415 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
417 def render(self, node):
419 'drawCentredString': self._drawCenteredString,
420 'drawRightString': self._drawRightString,
421 'drawString': self._drawString,
423 'ellipse': self._ellipse,
424 'lines': self._lines,
426 'curves': self._curves,
427 'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))),
428 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))),
429 'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))),
430 'place': self._place,
431 'circle': self._circle,
432 'lineMode': self._line_mode,
434 'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
435 'translate': self._translate,
438 for nd in node.childNodes:
439 if nd.nodeType==nd.ELEMENT_NODE:
441 if nd.localName==tag:
445 class _rml_draw(object):
446 def __init__(self, node, styles, images={}, path='.', title=None):
452 self.canvas_title = title
454 def render(self, canvas, doc):
456 cnv = _rml_canvas(canvas, doc, self.styles, images=self.images, path=self.path, title=self.canvas_title)
457 cnv.render(self.node)
458 canvas.restoreState()
460 class _rml_flowable(object):
461 def __init__(self, doc, images={}, path='.', title=None):
463 self.styles = doc.styles
468 def _textual(self, node):
470 for n in node.childNodes:
471 if n.nodeType == node.ELEMENT_NODE:
472 if n.localName == 'getName':
473 newNode = self.doc.dom.createTextNode(self.styles.names.get(n.getAttribute('id'),'Unknown name'))
474 node.insertBefore(newNode, n)
477 elif n.localName == 'pageNumber':
478 rc += '<pageNumber/>' # TODO: change this !
480 #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
483 elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
484 rc += str2xml(n.data)
485 return rc.encode(encoding, 'replace')
487 def _table(self, node):
492 childs = _child_get(node,'tr')
499 if tr.hasAttribute('style'):
500 st = copy.deepcopy(self.styles.table_styles[tr.getAttribute('style')])
505 if tr.hasAttribute('paraStyle'):
506 paraStyle = self.styles.styles[tr.getAttribute('paraStyle')]
510 for td in _child_get(tr, 'td'):
511 if td.hasAttribute('style'):
512 st = copy.deepcopy(self.styles.table_styles[td.getAttribute('style')])
519 if td.hasAttribute('paraStyle'):
521 paraStyle = self.styles.styles[td.getAttribute('paraStyle')]
525 for n in td.childNodes:
526 if n.nodeType==node.ELEMENT_NODE:
527 fl = self._flowable(n, extra_style=paraStyle)
530 flow = self._textual(td)
532 if len(data2)>length:
535 while len(ab)<length:
537 while len(data2)<length:
541 if node.hasAttribute('colWidths'):
542 assert length == len(node.getAttribute('colWidths').split(','))
543 colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')]
544 if node.hasAttribute('rowHeights'):
545 rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')]
546 if len(rowheights) == 1:
547 rowheights = rowheights[0]
548 table = platypus.LongTable(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
549 if node.hasAttribute('style'):
550 table.setStyle(self.styles.table_styles[node.getAttribute('style')])
555 def _illustration(self, node):
556 class Illustration(platypus.flowables.Flowable):
557 def __init__(self, node, styles, self2):
560 self.width = utils.unit_get(node.getAttribute('width'))
561 self.height = utils.unit_get(node.getAttribute('height'))
563 def wrap(self, *args):
564 return (self.width, self.height)
567 drw = _rml_draw(self.node, self.styles, images=self.self2.images, path=self.self2.path, title=self.self2.title)
568 drw.render(self.canv, None)
569 return Illustration(node, self.styles, self)
571 def _textual_image(self, node):
574 for n in node.childNodes:
575 if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
577 return base64.decodestring(rc)
579 def _flowable(self, node, extra_style=None):
580 if node.localName=='para':
581 style = self.styles.para_style_get(node)
583 style.__dict__.update(extra_style)
584 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
585 elif node.localName=='barCode':
587 from reportlab.graphics.barcode import code128
588 from reportlab.graphics.barcode import code39
589 from reportlab.graphics.barcode import code93
590 from reportlab.graphics.barcode import common
591 from reportlab.graphics.barcode import fourstate
592 from reportlab.graphics.barcode import usps
594 print 'Warning: Reportlab barcode extension not installed !'
596 args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
598 'codabar': lambda x: common.Codabar(x, **args),
599 'code11': lambda x: common.Code11(x, **args),
600 'code128': lambda x: code128.Code128(x, **args),
601 'standard39': lambda x: code39.Standard39(x, **args),
602 'standard93': lambda x: code93.Standard93(x, **args),
603 'i2of5': lambda x: common.I2of5(x, **args),
604 'extended39': lambda x: code39.Extended39(x, **args),
605 'extended93': lambda x: code93.Extended93(x, **args),
606 'msi': lambda x: common.MSI(x, **args),
607 'fim': lambda x: usps.FIM(x, **args),
608 'postnet': lambda x: usps.POSTNET(x, **args),
611 if node.hasAttribute('code'):
612 code = node.getAttribute('code').lower()
613 return codes[code](self._textual(node))
614 elif node.localName=='name':
615 self.styles.names[ node.getAttribute('id')] = node.getAttribute('value')
617 elif node.localName=='xpre':
618 style = self.styles.para_style_get(node)
619 return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
620 elif node.localName=='pre':
621 style = self.styles.para_style_get(node)
622 return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
623 elif node.localName=='illustration':
624 return self._illustration(node)
625 elif node.localName=='blockTable':
626 return self._table(node)
627 elif node.localName=='title':
628 styles = reportlab.lib.styles.getSampleStyleSheet()
629 style = styles['Title']
630 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
631 elif re.match('^h([1-9]+[0-9]*)$', node.localName):
632 styles = reportlab.lib.styles.getSampleStyleSheet()
633 style = styles['Heading'+str(node.localName[1:])]
634 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
635 elif node.localName=='image':
636 if not node.hasAttribute('file'):
637 if node.hasAttribute('name'):
638 image_data = self.doc.images[node.getAttribute('name')].read()
641 image_data = base64.decodestring(node.firstChild.nodeValue)
642 image = StringIO.StringIO(image_data)
643 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
645 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
647 from reportlab.lib.utils import ImageReader
648 name = str(node.getAttribute('file'))
649 img = ImageReader(name)
650 (sx,sy) = img.getSize()
653 for tag in ('width','height'):
654 if node.hasAttribute(tag):
655 args[tag] = utils.unit_get(node.getAttribute(tag))
656 if ('width' in args) and (not 'height' in args):
657 args['height'] = sy * args['width'] / sx
658 elif ('height' in args) and (not 'width' in args):
659 args['width'] = sx * args['height'] / sy
660 elif ('width' in args) and ('height' in args):
661 if (float(args['width'])/args['height'])>(float(sx)>sy):
662 args['width'] = sx * args['height'] / sy
664 args['height'] = sy * args['width'] / sx
665 return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
666 elif node.localName=='spacer':
667 if node.hasAttribute('width'):
668 width = utils.unit_get(node.getAttribute('width'))
670 width = utils.unit_get('1cm')
671 length = utils.unit_get(node.getAttribute('length'))
672 return platypus.Spacer(width=width, height=length)
673 elif node.localName=='section':
674 return self.render(node)
675 elif node.localName == 'pageNumberReset':
677 elif node.localName in ('pageBreak', 'nextPage'):
678 return platypus.PageBreak()
679 elif node.localName=='condPageBreak':
680 return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
681 elif node.localName=='setNextTemplate':
682 return platypus.NextPageTemplate(str(node.getAttribute('name')))
683 elif node.localName=='nextFrame':
684 return platypus.CondPageBreak(1000) # TODO: change the 1000 !
685 elif node.localName == 'setNextFrame':
686 from reportlab.platypus.doctemplate import NextFrameFlowable
687 return NextFrameFlowable(str(node.getAttribute('name')))
688 elif node.localName == 'currentFrame':
689 from reportlab.platypus.doctemplate import CurrentFrameFlowable
690 return CurrentFrameFlowable(str(node.getAttribute('name')))
691 elif node.localName == 'frameEnd':
692 return EndFrameFlowable()
693 elif node.localName == 'hr':
694 width_hr=node.hasAttribute('width') and node.getAttribute('width') or '100%'
695 color_hr=node.hasAttribute('color') and node.getAttribute('color') or 'black'
696 thickness_hr=node.hasAttribute('thickness') and node.getAttribute('thickness') or 1
697 lineCap_hr=node.hasAttribute('lineCap') and node.getAttribute('lineCap') or 'round'
698 return platypus.flowables.HRFlowable(width=width_hr,color=color.get(color_hr),thickness=float(thickness_hr),lineCap=str(lineCap_hr))
700 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
703 def render(self, node_story):
705 node = node_story.firstChild
707 if node.nodeType == node.ELEMENT_NODE:
708 flow = self._flowable(node)
710 if type(flow) == type([]):
714 node = node.nextSibling
717 from reportlab.platypus.doctemplate import ActionFlowable
719 class EndFrameFlowable(ActionFlowable):
720 def __init__(self,resume=0):
721 ActionFlowable.__init__(self,('frameEnd',resume))
723 class TinyDocTemplate(platypus.BaseDocTemplate):
724 def ___handle_pageBegin(self):
725 self.page = self.page + 1
726 self.pageTemplate.beforeDrawPage(self.canv,self)
727 self.pageTemplate.checkPageSize(self.canv,self)
728 self.pageTemplate.onPage(self.canv,self)
729 for f in self.pageTemplate.frames: f._reset()
731 #keep a count of flowables added to this page. zero indicates bad stuff
732 self._curPageFlowableCount = 0
733 if hasattr(self,'_nextFrameIndex'):
734 del self._nextFrameIndex
735 for f in self.pageTemplate.frames:
739 self.handle_frameBegin()
740 def afterFlowable(self, flowable):
741 if isinstance(flowable, PageReset):
742 self.canv._pageNumber = 0
744 class _rml_template(object):
745 def __init__(self, out, node, doc, images={}, path='.', title=None):
749 if not node.hasAttribute('pageSize'):
750 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
752 ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
753 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
754 cm = reportlab.lib.units.cm
755 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
756 self.page_templates = []
757 self.styles = doc.styles
759 pts = node.getElementsByTagName('pageTemplate')
762 for frame_el in pt.getElementsByTagName('frame'):
763 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
764 if utils.attr_get(frame_el, ['last']):
765 frame.lastFrame = True
766 frames.append( frame )
767 gr = pt.getElementsByTagName('pageGraphics')
769 drw = _rml_draw(gr[0], self.doc, images=images, path=self.path, title=self.title)
770 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
772 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
773 self.doc_tmpl.addPageTemplates(self.page_templates)
775 def render(self, node_stories):
777 r = _rml_flowable(self.doc,images=self.images, path=self.path, title=self.title)
778 for node_story in node_stories:
779 fis += r.render(node_story)
780 if node_story==node_stories[-1]:
782 fis.append(PageCount())
784 fis.append(platypus.PageBreak())
786 self.doc_tmpl.build(fis)
788 def parseString(data, fout=None, images={}, path='.',title=None):
789 r = _rml_doc(data, images, path, title=title)
796 fp = StringIO.StringIO()
801 print 'Usage: trml2pdf input.rml >output.pdf'
802 print 'Render the standard input (RML) and output a PDF file'
805 if __name__=="__main__":
807 if sys.argv[1]=='--help':
809 print parseString(file(sys.argv[1], 'r').read()),
811 print 'Usage: trml2pdf input.rml >output.pdf'
812 print 'Try \'trml2pdf --help\' for more information.'
814 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: