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">&nbsp;</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