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# 7# This library is free software; you can redistribute it and/or 8# modify it under the terms of the GNU Lesser General Public 9# License as published by the Free Software Foundation; either 10# version 2.1 of the License, or (at your option) any later version. 11# 12# This library 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 GNU 15# Lesser General Public License for more details. 16# 17# You should have received a copy of the GNU Lesser General Public 18# License along with this library; if not, write to the Free Software 19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 21import sys 22import io 23import xml.dom.minidom 24import copy 25 26from . import utils 27 28class _flowable(object): 29 def __init__(self, template, doc): 30 self._tags = { 31 'title': self._tag_title, 32 'spacer': self._tag_spacer, 33 'para': self._tag_para, 34 'nextFrame': self._tag_next_frame, 35 'blockTable': self._tag_table, 36 'pageBreak': self._tag_page_break, 37 'setNextTemplate': self._tag_next_template, 38 } 39 self.template = template 40 self.doc = doc 41 42 def _tag_page_break(self, node): 43 return '<br/>'*3 44 45 def _tag_next_template(self, node): 46 return '' 47 48 def _tag_next_frame(self, node): 49 result=self.template.frame_stop() 50 result+='<br/>' 51 result+=self.template.frame_start() 52 return result 53 54 def _tag_title(self, node): 55 node.tagName='h1' 56 return node.toxml() 57 58 def _tag_spacer(self, node): 59 length = 1+int(utils.unit_get(node.getAttribute('length')))/35 60 return "<br/>"*length 61 62 def _tag_table(self, node): 63 node.tagName='table' 64 if node.hasAttribute('colWidths'): 65 sizes = [utils.unit_get(x) for x in node.getAttribute('colWidths').split(',')] 66 tr = self.doc.createElement('tr') 67 for s in sizes: 68 td = self.doc.createElement('td') 69 td.setAttribute("width", str(s)) 70 tr.appendChild(td) 71 node.appendChild(tr) 72 return node.toxml() 73 74 def _tag_para(self, node): 75 node.tagName='p' 76 if node.hasAttribute('style'): 77 node.setAttribute('class', node.getAttribute('style')) 78 return node.toxml() 79 80 def render(self, node): 81 result = self.template.start() 82 result += self.template.frame_start() 83 for n in node.childNodes: 84 if n.nodeType==node.ELEMENT_NODE: 85 if n.localName in self._tags: 86 result += self._tags[n.localName](n) 87 else: 88 pass 89 #print 'tag', n.localName, 'not yet implemented!' 90 result += self.template.frame_stop() 91 result += self.template.end() 92 return result 93 94class _rml_tmpl_tag(object): 95 def __init__(self, *args): 96 pass 97 def tag_start(self): 98 return '' 99 def tag_end(self): 100 return False 101 def tag_stop(self): 102 return '' 103 104class _rml_tmpl_frame(_rml_tmpl_tag): 105 def __init__(self, posx, width): 106 self.width = width 107 self.posx = posx 108 def tag_start(self): 109 return '<table border="0" width="%d"><tr><td width="%d"> </td><td>' % (self.width+self.posx,self.posx) 110 def tag_end(self): 111 return True 112 def tag_stop(self): 113 return '</td></tr></table><br/>' 114 115class _rml_tmpl_draw_string(_rml_tmpl_tag): 116 def __init__(self, node, style): 117 self.posx = utils.unit_get(node.getAttribute('x')) 118 self.posy = utils.unit_get(node.getAttribute('y')) 119 aligns = { 120 'drawString': 'left', 121 'drawRightString': 'right', 122 'drawCentredString': 'center' 123 } 124 align = aligns[node.localName] 125 self.pos = [(self.posx, self.posy, align, utils.text_get(node), style.get('td'), style.font_size_get('td'))] 126 127 def tag_start(self): 128 self.pos.sort() 129 res = '<table border="0" cellpadding="0" cellspacing="0"><tr>' 130 posx = 0 131 i = 0 132 for (x,y,align,txt, style, fs) in self.pos: 133 if align=="left": 134 pos2 = len(txt)*fs 135 res+='<td width="%d"></td><td style="%s" width="%d">%s</td>' % (x - posx, style, pos2, txt) 136 posx = x+pos2 137 if align=="right": 138 res+='<td width="%d" align="right" style="%s">%s</td>' % (x - posx, style, txt) 139 posx = x 140 if align=="center": 141 res+='<td width="%d" align="center" style="%s">%s</td>' % ((x - posx)*2, style, txt) 142 posx = 2*x-posx 143 i+=1 144 res+='</tr></table>' 145 return res 146 def merge(self, ds): 147 self.pos+=ds.pos 148 149class _rml_tmpl_draw_lines(_rml_tmpl_tag): 150 def __init__(self, node, style): 151 coord = [utils.unit_get(x) for x in utils.text_get(node).split(' ')] 152 self.ok = False 153 self.posx = coord[0] 154 self.posy = coord[1] 155 self.width = coord[2]-coord[0] 156 self.ok = coord[1]==coord[3] 157 self.style = style 158 self.style = style.get('hr') 159 160 def tag_start(self): 161 if self.ok: 162 return '<table border="0" cellpadding="0" cellspacing="0" width="%d"><tr><td width="%d"></td><td><hr width="100%%" style="margin:0px; %s"></td></tr></table>' % (self.posx+self.width,self.posx,self.style) 163 else: 164 return '' 165 166class _rml_stylesheet(object): 167 def __init__(self, stylesheet, doc): 168 self.doc = doc 169 self.attrs = {} 170 self._tags = { 171 'fontSize': lambda x: ('font-size',str(utils.unit_get(x))+'px'), 172 'alignment': lambda x: ('text-align',str(x)) 173 } 174 result = '' 175 for ps in stylesheet.getElementsByTagName('paraStyle'): 176 attr = {} 177 attrs = ps.attributes 178 for i in range(attrs.length): 179 name = attrs.item(i).localName 180 attr[name] = ps.getAttribute(name) 181 attrs = [] 182 for a in attr: 183 if a in self._tags: 184 attrs.append("%s:%s" % self._tags[a](attr[a])) 185 if len(attrs): 186 result += "p."+attr['name']+" {"+'; '.join(attrs)+"}\n" 187 self.result = result 188 189 def render(self): 190 return self.result 191 192class _rml_draw_style(object): 193 def __init__(self): 194 self.style = {} 195 self._styles = { 196 'fill': lambda x: {'td': {'color':x.getAttribute('color')}}, 197 'setFont': lambda x: {'td': {'font-size':x.getAttribute('size')+'px'}}, 198 'stroke': lambda x: {'hr': {'color':x.getAttribute('color')}}, 199 } 200 def update(self, node): 201 if node.localName in self._styles: 202 result = self._styles[node.localName](node) 203 for key in result: 204 if key in self.style: 205 self.style[key].update(result[key]) 206 else: 207 self.style[key] = result[key] 208 def font_size_get(self,tag): 209 size = utils.unit_get(self.style.get('td', {}).get('font-size','16')) 210 return size 211 212 def get(self,tag): 213 if not tag in self.style: 214 return "" 215 return ';'.join(['%s:%s' % (x[0],x[1]) for x in list(self.style[tag].items())]) 216 217class _rml_template(object): 218 def __init__(self, template): 219 self.frame_pos = -1 220 self.frames = [] 221 self.template_order = [] 222 self.page_template = {} 223 self.loop = 0 224 self._tags = { 225 'drawString': _rml_tmpl_draw_string, 226 'drawRightString': _rml_tmpl_draw_string, 227 'drawCentredString': _rml_tmpl_draw_string, 228 'lines': _rml_tmpl_draw_lines 229 } 230 self.style = _rml_draw_style() 231 for pt in template.getElementsByTagName('pageTemplate'): 232 frames = {} 233 id = pt.getAttribute('id') 234 self.template_order.append(id) 235 for tmpl in pt.getElementsByTagName('frame'): 236 posy = int(utils.unit_get(tmpl.getAttribute('y1'))) #+utils.unit_get(tmpl.getAttribute('height'))) 237 posx = int(utils.unit_get(tmpl.getAttribute('x1'))) 238 frames[(posy,posx,tmpl.getAttribute('id'))] = _rml_tmpl_frame(posx, utils.unit_get(tmpl.getAttribute('width'))) 239 for tmpl in template.getElementsByTagName('pageGraphics'): 240 for n in tmpl.childNodes: 241 if n.nodeType==n.ELEMENT_NODE: 242 if n.localName in self._tags: 243 t = self._tags[n.localName](n, self.style) 244 frames[(t.posy,t.posx,n.localName)] = t 245 else: 246 self.style.update(n) 247 keys = list(frames.keys()) 248 keys.sort() 249 keys.reverse() 250 self.page_template[id] = [] 251 for key in range(len(keys)): 252 if key>0 and keys[key-1][0] == keys[key][0]: 253 if type(self.page_template[id][-1]) == type(frames[keys[key]]): 254 self.page_template[id][-1].merge(frames[keys[key]]) 255 continue 256 self.page_template[id].append(frames[keys[key]]) 257 self.template = self.template_order[0] 258 259 def _get_style(self): 260 return self.style 261 262 def set_next_template(self): 263 self.template = self.template_order[(self.template_order.index(name)+1) % self.template_order] 264 self.frame_pos = -1 265 266 def set_template(self, name): 267 self.template = name 268 self.frame_pos = -1 269 270 def frame_start(self): 271 result = '' 272 frames = self.page_template[self.template] 273 ok = True 274 while ok: 275 self.frame_pos += 1 276 if self.frame_pos>=len(frames): 277 self.frame_pos=0 278 self.loop=1 279 ok = False 280 continue 281 f = frames[self.frame_pos] 282 result+=f.tag_start() 283 ok = not f.tag_end() 284 if ok: 285 result+=f.tag_stop() 286 return result 287 288 def frame_stop(self): 289 frames = self.page_template[self.template] 290 f = frames[self.frame_pos] 291 result=f.tag_stop() 292 return result 293 294 def start(self): 295 return '' 296 297 def end(self): 298 result = '' 299 while not self.loop: 300 result += self.frame_start() 301 result += self.frame_stop() 302 return result 303 304class _rml_doc(object): 305 def __init__(self, data): 306 self.dom = xml.dom.minidom.parseString(data) 307 self.filename = self.dom.documentElement.getAttribute('filename') 308 self.result = '' 309 310 def render(self, out): 311 self.result += '''<!DOCTYPE HTML PUBLIC "-//w3c//DTD HTML 4.0 Frameset//EN"> 312<html> 313<head> 314 <style type="text/css"> 315 p {margin:0px; font-size:12px;} 316 td {font-size:14px;} 317''' 318 style = self.dom.documentElement.getElementsByTagName('stylesheet')[0] 319 s = _rml_stylesheet(style, self.dom) 320 self.result += s.render() 321 self.result+=''' 322 </style> 323</head> 324<body>''' 325 326 template = _rml_template(self.dom.documentElement.getElementsByTagName('template')[0]) 327 f = _flowable(template, self.dom) 328 self.result += f.render(self.dom.documentElement.getElementsByTagName('story')[0]) 329 del f 330 self.result += '</body></html>' 331 out.write( self.result) 332 333def parseString(data, fout=None): 334 r = _rml_doc(data) 335 if fout: 336 fp = file(fout,'wb') 337 r.render(fp) 338 fp.close() 339 return fout 340 else: 341 fp = io.StringIO() 342 r.render(fp) 343 return fp.getvalue() 344 345def trml2pdf_help(): 346 print('Usage: trml2pdf input.rml >output.html') 347 print('Render the standard input (RML) and output a PDF file') 348 sys.exit(0) 349 350if __name__=="__main__": 351 if len(sys.argv)>1: 352 if sys.argv[1]=='--help': 353 trml2pdf_help() 354 print(parseString(file(sys.argv[1], 'r').read()), end=' ') 355 else: 356 print('Usage: trml2pdf input.rml >output.pdf') 357 print('Try \'trml2pdf --help\' for more information.') 358