1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# trml2pdf - An RML to PDF converter 5# Copyright (C) 2003, Fabien Pinckaers, UCL, FSA 6# Contributors 7# Richard Waid <richard@iopen.net> 8# 9# This library is free software; you can redistribute it and/or 10# modify it under the terms of the GNU Lesser General Public 11# License as published by the Free Software Foundation; either 12# version 2.1 of the License, or (at your option) any later version. 13# 14# This library is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17# Lesser General Public License for more details. 18# 19# You should have received a copy of the GNU Lesser General Public 20# License along with this library; if not, write to the Free Software 21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 23import sys 24import io 25import xml.dom.minidom 26import copy 27 28import reportlab 29from reportlab.pdfgen import canvas 30from reportlab import platypus 31 32from . import utils 33from . import color 34 35# 36# Change this to UTF-8 if you plan tu use Reportlab's UTF-8 support 37# 38encoding = 'latin1' 39 40def _child_get(node, childs): 41 clds = [] 42 for n in node.childNodes: 43 if (n.nodeType==n.ELEMENT_NODE) and (n.localName==childs): 44 clds.append(n) 45 return clds 46 47class _rml_styles(object): 48 def __init__(self, nodes): 49 self.styles = {} 50 self.names = {} 51 self.table_styles = {} 52 for node in nodes: 53 for style in node.getElementsByTagName('blockTableStyle'): 54 self.table_styles[style.getAttribute('id')] = self._table_style_get(style) 55 for style in node.getElementsByTagName('paraStyle'): 56 self.styles[style.getAttribute('name')] = self._para_style_get(style) 57 for variable in node.getElementsByTagName('initialize'): 58 for name in variable.getElementsByTagName('name'): 59 self.names[ name.getAttribute('id')] = name.getAttribute('value') 60 61 def _para_style_update(self, style, node): 62 for attr in ['textColor', 'backColor', 'bulletColor']: 63 if node.hasAttribute(attr): 64 style.__dict__[attr] = color.get(node.getAttribute(attr)) 65 for attr in ['fontName', 'bulletFontName', 'bulletText']: 66 if node.hasAttribute(attr): 67 style.__dict__[attr] = node.getAttribute(attr) 68 for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter', 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading']: 69 if node.hasAttribute(attr): 70 style.__dict__[attr] = utils.unit_get(node.getAttribute(attr)) 71 if node.hasAttribute('alignment'): 72 align = { 73 'right':reportlab.lib.enums.TA_RIGHT, 74 'center':reportlab.lib.enums.TA_CENTER, 75 'justify':reportlab.lib.enums.TA_JUSTIFY 76 } 77 style.alignment = align.get(node.getAttribute('alignment').lower(), reportlab.lib.enums.TA_LEFT) 78 return style 79 80 def _table_style_get(self, style_node): 81 styles = [] 82 for node in style_node.childNodes: 83 if node.nodeType==node.ELEMENT_NODE: 84 start = utils.tuple_int_get(node, 'start', (0,0) ) 85 stop = utils.tuple_int_get(node, 'stop', (-1,-1) ) 86 if node.localName=='blockValign': 87 styles.append(('VALIGN', start, stop, str(node.getAttribute('value')))) 88 elif node.localName=='blockFont': 89 styles.append(('FONT', start, stop, str(node.getAttribute('name')))) 90 elif node.localName=='blockTextColor': 91 styles.append(('TEXTCOLOR', start, stop, color.get(str(node.getAttribute('colorName'))))) 92 elif node.localName=='blockLeading': 93 styles.append(('LEADING', start, stop, utils.unit_get(node.getAttribute('length')))) 94 elif node.localName=='blockAlignment': 95 styles.append(('ALIGNMENT', start, stop, str(node.getAttribute('value')))) 96 elif node.localName=='blockLeftPadding': 97 styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.getAttribute('length')))) 98 elif node.localName=='blockRightPadding': 99 styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.getAttribute('length')))) 100 elif node.localName=='blockTopPadding': 101 styles.append(('TOPPADDING', start, stop, utils.unit_get(node.getAttribute('length')))) 102 elif node.localName=='blockBottomPadding': 103 styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.getAttribute('length')))) 104 elif node.localName=='blockBackground': 105 styles.append(('BACKGROUND', start, stop, color.get(node.getAttribute('colorName')))) 106 if node.hasAttribute('size'): 107 styles.append(('FONTSIZE', start, stop, utils.unit_get(node.getAttribute('size')))) 108 elif node.localName=='lineStyle': 109 kind = node.getAttribute('kind') 110 kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ] 111 assert kind in kind_list 112 thick = 1 113 if node.hasAttribute('thickness'): 114 thick = float(node.getAttribute('thickness')) 115 styles.append((kind, start, stop, thick, color.get(node.getAttribute('colorName')))) 116 return platypus.tables.TableStyle(styles) 117 118 def _para_style_get(self, node): 119 styles = reportlab.lib.styles.getSampleStyleSheet() 120 style = copy.deepcopy(styles["Normal"]) 121 self._para_style_update(style, node) 122 return style 123 124 def para_style_get(self, node): 125 style = False 126 if node.hasAttribute('style'): 127 if node.getAttribute('style') in self.styles: 128 style = copy.deepcopy(self.styles[node.getAttribute('style')]) 129 else: 130 sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.getAttribute('style'),) ) 131 if not style: 132 styles = reportlab.lib.styles.getSampleStyleSheet() 133 style = copy.deepcopy(styles['Normal']) 134 return self._para_style_update(style, node) 135 136class _rml_doc(object): 137 def __init__(self, data): 138 self.dom = xml.dom.minidom.parseString(data) 139 self.filename = self.dom.documentElement.getAttribute('filename') 140 141 def docinit(self, els): 142 from reportlab.lib.fonts import addMapping 143 from reportlab.pdfbase import pdfmetrics 144 from reportlab.pdfbase.ttfonts import TTFont 145 146 for node in els: 147 for font in node.getElementsByTagName('registerFont'): 148 name = font.getAttribute('fontName').encode('ascii') 149 fname = font.getAttribute('fontFile').encode('ascii') 150 pdfmetrics.registerFont(TTFont(name, fname )) 151 addMapping(name, 0, 0, name) #normal 152 addMapping(name, 0, 1, name) #italic 153 addMapping(name, 1, 0, name) #bold 154 addMapping(name, 1, 1, name) #italic and bold 155 156 def render(self, out): 157 el = self.dom.documentElement.getElementsByTagName('docinit') 158 if el: 159 self.docinit(el) 160 161 el = self.dom.documentElement.getElementsByTagName('stylesheet') 162 self.styles = _rml_styles(el) 163 164 el = self.dom.documentElement.getElementsByTagName('template') 165 if len(el): 166 pt_obj = _rml_template(out, el[0], self) 167 pt_obj.render(self.dom.documentElement.getElementsByTagName('story')[0]) 168 else: 169 self.canvas = canvas.Canvas(out) 170 pd = self.dom.documentElement.getElementsByTagName('pageDrawing')[0] 171 pd_obj = _rml_canvas(self.canvas, None, self) 172 pd_obj.render(pd) 173 self.canvas.showPage() 174 self.canvas.save() 175 176class _rml_canvas(object): 177 def __init__(self, canvas, doc_tmpl=None, doc=None): 178 self.canvas = canvas 179 self.styles = doc.styles 180 self.doc_tmpl = doc_tmpl 181 self.doc = doc 182 183 def _textual(self, node): 184 rc = '' 185 for n in node.childNodes: 186 if n.nodeType == n.ELEMENT_NODE: 187 if n.localName=='pageNumber': 188 rc += str(self.canvas.getPageNumber()) 189 elif (n.nodeType == node.CDATA_SECTION_NODE): 190 rc += n.data 191 elif (n.nodeType == node.TEXT_NODE): 192 rc += n.data 193 return rc.encode(encoding) 194 195 def _drawString(self, node): 196 self.canvas.drawString(text=self._textual(node), **utils.attr_get(node, ['x','y'])) 197 def _drawCenteredString(self, node): 198 self.canvas.drawCentredString(text=self._textual(node), **utils.attr_get(node, ['x','y'])) 199 def _drawRightString(self, node): 200 self.canvas.drawRightString(text=self._textual(node), **utils.attr_get(node, ['x','y'])) 201 def _rect(self, node): 202 if node.hasAttribute('round'): 203 self.canvas.roundRect(radius=utils.unit_get(node.getAttribute('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'})) 204 else: 205 self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'})) 206 def _ellipse(self, node): 207 x1 = utils.unit_get(node.getAttribute('x')) 208 x2 = utils.unit_get(node.getAttribute('width')) 209 y1 = utils.unit_get(node.getAttribute('y')) 210 y2 = utils.unit_get(node.getAttribute('height')) 211 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'})) 212 def _curves(self, node): 213 line_str = utils.text_get(node).split() 214 lines = [] 215 while len(line_str)>7: 216 self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]]) 217 line_str = line_str[8:] 218 def _lines(self, node): 219 line_str = utils.text_get(node).split() 220 lines = [] 221 while len(line_str)>3: 222 lines.append([utils.unit_get(l) for l in line_str[0:4]]) 223 line_str = line_str[4:] 224 self.canvas.lines(lines) 225 def _grid(self, node): 226 xlist = [utils.unit_get(s) for s in node.getAttribute('xs').split(',')] 227 ylist = [utils.unit_get(s) for s in node.getAttribute('ys').split(',')] 228 self.canvas.grid(xlist, ylist) 229 def _translate(self, node): 230 dx = 0 231 dy = 0 232 if node.hasAttribute('dx'): 233 dx = utils.unit_get(node.getAttribute('dx')) 234 if node.hasAttribute('dy'): 235 dy = utils.unit_get(node.getAttribute('dy')) 236 self.canvas.translate(dx,dy) 237 238 def _circle(self, node): 239 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'})) 240 241 def _place(self, node): 242 flows = _rml_flowable(self.doc).render(node) 243 infos = utils.attr_get(node, ['x','y','width','height']) 244 245 infos['y']+=infos['height'] 246 for flow in flows: 247 w,h = flow.wrap(infos['width'], infos['height']) 248 if w<=infos['width'] and h<=infos['height']: 249 infos['y']-=h 250 flow.drawOn(self.canvas,infos['x'],infos['y']) 251 infos['height']-=h 252 else: 253 raise ValueError("Not enough space") 254 255 def _line_mode(self, node): 256 ljoin = {'round':1, 'mitered':0, 'bevelled':2} 257 lcap = {'default':0, 'round':1, 'square':2} 258 if node.hasAttribute('width'): 259 self.canvas.setLineWidth(utils.unit_get(node.getAttribute('width'))) 260 if node.hasAttribute('join'): 261 self.canvas.setLineJoin(ljoin[node.getAttribute('join')]) 262 if node.hasAttribute('cap'): 263 self.canvas.setLineCap(lcap[node.getAttribute('cap')]) 264 if node.hasAttribute('miterLimit'): 265 self.canvas.setDash(utils.unit_get(node.getAttribute('miterLimit'))) 266 if node.hasAttribute('dash'): 267 dashes = node.getAttribute('dash').split(',') 268 for x in range(len(dashes)): 269 dashes[x]=utils.unit_get(dashes[x]) 270 self.canvas.setDash(node.getAttribute('dash').split(',')) 271 272 def _image(self, node): 273 import urllib.request, urllib.parse, urllib.error 274 from reportlab.lib.utils import ImageReader 275 u = urllib.request.urlopen(str(node.getAttribute('file'))) 276 s = io.StringIO() 277 s.write(u.read()) 278 s.seek(0) 279 img = ImageReader(s) 280 (sx,sy) = img.getSize() 281 282 args = {} 283 for tag in ('width','height','x','y'): 284 if node.hasAttribute(tag): 285 args[tag] = utils.unit_get(node.getAttribute(tag)) 286 if ('width' in args) and (not 'height' in args): 287 args['height'] = sy * args['width'] / sx 288 elif ('height' in args) and (not 'width' in args): 289 args['width'] = sx * args['height'] / sy 290 elif ('width' in args) and ('height' in args): 291 if (float(args['width'])/args['height'])>(float(sx)>sy): 292 args['width'] = sx * args['height'] / sy 293 else: 294 args['height'] = sy * args['width'] / sx 295 self.canvas.drawImage(img, **args) 296 297 def _path(self, node): 298 self.path = self.canvas.beginPath() 299 self.path.moveTo(**utils.attr_get(node, ['x','y'])) 300 for n in node.childNodes: 301 if n.nodeType == node.ELEMENT_NODE: 302 if n.localName=='moveto': 303 vals = utils.text_get(n).split() 304 self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1])) 305 elif n.localName=='curvesto': 306 vals = utils.text_get(n).split() 307 while len(vals)>5: 308 pos=[] 309 while len(pos)<6: 310 pos.append(utils.unit_get(vals.pop(0))) 311 self.path.curveTo(*pos) 312 elif (n.nodeType == node.TEXT_NODE): 313 data = n.data.split() # Not sure if I must merge all TEXT_NODE ? 314 while len(data)>1: 315 x = utils.unit_get(data.pop(0)) 316 y = utils.unit_get(data.pop(0)) 317 self.path.lineTo(x,y) 318 if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')): 319 self.path.close() 320 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'})) 321 322 def render(self, node): 323 tags = { 324 'drawCentredString': self._drawCenteredString, 325 'drawRightString': self._drawRightString, 326 'drawString': self._drawString, 327 'rect': self._rect, 328 'ellipse': self._ellipse, 329 'lines': self._lines, 330 'grid': self._grid, 331 'curves': self._curves, 332 'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))), 333 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))), 334 'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))), 335 'place': self._place, 336 'circle': self._circle, 337 'lineMode': self._line_mode, 338 'path': self._path, 339 'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))), 340 'translate': self._translate, 341 'image': self._image 342 } 343 for nd in node.childNodes: 344 if nd.nodeType==nd.ELEMENT_NODE: 345 for tag in tags: 346 if nd.localName==tag: 347 tags[tag](nd) 348 break 349 350class _rml_draw(object): 351 def __init__(self, node, styles): 352 self.node = node 353 self.styles = styles 354 self.canvas = None 355 356 def render(self, canvas, doc): 357 canvas.saveState() 358 cnv = _rml_canvas(canvas, doc, self.styles) 359 cnv.render(self.node) 360 canvas.restoreState() 361 362class _rml_flowable(object): 363 def __init__(self, doc): 364 self.doc = doc 365 self.styles = doc.styles 366 367 def _textual(self, node): 368 rc = '' 369 for n in node.childNodes: 370 if n.nodeType == node.ELEMENT_NODE: 371 if n.localName=='getName': 372 newNode = self.doc.dom.createTextNode(self.styles.names.get(n.getAttribute('id'),'Unknown name')) 373 node.insertBefore(newNode, n) 374 node.removeChild(n) 375 if n.localName=='pageNumber': 376 rc+='<pageNumber/>' # TODO: change this ! 377 else: 378 self._textual(n) 379 rc += n.toxml() 380 elif (n.nodeType == node.CDATA_SECTION_NODE): 381 rc += n.data 382 elif (n.nodeType == node.TEXT_NODE): 383 rc += n.data 384 return rc.encode(encoding) 385 386 def _table(self, node): 387 length = 0 388 colwidths = None 389 rowheights = None 390 data = [] 391 for tr in _child_get(node,'tr'): 392 data2 = [] 393 for td in _child_get(tr, 'td'): 394 flow = [] 395 for n in td.childNodes: 396 if n.nodeType==node.ELEMENT_NODE: 397 flow.append( self._flowable(n) ) 398 if not len(flow): 399 flow = self._textual(td) 400 data2.append( flow ) 401 if len(data2)>length: 402 length=len(data2) 403 for ab in data: 404 while len(ab)<length: 405 ab.append('') 406 while len(data2)<length: 407 data2.append('') 408 data.append( data2 ) 409 if node.hasAttribute('colWidths'): 410 assert length == len(node.getAttribute('colWidths').split(',')) 411 colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')] 412 if node.hasAttribute('rowHeights'): 413 rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')] 414 table = platypus.Table(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'}))) 415 if node.hasAttribute('style'): 416 table.setStyle(self.styles.table_styles[node.getAttribute('style')]) 417 return table 418 419 def _illustration(self, node): 420 class Illustration(platypus.flowables.Flowable): 421 def __init__(self, node, styles): 422 self.node = node 423 self.styles = styles 424 self.width = utils.unit_get(node.getAttribute('width')) 425 self.height = utils.unit_get(node.getAttribute('height')) 426 def wrap(self, *args): 427 return (self.width, self.height) 428 def draw(self): 429 canvas = self.canv 430 drw = _rml_draw(self.node, self.styles) 431 drw.render(self.canv, None) 432 return Illustration(node, self.styles) 433 434 def _flowable(self, node): 435 if node.localName=='para': 436 style = self.styles.para_style_get(node) 437 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'}))) 438 elif node.localName=='name': 439 self.styles.names[ node.getAttribute('id')] = node.getAttribute('value') 440 return None 441 elif node.localName=='xpre': 442 style = self.styles.para_style_get(node) 443 return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'}))) 444 elif node.localName=='pre': 445 style = self.styles.para_style_get(node) 446 return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'}))) 447 elif node.localName=='illustration': 448 return self._illustration(node) 449 elif node.localName=='blockTable': 450 return self._table(node) 451 elif node.localName=='title': 452 styles = reportlab.lib.styles.getSampleStyleSheet() 453 style = styles['Title'] 454 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'}))) 455 elif node.localName=='h1': 456 styles = reportlab.lib.styles.getSampleStyleSheet() 457 style = styles['Heading1'] 458 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'}))) 459 elif node.localName=='h2': 460 styles = reportlab.lib.styles.getSampleStyleSheet() 461 style = styles['Heading2'] 462 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'}))) 463 elif node.localName=='h3': 464 styles = reportlab.lib.styles.getSampleStyleSheet() 465 style = styles['Heading3'] 466 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'}))) 467 elif node.localName=='image': 468 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height']))) 469 elif node.localName=='spacer': 470 if node.hasAttribute('width'): 471 width = utils.unit_get(node.getAttribute('width')) 472 else: 473 width = utils.unit_get('1cm') 474 length = utils.unit_get(node.getAttribute('length')) 475 return platypus.Spacer(width=width, height=length) 476 elif node.localName=='pageBreak': 477 return platypus.PageBreak() 478 elif node.localName=='condPageBreak': 479 return platypus.CondPageBreak(**(utils.attr_get(node, ['height']))) 480 elif node.localName=='setNextTemplate': 481 return platypus.NextPageTemplate(str(node.getAttribute('name'))) 482 elif node.localName=='nextFrame': 483 return platypus.CondPageBreak(1000) # TODO: change the 1000 ! 484 else: 485 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,)) 486 return None 487 488 def render(self, node_story): 489 story = [] 490 node = node_story.firstChild 491 while node: 492 if node.nodeType == node.ELEMENT_NODE: 493 flow = self._flowable(node) 494 if flow: 495 story.append(flow) 496 node = node.nextSibling 497 return story 498 499class _rml_template(object): 500 def __init__(self, out, node, doc): 501 if not node.hasAttribute('pageSize'): 502 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm')) 503 else: 504 ps = [x.strip() for x in node.getAttribute('pageSize').replace(')', '').replace('(', '').split(',')] 505 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) ) 506 cm = reportlab.lib.units.cm 507 self.doc_tmpl = platypus.BaseDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'})) 508 self.page_templates = [] 509 self.styles = doc.styles 510 self.doc = doc 511 pts = node.getElementsByTagName('pageTemplate') 512 for pt in pts: 513 frames = [] 514 for frame_el in pt.getElementsByTagName('frame'): 515 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'text', 'showBoundary':'bool'})) ) 516 frames.append( frame ) 517 gr = pt.getElementsByTagName('pageGraphics') 518 if len(gr): 519 drw = _rml_draw(gr[0], self.doc) 520 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) )) 521 else: 522 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) )) 523 self.doc_tmpl.addPageTemplates(self.page_templates) 524 525 def render(self, node_story): 526 r = _rml_flowable(self.doc) 527 fis = r.render(node_story) 528 self.doc_tmpl.build(fis) 529 530def parseString(data, fout=None): 531 r = _rml_doc(data) 532 if fout: 533 fp = file(fout,'wb') 534 r.render(fp) 535 fp.close() 536 return fout 537 else: 538 fp = io.StringIO() 539 r.render(fp) 540 return fp.getvalue() 541 542def trml2pdf_help(): 543 print('Usage: trml2pdf input.rml >output.pdf') 544 print('Render the standard input (RML) and output a PDF file') 545 sys.exit(0) 546 547if __name__=="__main__": 548 if len(sys.argv)>1: 549 if sys.argv[1]=='--help': 550 trml2pdf_help() 551 print(parseString(file(sys.argv[1], 'r').read()), end=' ') 552 else: 553 print('Usage: trml2pdf input.rml >output.pdf') 554 print('Try \'trml2pdf --help\' for more information.') 555