1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
24 from StringIO import StringIO
28 from reportlab.pdfgen import canvas
29 from reportlab import platypus
34 from lxml import etree
36 from reportlab.platypus.doctemplate import ActionFlowable
40 class PageCount(platypus.Flowable):
42 self.canv.beginForm("pageCount")
43 self.canv.setFont("Helvetica", utils.unit_get(str(8)))
44 self.canv.drawString(0, 0, str(self.canv.getPageNumber()))
47 class PageReset(platypus.Flowable):
49 self.canv._pageNumber = 0
51 class _rml_styles(object,):
52 def __init__(self, nodes, localcontext):
53 self.localcontext = localcontext
56 self.table_styles = {}
58 for style in node.findall('blockTableStyle'):
59 self.table_styles[style.get('id')] = self._table_style_get(style)
60 for style in node.findall('paraStyle'):
61 self.styles[style.get('name')] = self._para_style_update(style)
62 for variable in node.findall('initialize'):
63 for name in variable.findall('name'):
64 self.names[ name.get('id')] = name.get('value')
66 def _para_style_update(self, node):
68 for attr in ['textColor', 'backColor', 'bulletColor', 'borderColor']:
70 data[attr] = color.get(node.get(attr))
71 for attr in ['fontName', 'bulletFontName', 'bulletText']:
73 data[attr] = node.get(attr)
74 for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter',
75 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading',
76 'borderWidth','borderPadding','borderRadius']:
78 data[attr] = utils.unit_get(node.get(attr))
79 if node.get('alignment'):
81 'right':reportlab.lib.enums.TA_RIGHT,
82 'center':reportlab.lib.enums.TA_CENTER,
83 'justify':reportlab.lib.enums.TA_JUSTIFY
85 data['alignment'] = align.get(node.get('alignment').lower(), reportlab.lib.enums.TA_LEFT)
88 def _table_style_get(self, style_node):
90 for node in style_node:
91 start = utils.tuple_int_get(node, 'start', (0,0) )
92 stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
93 if node.tag=='blockValign':
94 styles.append(('VALIGN', start, stop, str(node.get('value'))))
95 elif node.tag=='blockFont':
96 styles.append(('FONT', start, stop, str(node.get('name'))))
97 elif node.tag=='blockTextColor':
98 styles.append(('TEXTCOLOR', start, stop, color.get(str(node.get('colorName')))))
99 elif node.tag=='blockLeading':
100 styles.append(('LEADING', start, stop, utils.unit_get(node.get('length'))))
101 elif node.tag=='blockAlignment':
102 styles.append(('ALIGNMENT', start, stop, str(node.get('value'))))
103 elif node.tag=='blockSpan':
104 styles.append(('SPAN', start, stop))
105 elif node.tag=='blockLeftPadding':
106 styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.get('length'))))
107 elif node.tag=='blockRightPadding':
108 styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.get('length'))))
109 elif node.tag=='blockTopPadding':
110 styles.append(('TOPPADDING', start, stop, utils.unit_get(node.get('length'))))
111 elif node.tag=='blockBottomPadding':
112 styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.get('length'))))
113 elif node.tag=='blockBackground':
114 styles.append(('BACKGROUND', start, stop, color.get(node.get('colorName'))))
116 styles.append(('FONTSIZE', start, stop, utils.unit_get(node.get('size'))))
117 elif node.tag=='lineStyle':
118 kind = node.get('kind')
119 kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
120 assert kind in kind_list
122 if node.get('thickness'):
123 thick = float(node.get('thickness'))
124 styles.append((kind, start, stop, thick, color.get(node.get('colorName'))))
125 return platypus.tables.TableStyle(styles)
127 def para_style_get(self, node):
129 if node.get('style'):
130 if node.get('style') in self.styles:
131 styles = reportlab.lib.styles.getSampleStyleSheet()
132 sname = node.get('style')
133 style = reportlab.lib.styles.ParagraphStyle(sname, styles["Normal"], **self.styles[sname])
135 sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.get('style'),) )
137 styles = reportlab.lib.styles.getSampleStyleSheet()
138 style = styles['Normal']
139 style.__dict__.update(self._para_style_update(node))
142 class _rml_doc(object):
143 def __init__(self, node, localcontext, images={}, path='.', title=None):
144 self.localcontext = localcontext
146 self.filename = self.etree.get('filename')
151 def docinit(self, els):
152 from reportlab.lib.fonts import addMapping
153 from reportlab.pdfbase import pdfmetrics
154 from reportlab.pdfbase.ttfonts import TTFont
157 for font in node.findall('registerFont'):
158 name = font.get('fontName').encode('ascii')
159 fname = font.get('fontFile').encode('ascii')
160 pdfmetrics.registerFont(TTFont(name, fname ))
161 addMapping(name, 0, 0, name) #normal
162 addMapping(name, 0, 1, name) #italic
163 addMapping(name, 1, 0, name) #bold
164 addMapping(name, 1, 1, name) #italic and bold
166 def setTTFontMapping(self,face, fontname,filename, mode='all'):
167 from reportlab.lib.fonts import addMapping
168 from reportlab.pdfbase import pdfmetrics
169 from reportlab.pdfbase.ttfonts import TTFont
171 pdfmetrics.registerFont(TTFont(fontname, filename ))
173 addMapping(face, 0, 0, fontname) #normal
174 addMapping(face, 0, 1, fontname) #italic
175 addMapping(face, 1, 0, fontname) #bold
176 addMapping(face, 1, 1, fontname) #italic and bold
177 elif (mode== 'normal') or (mode == 'regular'):
178 addMapping(face, 0, 0, fontname) #normal
179 elif (mode == 'italic'):
180 addMapping(face, 0, 1, fontname) #italic
181 elif (mode == 'bold'):
182 addMapping(face, 1, 0, fontname) #bold
183 elif (mode == 'bolditalic'):
184 addMapping(face, 1, 1, fontname) #italic and bold
186 def _textual_image(self, node):
189 rc +=( etree.tostring(n) or '') + n.tail
190 return base64.decodestring(node.tostring())
192 def _images(self, el):
194 for node in el.findall('image'):
195 rc =( node.text or '')
196 result[node.get('name')] = base64.decodestring(rc)
199 def render(self, out):
200 el = self.etree.findall('docinit')
204 el = self.etree.findall('stylesheet')
205 self.styles = _rml_styles(el,self.localcontext)
207 el = self.etree.findall('images')
209 self.images.update( self._images(el[0]) )
210 el = self.etree.findall('template')
212 pt_obj = _rml_template(self.localcontext, out, el[0], self, images=self.images, path=self.path, title=self.title)
213 el = utils._child_get(self.etree, self, 'story')
216 self.canvas = canvas.Canvas(out)
217 pd = self.etree.find('pageDrawing')[0]
218 pd_obj = _rml_canvas(self.canvas, self.localcontext, None, self, self.images, path=self.path, title=self.title)
221 self.canvas.showPage()
224 class _rml_canvas(object):
225 def __init__(self, canvas, localcontext, doc_tmpl=None, doc=None, images={}, path='.', title=None):
226 self.localcontext = localcontext
228 self.styles = doc.styles
229 self.doc_tmpl = doc_tmpl
235 self.canvas.setTitle(self.title)
237 def _textual(self, node, x=0, y=0):
238 text = node.text and node.text.encode('utf-8') or ''
239 rc = utils._process_text(self, text)
242 from reportlab.lib.sequencer import getSequencer
244 rc += str(seq.next(n.get('id')))
245 if n.tag == 'pageCount':
247 self.canvas.translate(x,y)
248 self.canvas.doForm('pageCount')
250 self.canvas.translate(-x,-y)
251 if n.tag == 'pageNumber':
252 rc += str(self.canvas.getPageNumber())
253 rc += utils._process_text(self, n.tail)
256 def _drawString(self, node):
257 v = utils.attr_get(node, ['x','y'])
258 text=self._textual(node, **v)
259 text = utils.xml2str(text)
260 self.canvas.drawString(text=text, **v)
262 def _drawCenteredString(self, node):
263 v = utils.attr_get(node, ['x','y'])
264 text=self._textual(node, **v)
265 text = utils.xml2str(text)
266 self.canvas.drawCentredString(text=text, **v)
268 def _drawRightString(self, node):
269 v = utils.attr_get(node, ['x','y'])
270 text=self._textual(node, **v)
271 text = utils.xml2str(text)
272 self.canvas.drawRightString(text=text, **v)
274 def _rect(self, node):
275 if node.get('round'):
276 self.canvas.roundRect(radius=utils.unit_get(node.get('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
278 self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
280 def _ellipse(self, node):
281 x1 = utils.unit_get(node.get('x'))
282 x2 = utils.unit_get(node.get('width'))
283 y1 = utils.unit_get(node.get('y'))
284 y2 = utils.unit_get(node.get('height'))
286 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
288 def _curves(self, node):
289 line_str = node.text.split()
291 while len(line_str)>7:
292 self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
293 line_str = line_str[8:]
295 def _lines(self, node):
296 line_str = node.text.split()
298 while len(line_str)>3:
299 lines.append([utils.unit_get(l) for l in line_str[0:4]])
300 line_str = line_str[4:]
301 self.canvas.lines(lines)
303 def _grid(self, node):
304 xlist = [utils.unit_get(s) for s in node.get('xs').split(',')]
305 ylist = [utils.unit_get(s) for s in node.get('ys').split(',')]
307 self.canvas.grid(xlist, ylist)
309 def _translate(self, node):
310 dx = utils.unit_get(node.get('dx')) or 0
311 dy = utils.unit_get(node.get('dy')) or 0
312 self.canvas.translate(dx,dy)
314 def _circle(self, node):
315 self.canvas.circle(x_cen=utils.unit_get(node.get('x')), y_cen=utils.unit_get(node.get('y')), r=utils.unit_get(node.get('radius')), **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
317 def _place(self, node):
318 flows = _rml_flowable(self.doc, self.localcontext, images=self.images, path=self.path, title=self.title).render(node)
319 infos = utils.attr_get(node, ['x','y','width','height'])
321 infos['y']+=infos['height']
323 w,h = flow.wrap(infos['width'], infos['height'])
324 if w<=infos['width'] and h<=infos['height']:
326 flow.drawOn(self.canvas,infos['x'],infos['y'])
329 raise ValueError, "Not enough space"
331 def _line_mode(self, node):
332 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
333 lcap = {'default':0, 'round':1, 'square':2}
335 if node.get('width'):
336 self.canvas.setLineWidth(utils.unit_get(node.get('width')))
338 self.canvas.setLineJoin(ljoin[node.get('join')])
340 self.canvas.setLineCap(lcap[node.get('cap')])
341 if node.get('miterLimit'):
342 self.canvas.setDash(utils.unit_get(node.get('miterLimit')))
344 dashes = node.get('dash').split(',')
345 for x in range(len(dashes)):
346 dashes[x]=utils.unit_get(dashes[x])
347 self.canvas.setDash(node.get('dash').split(','))
349 def _image(self, node):
351 from reportlab.lib.utils import ImageReader
352 if not node.get('file') :
354 image_data = self.images[node.get('name')]
355 s = cStringIO.StringIO(image_data)
358 if self.localcontext:
359 res = utils._regex.findall(node.text)
361 newtext = eval(key, {}, self.localcontext)
363 image_data = base64.decodestring(node.text)
364 if not image_data: return False
365 s = cStringIO.StringIO(image_data)
367 if node.get('file') in self.images:
368 s = cStringIO.StringIO(self.images[node.get('file')])
371 u = urllib.urlopen(str(node.get('file')))
372 s = cStringIO.StringIO(u.read())
374 u = file(os.path.join(self.path,str(node.get('file'))), 'rb')
375 s = cStringIO.StringIO(u.read())
377 (sx,sy) = img.getSize()
380 for tag in ('width','height','x','y'):
382 args[tag] = utils.unit_get(node.get(tag))
383 if ('width' in args) and (not 'height' in args):
384 args['height'] = sy * args['width'] / sx
385 elif ('height' in args) and (not 'width' in args):
386 args['width'] = sx * args['height'] / sy
387 elif ('width' in args) and ('height' in args):
388 if (float(args['width'])/args['height'])>(float(sx)>sy):
389 args['width'] = sx * args['height'] / sy
391 args['height'] = sy * args['width'] / sx
392 self.canvas.drawImage(img, **args)
394 def _path(self, node):
395 self.path = self.canvas.beginPath()
396 self.path.moveTo(**utils.attr_get(node, ['x','y']))
397 for n in utils._child_get(node, self):
400 vals = utils.text_get(n).split()
401 self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
402 elif n.tag=='curvesto':
403 vals = utils.text_get(n).split()
407 pos.append(utils.unit_get(vals.pop(0)))
408 self.path.curveTo(*pos)
410 data = n.text.split() # Not sure if I must merge all TEXT_NODE ?
412 x = utils.unit_get(data.pop(0))
413 y = utils.unit_get(data.pop(0))
414 self.path.lineTo(x,y)
415 if (not node.get('close')) or utils.bool_get(node.get('close')):
417 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
419 def setFont(self, node):
420 from reportlab.pdfbase import pdfmetrics
421 fname = node.get('name')
422 #TODO : other fonts should be supported
423 if fname not in pdfmetrics.standardFonts:
424 fname = self.canvas._fontname
425 return self.canvas.setFont(fname, utils.unit_get(node.get('size')))
427 def render(self, node):
429 'drawCentredString': self._drawCenteredString,
430 'drawRightString': self._drawRightString,
431 'drawString': self._drawString,
433 'ellipse': self._ellipse,
434 'lines': self._lines,
436 'curves': self._curves,
437 'fill': lambda node: self.canvas.setFillColor(color.get(node.get('color'))),
438 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.get('color'))),
439 'setFont': self.setFont ,
440 'place': self._place,
441 'circle': self._circle,
442 'lineMode': self._line_mode,
444 'rotate': lambda node: self.canvas.rotate(float(node.get('degrees'))),
445 'translate': self._translate,
448 for n in utils._child_get(node, self):
452 class _rml_draw(object):
453 def __init__(self, localcontext ,node, styles, images={}, path='.', title=None):
454 self.localcontext = localcontext
460 self.canvas_title = title
462 def render(self, canvas, doc):
464 cnv = _rml_canvas(canvas, self.localcontext, doc, self.styles, images=self.images, path=self.path, title=self.canvas_title)
465 cnv.render(self.node)
466 canvas.restoreState()
468 class _rml_flowable(object):
469 def __init__(self, doc, localcontext, images={}, path='.', title=None):
470 self.localcontext = localcontext
472 self.styles = doc.styles
477 def _textual(self, node):
478 rc1 = utils._process_text(self, node.text or '')
479 for n in utils._child_get(node,self):
480 txt_n = copy.deepcopy(n)
481 for key in txt_n.attrib.keys():
482 if key in ('rml_except', 'rml_loop', 'rml_tag'):
483 del txt_n.attrib[key]
484 if True or not self._textual(n).isspace():
485 if not n.tag == 'bullet':
486 txt_n.text = utils.xml2str(self._textual(n))
487 txt_n.tail = n.tail and utils._process_text(self, n.tail.replace('\n','')) or ''
488 rc1 += etree.tostring(txt_n)
491 def _table(self, node):
492 children = utils._child_get(node,self,'tr')
504 st = copy.deepcopy(self.styles.table_styles[tr.get('style')])
509 if tr.get('paraStyle'):
510 paraStyle = self.styles.styles[tr.get('paraStyle')]
513 for td in utils._child_get(tr, self,'td'):
515 st = copy.deepcopy(self.styles.table_styles[td.get('style')])
522 if td.get('paraStyle'):
524 paraStyle = self.styles.styles[td.get('paraStyle')]
528 for n in utils._child_get(td, self):
529 if n.tag == etree.Comment:
532 fl = self._flowable(n, extra_style=paraStyle)
533 if isinstance(fl,list):
539 flow = self._textual(td)
541 if len(data2)>length:
544 while len(ab)<length:
546 while len(data2)<length:
551 if node.get('colWidths'):
552 assert length == len(node.get('colWidths').split(','))
553 colwidths = [utils.unit_get(f.strip()) for f in node.get('colWidths').split(',')]
554 if node.get('rowHeights'):
555 rowheights = [utils.unit_get(f.strip()) for f in node.get('rowHeights').split(',')]
556 if len(rowheights) == 1:
557 rowheights = rowheights[0]
558 table = platypus.LongTable(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
559 if node.get('style'):
560 table.setStyle(self.styles.table_styles[node.get('style')])
565 def _illustration(self, node):
566 class Illustration(platypus.flowables.Flowable):
567 def __init__(self, node, localcontext, styles, self2):
568 self.localcontext = localcontext
571 self.width = utils.unit_get(node.get('width'))
572 self.height = utils.unit_get(node.get('height'))
574 def wrap(self, *args):
575 return (self.width, self.height)
578 drw = _rml_draw(self.localcontext ,self.node,self.styles, images=self.self2.images, path=self.self2.path, title=self.self2.title)
579 drw.render(self.canv, None)
580 return Illustration(node, self.localcontext, self.styles, self)
582 def _textual_image(self, node):
583 return base64.decodestring(node.text)
585 def _flowable(self, node, extra_style=None):
587 style = self.styles.para_style_get(node)
589 style.__dict__.update(extra_style)
591 for i in self._textual(node).split('\n'):
592 result.append(platypus.Paragraph(i, style, **(utils.attr_get(node, [], {'bulletText':'str'}))))
594 elif node.tag=='barCode':
596 from reportlab.graphics.barcode import code128
597 from reportlab.graphics.barcode import code39
598 from reportlab.graphics.barcode import code93
599 from reportlab.graphics.barcode import common
600 from reportlab.graphics.barcode import fourstate
601 from reportlab.graphics.barcode import usps
604 args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'int','quiet':'int','width':'unit','stop':'bool','bearers':'int','barWidth':'float','barHeight':'float'})
606 'codabar': lambda x: common.Codabar(x, **args),
607 'code11': lambda x: common.Code11(x, **args),
608 'code128': lambda x: code128.Code128(x, **args),
609 'standard39': lambda x: code39.Standard39(x, **args),
610 'standard93': lambda x: code93.Standard93(x, **args),
611 'i2of5': lambda x: common.I2of5(x, **args),
612 'extended39': lambda x: code39.Extended39(x, **args),
613 'extended93': lambda x: code93.Extended93(x, **args),
614 'msi': lambda x: common.MSI(x, **args),
615 'fim': lambda x: usps.FIM(x, **args),
616 'postnet': lambda x: usps.POSTNET(x, **args),
620 code = node.get('code').lower()
621 return codes[code](self._textual(node))
622 elif node.tag=='name':
623 self.styles.names[ node.get('id')] = node.get('value')
625 elif node.tag=='xpre':
626 style = self.styles.para_style_get(node)
627 return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
628 elif node.tag=='pre':
629 style = self.styles.para_style_get(node)
630 return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
631 elif node.tag=='illustration':
632 return self._illustration(node)
633 elif node.tag=='blockTable':
634 return self._table(node)
635 elif node.tag=='title':
636 styles = reportlab.lib.styles.getSampleStyleSheet()
637 style = styles['Title']
638 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
639 elif re.match('^h([1-9]+[0-9]*)$', (node.text or '')):
640 styles = reportlab.lib.styles.getSampleStyleSheet()
641 style = styles['Heading'+str(node.get[1:])]
642 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
643 elif node.tag=='image':
644 if not node.get('file'):
646 image_data = self.doc.images[node.get('name')].read()
649 if self.localcontext:
650 newtext = utils._process_text(self, node.text or '')
652 image_data = base64.decodestring(node.text)
653 if not image_data: return False
654 image = cStringIO.StringIO(image_data)
655 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
657 return platypus.Image(node.get('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
658 from reportlab.lib.utils import ImageReader
659 name = str(node.get('file'))
660 img = ImageReader(name)
661 (sx,sy) = img.getSize()
663 for tag in ('width','height'):
665 args[tag] = utils.unit_get(node.get(tag))
666 if ('width' in args) and (not 'height' in args):
667 args['height'] = sy * args['width'] / sx
668 elif ('height' in args) and (not 'width' in args):
669 args['width'] = sx * args['height'] / sy
670 elif ('width' in args) and ('height' in args):
671 if (float(args['width'])/args['height'])>(float(sx)>sy):
672 args['width'] = sx * args['height'] / sy
674 args['height'] = sy * args['width'] / sx
675 return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
676 elif node.tag=='spacer':
677 if node.get('width'):
678 width = utils.unit_get(node.get('width'))
680 width = utils.unit_get('1cm')
681 length = utils.unit_get(node.get('length'))
682 return platypus.Spacer(width=width, height=length)
683 elif node.tag=='section':
684 return self.render(node)
685 elif node.tag == 'pageNumberReset':
687 elif node.tag in ('pageBreak', 'nextPage'):
688 return platypus.PageBreak()
689 elif node.tag=='condPageBreak':
690 return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
691 elif node.tag=='setNextTemplate':
692 return platypus.NextPageTemplate(str(node.get('name')))
693 elif node.tag=='nextFrame':
694 return platypus.CondPageBreak(1000) # TODO: change the 1000 !
695 elif node.tag == 'setNextFrame':
696 from reportlab.platypus.doctemplate import NextFrameFlowable
697 return NextFrameFlowable(str(node.get('name')))
698 elif node.tag == 'currentFrame':
699 from reportlab.platypus.doctemplate import CurrentFrameFlowable
700 return CurrentFrameFlowable(str(node.get('name')))
701 elif node.tag == 'frameEnd':
702 return EndFrameFlowable()
703 elif node.tag == 'hr':
704 width_hr=node.get('width') or '100%'
705 color_hr=node.get('color') or 'black'
706 thickness_hr=node.get('thickness') or 1
707 lineCap_hr=node.get('lineCap') or 'round'
708 return platypus.flowables.HRFlowable(width=width_hr,color=color.get(color_hr),thickness=float(thickness_hr),lineCap=str(lineCap_hr))
710 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.tag,))
713 def render(self, node_story):
714 def process_story(node_story):
716 for node in utils._child_get(node_story, self):
717 if node.tag == etree.Comment:
720 flow = self._flowable(node)
722 if isinstance(flow,list):
723 sub_story = sub_story + flow
725 sub_story.append(flow)
727 return process_story(node_story)
730 class EndFrameFlowable(ActionFlowable):
731 def __init__(self,resume=0):
732 ActionFlowable.__init__(self,('frameEnd',resume))
734 class TinyDocTemplate(platypus.BaseDocTemplate):
735 def ___handle_pageBegin(self):
736 self.page = self.page + 1
737 self.pageTemplate.beforeDrawPage(self.canv,self)
738 self.pageTemplate.checkPageSize(self.canv,self)
739 self.pageTemplate.onPage(self.canv,self)
740 for f in self.pageTemplate.frames: f._reset()
742 self._curPageFlowableCount = 0
743 if hasattr(self,'_nextFrameIndex'):
744 del self._nextFrameIndex
745 for f in self.pageTemplate.frames:
749 self.handle_frameBegin()
750 def afterFlowable(self, flowable):
751 if isinstance(flowable, PageReset):
752 self.canv._pageNumber = 0
754 class _rml_template(object):
755 def __init__(self, localcontext, out, node, doc, images={}, path='.', title=None):
756 self.localcontext = localcontext
760 if not node.get('pageSize'):
761 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
763 ps = map(lambda x:x.strip(), node.get('pageSize').replace(')', '').replace('(', '').split(','))
764 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
766 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
767 self.page_templates = []
768 self.styles = doc.styles
770 pts = node.findall('pageTemplate')
773 for frame_el in pt.findall('frame'):
774 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
775 if utils.attr_get(frame_el, ['last']):
776 frame.lastFrame = True
777 frames.append( frame )
779 gr = pt.findall('pageGraphics')\
780 or pt[1].findall('pageGraphics')
784 drw = _rml_draw(self.localcontext,gr[0], self.doc, images=images, path=self.path, title=self.title)
785 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
787 drw = _rml_draw(self.localcontext,node,self.doc,title=self.title)
788 self.page_templates.append( platypus.PageTemplate(frames=frames,onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
789 self.doc_tmpl.addPageTemplates(self.page_templates)
791 def render(self, node_stories):
793 r = _rml_flowable(self.doc,self.localcontext, images=self.images, path=self.path, title=self.title)
795 for node_story in node_stories:
797 fis.append(platypus.PageBreak())
798 fis += r.render(node_story)
800 if self.localcontext:
801 fis.append(PageCount())
802 self.doc_tmpl.build(fis)
804 def parseNode(rml, localcontext = {},fout=None, images={}, path='.',title=None):
805 node = etree.XML(rml)
806 r = _rml_doc(node, localcontext, images, path, title=title)
807 #try to override some font mappings
809 from customfonts import SetCustomFonts
813 fp = cStringIO.StringIO()
817 def parseString(rml, localcontext = {},fout=None, images={}, path='.',title=None):
818 node = etree.XML(rml)
819 r = _rml_doc(node, localcontext, images, path, title=title)
821 #try to override some font mappings
823 from customfonts import SetCustomFonts
834 fp = cStringIO.StringIO()
839 print 'Usage: trml2pdf input.rml >output.pdf'
840 print 'Render the standard input (RML) and output a PDF file'
843 if __name__=="__main__":
845 if sys.argv[1]=='--help':
847 print parseString(file(sys.argv[1], 'r').read()),
849 print 'Usage: trml2pdf input.rml >output.pdf'
850 print 'Try \'trml2pdf --help\' for more information.'