Browse Source

commit web_printscreen_zb

sebas 8 years ago
commit
515cef2031

+ 27 - 0
__init__.py

@@ -0,0 +1,27 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Copyright (c) 2013 ZestyBeanz Technologies Pvt. Ltd.
+#    (http://wwww.zbeanztech.com)
+#    contact@zbeanztech.com
+#    prajul@zbeanztech.com
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import trml2pdf
+import controllers
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

BIN
__init__.pyc


+ 41 - 0
__openerp__.py

@@ -0,0 +1,41 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Copyright (c) 2013 ZestyBeanz Technologies Pvt. Ltd.
+#    (http://wwww.zbeanztech.com)
+#    contact@zbeanztech.com
+#    prajul@zbeanztech.com
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    'name': 'Web Printscreen ZB',
+    'version': '1.0',
+    'category': 'Web',
+    'description': """
+        Module to export current active tree view in to excel report
+    """,
+    'author': 'Zesty Beanz Technologies',
+    'website': 'http://www.zbeanztech.com',
+    'depends': ['web'],
+    'data': ['views/web_printscreen_zb.xml'],
+    'qweb': ['static/src/xml/web_printscreen_export.xml'],
+    'installable': True,
+    'auto_install': False,
+    'web_preload': False,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

+ 185 - 0
controllers.py

@@ -0,0 +1,185 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Copyright (c) 2013 ZestyBeanz Technologies Pvt. Ltd.
+#    (http://wwww.zbeanztech.com)
+#    contact@zbeanztech.com
+#    prajul@zbeanztech.com
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+import openerp.addons.web.http as openerpweb
+from openerp.addons.web.controllers.main import ExcelExport
+from openerp.addons.web.controllers.main import Export
+import re
+from cStringIO import StringIO
+from lxml  import etree
+import trml2pdf
+import time, os
+import locale
+import openerp.tools as tools
+try:
+    import xlwt
+except ImportError:
+    xlwt = None
+
+class ZbExcelExport(ExcelExport):
+    _cp_path = '/web/export/zb_excel_export'
+
+    def from_data(self, fields, rows):
+        workbook = xlwt.Workbook()
+        worksheet = workbook.add_sheet('Sheet 1')
+        style = xlwt.easyxf('align: wrap yes')
+        font = xlwt.Font()
+        font.bold = True
+        style.font = font
+        ignore_index = []
+        count = 0
+        for i, fieldname in enumerate(fields):
+            if fieldname.get('header_data_id', False):
+                field_name = fieldname.get('header_name', '')
+                worksheet.write(0, i-count, field_name, style)
+                worksheet.col(i).width = 8000
+            else:
+                count += 1
+                ignore_index.append(i)
+        style = xlwt.easyxf('align: wrap yes')
+        bold_style = xlwt.easyxf('align: wrap yes')
+        font = xlwt.Font()
+        font.bold = True
+        bold_style.font = font
+        for row_index, row in enumerate(rows):
+            count = 0
+            for cell_index, cell_value in enumerate(row):
+                if cell_index not in ignore_index:
+                    cell_style = style
+                    if cell_value.get('bold', False):
+                        cell_style = bold_style
+                    cellvalue = cell_value.get('data', '')
+                    if isinstance(cellvalue, basestring):
+                        cellvalue = re.sub("\r", " ", cellvalue)
+                    if cell_value.get('number', False) and cellvalue:
+                        cellvalue = float(cellvalue)
+                    if cellvalue is False: cellvalue = None
+                    worksheet.write(row_index + 1, cell_index - count, cellvalue, cell_style)
+                else:
+                    count += 1
+        fp = StringIO()
+        workbook.save(fp)
+        fp.seek(0)
+        data = fp.read()
+        fp.close()
+        return data
+    
+    @openerpweb.httprequest
+    def index(self, req, data, token):
+        data = json.loads(data)
+        return req.make_response(
+            self.from_data(data.get('headers', []), data.get('rows', [])),
+                           headers=[
+                                    ('Content-Disposition', 'attachment; filename="%s"'
+                                        % data.get('model', 'Export.xls')),
+                                    ('Content-Type', self.content_type)
+                                    ],
+                                 cookies={'fileToken': token}
+                                 )
+
+class ExportPdf(Export):
+    _cp_path = '/web/export/zb_pdf'
+    fmt = {
+        'tag': 'pdf',
+        'label': 'PDF',
+        'error': None
+    }
+    
+    @property
+    def content_type(self):
+        return 'application/pdf'
+    
+    def filename(self, base):
+        return base + '.pdf'
+    
+    def from_data(self, uid, fields, rows, company_name):
+        pageSize=[210.0,297.0]
+        new_doc = etree.Element("report")
+        config = etree.SubElement(new_doc, 'config')
+        def _append_node(name, text):
+            n = etree.SubElement(config, name)
+            n.text = text
+        _append_node('date', time.strftime(str(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y'))))
+        _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
+        _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
+        _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
+        _append_node('PageFormat', 'a4')
+        _append_node('header-date', time.strftime(str(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y'))))
+        _append_node('company', company_name)
+        l = []
+        t = 0
+        temp = []
+        tsum = []
+        skip_index = []
+        header = etree.SubElement(new_doc, 'header')
+        i = 0
+        for f in fields:
+            if f.get('header_data_id', False):
+                value = f.get('header_name', "")
+                field = etree.SubElement(header, 'field')
+                field.text = tools.ustr(value)
+            else:
+                skip_index.append(i)
+            i += 1
+        lines = etree.SubElement(new_doc, 'lines')
+        for row_lines in rows:
+            node_line = etree.SubElement(lines, 'row')
+            j = 0
+            for row in row_lines:
+                if not j in skip_index:
+                    para = "yes"
+                    tree = "no"
+                    value = row.get('data', '')
+                    if row.get('bold', False):
+                        para = "group"
+                    if row.get('number', False):
+                        tree = "float"
+                    col = etree.SubElement(node_line, 'col', para=para, tree=tree)
+                    col.text = tools.ustr(value)
+                j += 1
+        transform = etree.XSLT(
+            etree.parse(os.path.join(tools.config['root_path'],
+                                     'addons/base/report/custom_new.xsl')))
+        rml = etree.tostring(transform(new_doc))
+        self.obj = trml2pdf.parseNode(rml, title='Printscreen')
+        return self.obj
+
+class ZbPdfExport(ExportPdf):
+    _cp_path = '/web/export/zb_pdf_export'
+    
+    @openerpweb.httprequest
+    def index(self, req, data, token):
+        data = json.loads(data)
+        uid = data.get('uid', False)
+        return req.make_response(self.from_data(uid, data.get('headers', []), data.get('rows', []),
+                                                data.get('company_name','')),
+                                 headers=[('Content-Disposition',
+                                           'attachment; filename=PDF Export'),
+                                          ('Content-Type', self.content_type)],
+                                 cookies={'fileToken': token})
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

BIN
controllers.pyc


+ 185 - 0
controllers.py~

@@ -0,0 +1,185 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Copyright (c) 2013 ZestyBeanz Technologies Pvt. Ltd.
+#    (http://wwww.zbeanztech.com)
+#    contact@zbeanztech.com
+#    prajul@zbeanztech.com
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+import openerp.addons.web.http as openerpweb
+from openerp.addons.web.controllers.main import ExcelExport
+from openerp.addons.web.controllers.main import Export
+import re
+from cStringIO import StringIO
+from lxml  import etree
+import trml2pdf
+import time, os
+import locale
+import openerp.tools as tools
+try:
+    import xlwt
+except ImportError:
+    xlwt = None
+
+class ZbExcelExport(ExcelExport):
+    _cp_path = '/web/export/zb_excel_export'
+
+    def from_data(self, fields, rows):
+        workbook = xlwt.Workbook()
+        worksheet = workbook.add_sheet('Sheet 1')
+        style = xlwt.easyxf('align: wrap yes')
+        font = xlwt.Font()
+        font.bold = True
+        style.font = font
+        ignore_index = []
+        count = 0
+        for i, fieldname in enumerate(fields):
+            if fieldname.get('header_data_id', False):
+                field_name = fieldname.get('header_name', '')
+                worksheet.write(0, i-count, field_name, style)
+                worksheet.col(i).width = 8000
+            else:
+                count += 1
+                ignore_index.append(i)
+        style = xlwt.easyxf('align: wrap yes')
+        bold_style = xlwt.easyxf('align: wrap yes')
+        font = xlwt.Font()
+        font.bold = True
+        bold_style.font = font
+        for row_index, row in enumerate(rows):
+            count = 0
+            for cell_index, cell_value in enumerate(row):
+                if cell_index not in ignore_index:
+                    cell_style = style
+                    if cell_value.get('bold', False):
+                        cell_style = bold_style
+                    cellvalue = cell_value.get('data', '')
+                    if isinstance(cellvalue, basestring):
+                        cellvalue = re.sub("\r", " ", cellvalue)
+                    if cell_value.get('number', False) and cellvalue:
+                        cellvalue = float(cellvalue)
+                    if cellvalue is False: cellvalue = None
+                    worksheet.write(row_index + 1, cell_index - count, cellvalue, cell_style)
+                else:
+                    count += 1
+        fp = StringIO()
+        workbook.save(fp)
+        fp.seek(0)
+        data = fp.read()
+        fp.close()
+        return data
+    
+    @openerpweb.httprequest
+    def index(self, req, data, token):
+        data = json.loads(data)
+        return req.make_response(
+            self.from_data(data.get('headers', []), data.get('rows', [])),
+                           headers=[
+                                    ('Content-Disposition', 'attachment; filename="%s"'
+                                        % data.get('model', 'Export.xls')),
+                                    ('Content-Type', self.content_type)
+                                    ],
+                                 cookies={'fileToken': token}
+                                 )
+
+class ExportPdf(Export):
+    _cp_path = '/web/export/zb_pdf'
+    fmt = {
+        'tag': 'pdf',
+        'label': 'PDF',
+        'error': None
+    }
+    
+    @property
+    def content_type(self):
+        return 'application/pdf'
+    
+    def filename(self, base):
+        return base + '.pdf'
+    
+    def from_data(self, uid, fields, rows, company_name):
+        pageSize=[210.0,297.0]
+        new_doc = etree.Element("report")
+        config = etree.SubElement(new_doc, 'config')
+        def _append_node(name, text):
+            n = etree.SubElement(config, name)
+            n.text = text
+        _append_node('date', time.strftime(str(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y'))))
+        _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
+        _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
+        _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
+        _append_node('PageFormat', 'a4')
+        _append_node('header-date', time.strftime(str(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y'))))
+        _append_node('company', company_name)
+        l = []
+        t = 0
+        temp = []
+        tsum = []
+        skip_index = []
+        header = etree.SubElement(new_doc, 'header')
+        i = 0
+        for f in fields:
+            if f.get('header_data_id', False):
+                value = f.get('header_name', "")
+                field = etree.SubElement(header, 'field')
+                field.text = tools.ustr(value)
+            else:
+                skip_index.append(i)
+            i += 1
+        lines = etree.SubElement(new_doc, 'lines')
+        for row_lines in rows:
+            node_line = etree.SubElement(lines, 'row')
+            j = 0
+            for row in row_lines:
+                if not j in skip_index:
+                    para = "yes"
+                    tree = "no"
+                    value = row.get('data', '')
+                    if row.get('bold', False):
+                        para = "group"
+                    if row.get('number', False):
+                        tree = "float"
+                    col = etree.SubElement(node_line, 'col', para=para, tree=tree)
+                    col.text = tools.ustr(value)
+                j += 1
+        transform = etree.XSLT(
+            etree.parse(os.path.join(tools.config['root_path'],
+                                     'addons/base/report/custom_new.xsl')))
+        rml = etree.tostring(transform(new_doc))
+        self.obj = trml2pdf.parseNode(rml, title='Printscreen')
+        return self.obj
+
+class ZbPdfExport(ExportPdf):
+    _cp_path = '/web/export/zb_pdf_export'
+    
+    @openerpweb.httprequest
+    def index(self, req, data, token):
+        data = json.loads(data)
+        uid = data.get('uid', False)
+        return req.make_response(self.from_data(uid, data.get('headers', []), data.get('rows', []),
+                                                data.get('company_name','')),
+                                 headers=[('Content-Disposition',
+                                           'attachment; filename=PDF Export'),
+                                          ('Content-Type', self.content_type)],
+                                 cookies={'fileToken': int(token)})
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

+ 122 - 0
static/src/js/web_printscreen_export.js

@@ -0,0 +1,122 @@
+openerp.web_printscreen_zb = function(instance, m) {
+    
+    var _t = instance.web._t;
+    var QWeb = instance.web.qweb;
+    
+    instance.web.ListView.include({
+        load_list: function () {
+            var self = this;
+            this._super.apply(this, arguments);
+            self.$pager.find(".oe_list_button_import_excel").unbind('click').click(function(event){self.export_to_excel("excel")})
+            self.$pager.find(".oe_list_button_import_pdf").unbind('click').click(function(event){self.export_to_excel("pdf")})
+        },
+        export_to_excel: function(export_type) {
+            var self = this
+            var export_type = export_type
+            view = this.getParent()
+            // Find Header Element
+            header_eles = self.$el.find('.oe_list_header_columns')
+            header_name_list = []
+            $.each(header_eles,function(){
+                $header_ele = $(this)
+                header_td_elements = $header_ele.find('th')
+                $.each(header_td_elements,function(){
+                    $header_td = $(this)
+                    text = $header_td.text().trim() || ""
+                    data_id = $header_td.attr('data-id')
+                    if (text && !data_id){
+                        data_id = 'group_name'
+                    }
+                    header_name_list.push({'header_name': text.trim(), 'header_data_id': data_id})
+                   // }
+                });
+            });
+            
+            //Find Data Element
+            data_eles = self.$el.find('.oe_list_content > tbody > tr')
+            export_data = []
+            $.each(data_eles,function(){
+                data = []
+                $data_ele = $(this)
+                is_analysis = false
+                if ($data_ele.text().trim()){
+                //Find group name
+	                group_th_eles = $data_ele.find('th')
+	                $.each(group_th_eles,function(){
+	                    $group_th_ele = $(this)
+	                    text = $group_th_ele.text().trim() || ""
+	                    is_analysis = true
+	                    data.push({'data': text, 'bold': true})
+	                });
+	                data_td_eles = $data_ele.find('td')
+	                $.each(data_td_eles,function(){
+	                    $data_td_ele = $(this)
+	                    text = $data_td_ele.text().trim() || ""
+	                    if ($data_td_ele && $data_td_ele[0].classList.contains('oe_number') && !$data_td_ele[0].classList.contains('oe_list_field_float_time')){
+	                        text = text.replace('%', '')
+	                        text = instance.web.parse_value(text, { type:"float" })
+	                        data.push({'data': text || "", 'number': true})
+	                    }
+	                    else{
+	                        data.push({'data': text})
+	                    }
+	                });
+	                export_data.push(data)
+                }
+            });
+            
+            //Find Footer Element
+            
+            footer_eles = self.$el.find('.oe_list_content > tfoot> tr')
+            $.each(footer_eles,function(){
+                data = []
+                $footer_ele = $(this)
+                footer_td_eles = $footer_ele.find('td')
+                $.each(footer_td_eles,function(){
+                    $footer_td_ele = $(this)
+                    text = $footer_td_ele.text().trim() || ""
+                    if ($footer_td_ele && $footer_td_ele[0].classList.contains('oe_number')){
+                        text = instance.web.parse_value(text, { type:"float" })
+                        data.push({'data': text || "", 'bold': true, 'number': true})
+                    }
+                    else{
+                        data.push({'data': text, 'bold': true})
+                    }
+                });
+                export_data.push(data)
+            });
+            
+            //Export to excel
+            $.blockUI();
+            if (export_type === 'excel'){
+                 view.session.get_file({
+                     url: '/web/export/zb_excel_export',
+                     data: {data: JSON.stringify({
+                            model : view.model,
+                            headers : header_name_list,
+                            rows : export_data,
+                     })},
+                     complete: $.unblockUI
+                 });
+             }
+             else{
+                console.log(view)
+                new instance.web.Model("res.users").get_func("read")(this.session.uid, ["company_id"]).then(function(res) {
+                    new instance.web.Model("res.company").get_func("read")(res['company_id'][0], ["name"]).then(function(result) {
+                        view.session.get_file({
+                             url: '/web/export/zb_pdf_export',
+                             data: {data: JSON.stringify({
+                                    uid: view.session.uid,
+                                    model : view.model,
+                                    headers : header_name_list,
+                                    rows : export_data,
+                                    company_name: result['name']
+                             })},
+                             complete: $.unblockUI
+                         });
+                    });
+                });
+             }
+        },
+    });
+};

+ 16 - 0
static/src/xml/web_printscreen_export.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+<t t-extend="ViewPager">
+    <t t-jquery=".oe_pager_value" t-operation="prepend">
+        <a t-if="(widget.ViewManager &amp;&amp; widget.ViewManager.active_view) === 'list'" 
+                    class="oe_bold oe_list_button_import_pdf"
+                    id="button_export_pdf"
+                    href="#">PDF</a>
+                    <label t-if="(widget.ViewManager &amp;&amp; widget.ViewManager.active_view) === 'list'">or</label>
+        <a t-if="(widget.ViewManager &amp;&amp; widget.ViewManager.active_view) === 'list'" 
+                    class="oe_bold oe_list_button_import_excel"
+                    id="button_export_excel"
+                    href="#">Excel</a>
+    </t>
+</t>
+</templates>

+ 1044 - 0
trml2pdf.py

@@ -0,0 +1,1044 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Copyright (c) 2013 ZestyBeanz Technologies Pvt. Ltd.
+#    (http://wwww.zbeanztech.com)
+#    contact@zbeanztech.com
+#    prajul@zbeanztech.com
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+import sys
+import copy
+import reportlab
+import re
+from reportlab.pdfgen import canvas
+from reportlab import platypus
+from openerp.report.render.rml2pdf import utils
+from openerp.report.render.rml2pdf import color
+import os
+import logging
+from lxml import etree
+import base64
+from reportlab.platypus.doctemplate import ActionFlowable
+from openerp.tools.safe_eval import safe_eval as eval
+from reportlab.lib.units import inch,cm,mm
+from openerp.tools.misc import file_open
+from reportlab.pdfbase import pdfmetrics
+from reportlab.lib.pagesizes import A4, letter
+
+try:
+    from cStringIO import StringIO
+    _hush_pyflakes = [ StringIO ]
+except ImportError:
+    from StringIO import StringIO
+
+_logger = logging.getLogger(__name__)
+
+encoding = 'utf-8'
+
+def _open_image(filename, path=None):
+    """Attempt to open a binary file and return the descriptor
+    """
+    if os.path.isfile(filename):
+        return open(filename, 'rb')
+    for p in (path or []):
+        if p and os.path.isabs(p):
+            fullpath = os.path.join(p, filename)
+            if os.path.isfile(fullpath):
+                return open(fullpath, 'rb')
+        try:
+            if p:
+                fullpath = os.path.join(p, filename)
+            else:
+                fullpath = filename
+            return file_open(fullpath)
+        except IOError:
+            pass
+    raise IOError("File %s cannot be found in image path" % filename)
+
+class NumberedCanvas(canvas.Canvas):
+    def __init__(self, *args, **kwargs):
+        canvas.Canvas.__init__(self, *args, **kwargs)
+        self._codes = []
+        self._flag=False
+        self._pageCount=0
+        self._currentPage =0
+        self._pageCounter=0
+        self.pages={}
+
+    def showPage(self):
+        self._currentPage +=1
+        if not self._flag:
+            self._pageCount += 1
+        else:
+            self.pages.update({self._currentPage:self._pageCount})
+        self._codes.append({'code': self._code, 'stack': self._codeStack})
+        self._startPage()
+        self._flag=False
+
+    def pageCount(self):
+        if self.pages.get(self._pageCounter,False):
+            self._pageNumber=0
+        self._pageCounter +=1
+        key=self._pageCounter
+        if not self.pages.get(key,False):
+            while not self.pages.get(key,False):
+                key += 1
+        self.setFont("Helvetica", 8)
+        self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40),
+            " %(this)i / %(total)i" % {
+               'this': self._pageNumber+1,
+               'total': self.pages.get(key,False),
+            }
+        )
+
+    def save(self):
+        """add page info to each page (page x of y)"""
+        # reset page counter
+        self._pageNumber = 0
+        for code in self._codes:
+            self._code = code['code']
+            self._codeStack = code['stack']
+            self.pageCount()
+            canvas.Canvas.showPage(self)
+#        self.restoreState()
+        self._doc.SaveToFile(self._filename, self)
+
+class PageCount(platypus.Flowable):
+    def __init__(self, story_count=0):
+        platypus.Flowable.__init__(self)
+        self.story_count = story_count
+
+    def draw(self):
+        self.canv.beginForm("pageCount%d" % self.story_count)
+        self.canv.setFont("Helvetica", utils.unit_get(str(8)))
+        self.canv.drawString(0, 0, str(self.canv.getPageNumber()))
+        self.canv.endForm()
+
+class PageReset(platypus.Flowable):
+    def draw(self):
+        self.canv._doPageReset = True
+class PageReset(platypus.Flowable):
+    def draw(self):
+        self.canv._doPageReset = True
+
+class _rml_styles(object,):
+    def __init__(self, nodes, localcontext):
+        self.localcontext = localcontext
+        self.styles = {}
+        self.styles_obj = {}
+        self.names = {}
+        self.table_styles = {}
+        self.default_style = reportlab.lib.styles.getSampleStyleSheet()
+
+        for node in nodes:
+            for style in node.findall('blockTableStyle'):
+                self.table_styles[style.get('id')] = self._table_style_get(style)
+            for style in node.findall('paraStyle'):
+                sname = style.get('name')
+                self.styles[sname] = self._para_style_update(style)
+
+                self.styles_obj[sname] = reportlab.lib.styles.ParagraphStyle(sname, self.default_style["Normal"], **self.styles[sname])
+
+            for variable in node.findall('initialize'):
+                for name in variable.findall('name'):
+                    self.names[ name.get('id')] = name.get('value')
+
+    def _para_style_update(self, node):
+        data = {}
+        for attr in ['textColor', 'backColor', 'bulletColor', 'borderColor']:
+            if node.get(attr):
+                data[attr] = color.get(node.get(attr))
+        for attr in ['fontName', 'bulletFontName', 'bulletText']:
+            if node.get(attr):
+                data[attr] = node.get(attr)
+        for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter',
+            'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading',
+            'borderWidth','borderPadding','borderRadius']:
+            if node.get(attr):
+                data[attr] = utils.unit_get(node.get(attr))
+        if node.get('alignment'):
+            align = {
+                'right':reportlab.lib.enums.TA_RIGHT,
+                'center':reportlab.lib.enums.TA_CENTER,
+                'justify':reportlab.lib.enums.TA_JUSTIFY
+            }
+            data['alignment'] = align.get(node.get('alignment').lower(), reportlab.lib.enums.TA_LEFT)
+        return data
+
+    def _table_style_get(self, style_node):
+        styles = []
+        for node in style_node:
+            start = utils.tuple_int_get(node, 'start', (0,0) )
+            stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
+            if node.tag=='blockValign':
+                styles.append(('VALIGN', start, stop, str(node.get('value'))))
+            elif node.tag=='blockFont':
+                styles.append(('FONT', start, stop, str(node.get('name'))))
+            elif node.tag=='blockTextColor':
+                styles.append(('TEXTCOLOR', start, stop, color.get(str(node.get('colorName')))))
+            elif node.tag=='blockLeading':
+                styles.append(('LEADING', start, stop, utils.unit_get(node.get('length'))))
+            elif node.tag=='blockAlignment':
+                styles.append(('ALIGNMENT', start, stop, str(node.get('value'))))
+            elif node.tag=='blockSpan':
+                styles.append(('SPAN', start, stop))
+            elif node.tag=='blockLeftPadding':
+                styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.get('length'))))
+            elif node.tag=='blockRightPadding':
+                styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.get('length'))))
+            elif node.tag=='blockTopPadding':
+                styles.append(('TOPPADDING', start, stop, utils.unit_get(node.get('length'))))
+            elif node.tag=='blockBottomPadding':
+                styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.get('length'))))
+            elif node.tag=='blockBackground':
+                styles.append(('BACKGROUND', start, stop, color.get(node.get('colorName'))))
+            if node.get('size'):
+                styles.append(('FONTSIZE', start, stop, utils.unit_get(node.get('size'))))
+            elif node.tag=='lineStyle':
+                kind = node.get('kind')
+                kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
+                assert kind in kind_list
+                thick = 1
+                if node.get('thickness'):
+                    thick = float(node.get('thickness'))
+                styles.append((kind, start, stop, thick, color.get(node.get('colorName'))))
+        return platypus.tables.TableStyle(styles)
+
+    def para_style_get(self, node):
+        style = False
+        sname = node.get('style')
+        if sname:
+            if sname in self.styles_obj:
+                style = self.styles_obj[sname]
+            else:
+                _logger.warning('Warning: style not found, %s - setting default!\n' % (node.get('style'),) )
+        if not style:
+            style = self.default_style['Normal']
+        para_update = self._para_style_update(node)
+        if para_update:
+            # update style only is necessary
+            style = copy.deepcopy(style)
+            style.__dict__.update(para_update)
+        return style
+
+class _rml_doc(object):
+    def __init__(self, node, localcontext=None, images=None, path='.', title=None):
+        if images is None:
+            images = {}
+        if localcontext is None:
+            localcontext = {}
+        self.localcontext = localcontext
+        self.etree = node
+        self.filename = self.etree.get('filename')
+        self.images = images
+        self.path = path
+        self.title = title
+
+    def docinit(self, els):
+        from reportlab.lib.fonts import addMapping
+        from reportlab.pdfbase import pdfmetrics
+        from reportlab.pdfbase.ttfonts import TTFont
+
+        for node in els:
+            for font in node.findall('registerFont'):
+                name = font.get('fontName').encode('ascii')
+                fname = font.get('fontFile').encode('ascii')
+                if name not in pdfmetrics._fonts:
+                    pdfmetrics.registerFont(TTFont(name, fname))
+                addMapping(name, 0, 0, name)    #normal
+                addMapping(name, 0, 1, name)    #italic
+                addMapping(name, 1, 0, name)    #bold
+                addMapping(name, 1, 1, name)    #italic and bold
+
+    def setTTFontMapping(self,face, fontname, filename, mode='all'):
+        from reportlab.lib.fonts import addMapping
+        from reportlab.pdfbase import pdfmetrics
+        from reportlab.pdfbase.ttfonts import TTFont
+
+        if fontname not in pdfmetrics._fonts:
+            pdfmetrics.registerFont(TTFont(fontname, filename))
+        if mode == 'all':
+            addMapping(face, 0, 0, fontname)    #normal
+            addMapping(face, 0, 1, fontname)    #italic
+            addMapping(face, 1, 0, fontname)    #bold
+            addMapping(face, 1, 1, fontname)    #italic and bold
+        elif (mode== 'normal') or (mode == 'regular'):
+            addMapping(face, 0, 0, fontname)    #normal
+        elif mode == 'italic':
+            addMapping(face, 0, 1, fontname)    #italic
+        elif mode == 'bold':
+            addMapping(face, 1, 0, fontname)    #bold
+        elif mode == 'bolditalic':
+            addMapping(face, 1, 1, fontname)    #italic and bold
+
+    def _textual_image(self, node):
+        rc = ''
+        for n in node:
+            rc +=( etree.tostring(n) or '') + n.tail
+        return base64.decodestring(node.tostring())
+
+    def _images(self, el):
+        result = {}
+        for node in el.findall('.//image'):
+            rc =( node.text or '')
+            result[node.get('name')] = base64.decodestring(rc)
+        return result
+
+    def render(self, out):
+        el = self.etree.findall('.//docinit')
+        if el:
+            self.docinit(el)
+
+        el = self.etree.findall('.//stylesheet')
+        self.styles = _rml_styles(el,self.localcontext)
+
+        el = self.etree.findall('.//images')
+        if el:
+            self.images.update( self._images(el[0]) )
+
+        el = self.etree.findall('.//template')
+        if len(el):
+            pt_obj = _rml_template(self.localcontext, out, el[0], self, images=self.images, path=self.path, title=self.title)
+            el = utils._child_get(self.etree, self, 'story')
+            pt_obj.render(el)
+        else:
+            self.canvas = canvas.Canvas(out)
+            pd = self.etree.find('pageDrawing')[0]
+            pd_obj = _rml_canvas(self.canvas, self.localcontext, None, self, self.images, path=self.path, title=self.title)
+            pd_obj.render(pd)
+
+            self.canvas.showPage()
+            self.canvas.save()
+
+class _rml_canvas(object):
+    def __init__(self, canvas, localcontext, doc_tmpl=None, doc=None, images=None, path='.', title=None):
+        if images is None:
+            images = {}
+        self.localcontext = localcontext
+        self.canvas = canvas
+        self.styles = doc.styles
+        self.doc_tmpl = doc_tmpl
+        self.doc = doc
+        self.images = images
+        self.path = path
+        self.title = title
+        if self.title:
+            self.canvas.setTitle(self.title)
+
+    def _textual(self, node, x=0, y=0):
+        text = node.text and node.text.encode('utf-8') or ''
+        rc = utils._process_text(self, text)
+        for n in node:
+            if n.tag == 'seq':
+                from reportlab.lib.sequencer import getSequencer
+                seq = getSequencer()
+                rc += str(seq.next(n.get('id')))
+            if n.tag == 'pageCount':
+                if x or y:
+                    self.canvas.translate(x,y)
+                self.canvas.doForm('pageCount%s' % (self.canvas._storyCount,))
+                if x or y:
+                    self.canvas.translate(-x,-y)
+            if n.tag == 'pageNumber':
+                rc += str(self.canvas.getPageNumber())
+            rc += utils._process_text(self, n.tail)
+        return rc.replace('\n','')
+
+    def _drawString(self, node):
+        v = utils.attr_get(node, ['x','y'])
+        text=self._textual(node, **v)
+        text = utils.xml2str(text)
+        self.canvas.drawString(text=text, **v)
+
+    def _drawCenteredString(self, node):
+        v = utils.attr_get(node, ['x','y'])
+        text=self._textual(node, **v)
+        text = utils.xml2str(text)
+        self.canvas.drawCentredString(text=text, **v)
+
+    def _drawRightString(self, node):
+        v = utils.attr_get(node, ['x','y'])
+        text=self._textual(node, **v)
+        text = utils.xml2str(text)
+        self.canvas.drawRightString(text=text, **v)
+
+    def _rect(self, node):
+        if node.get('round'):
+            self.canvas.roundRect(radius=utils.unit_get(node.get('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
+        else:
+            self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
+
+    def _ellipse(self, node):
+        x1 = utils.unit_get(node.get('x'))
+        x2 = utils.unit_get(node.get('width'))
+        y1 = utils.unit_get(node.get('y'))
+        y2 = utils.unit_get(node.get('height'))
+
+        self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
+
+    def _curves(self, node):
+        line_str = node.text.split()
+        lines = []
+        while len(line_str)>7:
+            self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
+            line_str = line_str[8:]
+
+    def _lines(self, node):
+        line_str = node.text.split()
+        lines = []
+        while len(line_str)>3:
+            lines.append([utils.unit_get(l) for l in line_str[0:4]])
+            line_str = line_str[4:]
+        self.canvas.lines(lines)
+
+    def _grid(self, node):
+        xlist = [utils.unit_get(s) for s in node.get('xs').split(',')]
+        ylist = [utils.unit_get(s) for s in node.get('ys').split(',')]
+
+        self.canvas.grid(xlist, ylist)
+
+    def _translate(self, node):
+        dx = utils.unit_get(node.get('dx')) or 0
+        dy = utils.unit_get(node.get('dy')) or 0
+        self.canvas.translate(dx,dy)
+
+    def _circle(self, node):
+        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'}))
+
+    def _place(self, node):
+        flows = _rml_flowable(self.doc, self.localcontext, images=self.images, path=self.path, title=self.title).render(node)
+        infos = utils.attr_get(node, ['x','y','width','height'])
+
+        infos['y']+=infos['height']
+        for flow in flows:
+            w,h = flow.wrap(infos['width'], infos['height'])
+            if w<=infos['width'] and h<=infos['height']:
+                infos['y']-=h
+                flow.drawOn(self.canvas,infos['x'],infos['y'])
+                infos['height']-=h
+            else:
+                raise ValueError("Not enough space")
+
+    def _line_mode(self, node):
+        ljoin = {'round':1, 'mitered':0, 'bevelled':2}
+        lcap = {'default':0, 'round':1, 'square':2}
+
+        if node.get('width'):
+            self.canvas.setLineWidth(utils.unit_get(node.get('width')))
+        if node.get('join'):
+            self.canvas.setLineJoin(ljoin[node.get('join')])
+        if node.get('cap'):
+            self.canvas.setLineCap(lcap[node.get('cap')])
+        if node.get('miterLimit'):
+            self.canvas.setDash(utils.unit_get(node.get('miterLimit')))
+        if node.get('dash'):
+            dashes = node.get('dash').split(',')
+            for x in range(len(dashes)):
+                dashes[x]=utils.unit_get(dashes[x])
+            self.canvas.setDash(node.get('dash').split(','))
+
+    def _image(self, node):
+        import urllib
+        import urlparse
+        from reportlab.lib.utils import ImageReader
+        nfile = node.get('file')
+        if not nfile:
+            if node.get('name'):
+                image_data = self.images[node.get('name')]
+                _logger.debug("Image %s used", node.get('name'))
+                s = StringIO(image_data)
+            else:
+                newtext = node.text
+                if self.localcontext:
+                    res = utils._regex.findall(newtext)
+                    for key in res:
+                        newtext = eval(key, {}, self.localcontext) or ''
+                image_data = None
+                if newtext:
+                    image_data = base64.decodestring(newtext)
+                if image_data:
+                    s = StringIO(image_data)
+                else:
+                    _logger.debug("No image data!")
+                    return False
+        else:
+            if nfile in self.images:
+                s = StringIO(self.images[nfile])
+            else:
+                try:
+                    up = urlparse.urlparse(str(nfile))
+                except ValueError:
+                    up = False
+                if up and up.scheme:
+                    # RFC: do we really want to open external URLs?
+                    # Are we safe from cross-site scripting or attacks?
+                    _logger.debug("Retrieve image from %s", nfile)
+                    u = urllib.urlopen(str(nfile))
+                    s = StringIO(u.read())
+                else:
+                    _logger.debug("Open image file %s ", nfile)
+                    s = _open_image(nfile, path=self.path)
+        try:
+            img = ImageReader(s)
+            (sx,sy) = img.getSize()
+            _logger.debug("Image is %dx%d", sx, sy)
+            args = { 'x': 0.0, 'y': 0.0, 'mask': 'auto'}
+            for tag in ('width','height','x','y'):
+                if node.get(tag):
+                    args[tag] = utils.unit_get(node.get(tag))
+            if ('width' in args) and (not 'height' in args):
+                args['height'] = sy * args['width'] / sx
+            elif ('height' in args) and (not 'width' in args):
+                args['width'] = sx * args['height'] / sy
+            elif ('width' in args) and ('height' in args):
+                if (float(args['width'])/args['height'])>(float(sx)>sy):
+                    args['width'] = sx * args['height'] / sy
+                else:
+                    args['height'] = sy * args['width'] / sx
+            self.canvas.drawImage(img, **args)
+        finally:
+            s.close()
+#        self.canvas._doc.SaveToFile(self.canvas._filename, self.canvas)
+
+    def _path(self, node):
+        self.path = self.canvas.beginPath()
+        self.path.moveTo(**utils.attr_get(node, ['x','y']))
+        for n in utils._child_get(node, self):
+            if not n.text :
+                if n.tag=='moveto':
+                    vals = utils.text_get(n).split()
+                    self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
+                elif n.tag=='curvesto':
+                    vals = utils.text_get(n).split()
+                    while len(vals)>5:
+                        pos=[]
+                        while len(pos)<6:
+                            pos.append(utils.unit_get(vals.pop(0)))
+                        self.path.curveTo(*pos)
+            elif n.text:
+                data = n.text.split()               # Not sure if I must merge all TEXT_NODE ?
+                while len(data)>1:
+                    x = utils.unit_get(data.pop(0))
+                    y = utils.unit_get(data.pop(0))
+                    self.path.lineTo(x,y)
+        if (not node.get('close')) or utils.bool_get(node.get('close')):
+            self.path.close()
+        self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
+
+    def setFont(self, node):
+        fontname = node.get('name')
+        if fontname not in pdfmetrics.getRegisteredFontNames()\
+             or fontname not in pdfmetrics.standardFonts:
+                # let reportlab attempt to find it
+                try:
+                    pdfmetrics.getFont(fontname)
+                except Exception:
+                    _logger.debug('Could not locate font %s, substituting default: %s',
+                                 fontname,
+                                 self.canvas._fontname)
+                    fontname = self.canvas._fontname
+        return self.canvas.setFont(fontname, utils.unit_get(node.get('size')))
+
+    def render(self, node):
+        tags = {
+            'drawCentredString': self._drawCenteredString,
+            'drawRightString': self._drawRightString,
+            'drawString': self._drawString,
+            'rect': self._rect,
+            'ellipse': self._ellipse,
+            'lines': self._lines,
+            'grid': self._grid,
+            'curves': self._curves,
+            'fill': lambda node: self.canvas.setFillColor(color.get(node.get('color'))),
+            'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.get('color'))),
+            'setFont': self.setFont ,
+            'place': self._place,
+            'circle': self._circle,
+            'lineMode': self._line_mode,
+            'path': self._path,
+            'rotate': lambda node: self.canvas.rotate(float(node.get('degrees'))),
+            'translate': self._translate,
+            'image': self._image
+        }
+        for n in utils._child_get(node, self):
+            if n.tag in tags:
+                tags[n.tag](n)
+
+class _rml_draw(object):
+    def __init__(self, localcontext, node, styles, images=None, path='.', title=None):
+        if images is None:
+            images = {}
+        self.localcontext = localcontext
+        self.node = node
+        self.styles = styles
+        self.canvas = None
+        self.images = images
+        self.path = path
+        self.canvas_title = title
+
+    def render(self, canvas, doc):
+        canvas.saveState()
+        cnv = _rml_canvas(canvas, self.localcontext, doc, self.styles, images=self.images, path=self.path, title=self.canvas_title)
+        cnv.render(self.node)
+        canvas.restoreState()
+
+class _rml_Illustration(platypus.flowables.Flowable):
+    def __init__(self, node, localcontext, styles, self2):
+        self.localcontext = (localcontext or {}).copy()
+        self.node = node
+        self.styles = styles
+        self.width = utils.unit_get(node.get('width'))
+        self.height = utils.unit_get(node.get('height'))
+        self.self2 = self2
+    def wrap(self, *args):
+        return self.width, self.height
+    def draw(self):
+        drw = _rml_draw(self.localcontext ,self.node,self.styles, images=self.self2.images, path=self.self2.path, title=self.self2.title)
+        drw.render(self.canv, None)
+
+class _rml_flowable(object):
+    def __init__(self, doc, localcontext, images=None, path='.', title=None):
+        if images is None:
+            images = {}
+        self.localcontext = localcontext
+        self.doc = doc
+        self.styles = doc.styles
+        self.images = images
+        self.path = path
+        self.title = title
+
+    def _textual(self, node):
+        rc1 = utils._process_text(self, node.text or '')
+        for n in utils._child_get(node,self):
+            txt_n = copy.deepcopy(n)
+            for key in txt_n.attrib.keys():
+                if key in ('rml_except', 'rml_loop', 'rml_tag'):
+                    del txt_n.attrib[key]
+            if not n.tag == 'bullet':
+                txt_n.text = utils.xml2str(self._textual(n))
+            txt_n.tail = n.tail and utils.xml2str(utils._process_text(self, n.tail.replace('\n',''))) or ''
+            rc1 += etree.tostring(txt_n)
+        return rc1
+
+    def _table(self, node):
+        children = utils._child_get(node,self,'tr')
+        if not children:
+            return None
+        length = 0
+        colwidths = None
+        rowheights = None
+        data = []
+        styles = []
+        posy = 0
+        for tr in children:
+            paraStyle = None
+            if tr.get('style'):
+                st = copy.deepcopy(self.styles.table_styles[tr.get('style')])
+                for si in range(len(st._cmds)):
+                    s = list(st._cmds[si])
+                    s[1] = (s[1][0],posy)
+                    s[2] = (s[2][0],posy)
+                    st._cmds[si] = tuple(s)
+                styles.append(st)
+            if tr.get('paraStyle'):
+                paraStyle = self.styles.styles[tr.get('paraStyle')]
+            data2 = []
+            posx = 0
+            for td in utils._child_get(tr, self,'td'):
+                if td.get('style'):
+                    st = copy.deepcopy(self.styles.table_styles[td.get('style')])
+                    for s in st._cmds:
+                        s[1][1] = posy
+                        s[2][1] = posy
+                        s[1][0] = posx
+                        s[2][0] = posx
+                    styles.append(st)
+                if td.get('paraStyle'):
+                    # TODO: merge styles
+                    paraStyle = self.styles.styles[td.get('paraStyle')]
+                posx += 1
+
+                flow = []
+                for n in utils._child_get(td, self):
+                    if n.tag == etree.Comment:
+                        n.text = ''
+                        continue
+                    fl = self._flowable(n, extra_style=paraStyle)
+                    if isinstance(fl,list):
+                        flow  += fl
+                    else:
+                        flow.append( fl )
+
+                if not len(flow):
+                    flow = self._textual(td)
+                data2.append( flow )
+            if len(data2)>length:
+                length=len(data2)
+                for ab in data:
+                    while len(ab)<length:
+                        ab.append('')
+            while len(data2)<length:
+                data2.append('')
+            data.append( data2 )
+            posy += 1
+
+        if node.get('colWidths'):
+            assert length == len(node.get('colWidths').split(','))
+            colwidths = [utils.unit_get(f.strip()) for f in node.get('colWidths').split(',')]
+        if node.get('rowHeights'):
+            rowheights = [utils.unit_get(f.strip()) for f in node.get('rowHeights').split(',')]
+            if len(rowheights) == 1:
+                rowheights = rowheights[0]
+        table = platypus.LongTable(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
+        if node.get('style'):
+            table.setStyle(self.styles.table_styles[node.get('style')])
+        for s in styles:
+            table.setStyle(s)
+        return table
+
+    def _illustration(self, node):
+        return _rml_Illustration(node, self.localcontext, self.styles, self)
+
+    def _textual_image(self, node):
+        return base64.decodestring(node.text)
+
+    def _pto(self, node):
+        sub_story = []
+        pto_header = None
+        pto_trailer = None
+        for node in utils._child_get(node, self):
+            if node.tag == etree.Comment:
+                node.text = ''
+                continue
+            elif node.tag=='pto_header':
+                pto_header = self.render(node)
+            elif node.tag=='pto_trailer':
+                pto_trailer = self.render(node)
+            else:
+                flow = self._flowable(node)
+                if flow:
+                    if isinstance(flow,list):
+                        sub_story = sub_story + flow
+                    else:
+                        sub_story.append(flow)
+        return platypus.flowables.PTOContainer(sub_story, trailer=pto_trailer, header=pto_header)
+
+    def _flowable(self, node, extra_style=None):
+        if node.tag=='pto':
+            return self._pto(node)
+        if node.tag=='para':
+            style = self.styles.para_style_get(node)
+            if extra_style:
+                style.__dict__.update(extra_style)
+            result = []
+            for i in self._textual(node).split('\n'):
+                result.append(platypus.Paragraph(i, style, **(utils.attr_get(node, [], {'bulletText':'str'}))))
+            return result
+        elif node.tag=='barCode':
+            try:
+                from reportlab.graphics.barcode import code128
+                from reportlab.graphics.barcode import code39
+                from reportlab.graphics.barcode import code93
+                from reportlab.graphics.barcode import common
+                from reportlab.graphics.barcode import fourstate
+                from reportlab.graphics.barcode import usps
+                from reportlab.graphics.barcode import createBarcodeDrawing
+
+            except ImportError:
+                _logger.warning("Cannot use barcode renderers:", exc_info=True)
+                return None
+            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'})
+            codes = {
+                'codabar': lambda x: common.Codabar(x, **args),
+                'code11': lambda x: common.Code11(x, **args),
+                'code128': lambda x: code128.Code128(str(x), **args),
+                'standard39': lambda x: code39.Standard39(str(x), **args),
+                'standard93': lambda x: code93.Standard93(str(x), **args),
+                'i2of5': lambda x: common.I2of5(x, **args),
+                'extended39': lambda x: code39.Extended39(str(x), **args),
+                'extended93': lambda x: code93.Extended93(str(x), **args),
+                'msi': lambda x: common.MSI(x, **args),
+                'fim': lambda x: usps.FIM(x, **args),
+                'postnet': lambda x: usps.POSTNET(x, **args),
+                'ean13': lambda x: createBarcodeDrawing('EAN13', value=str(x), **args),
+                'qrcode': lambda x: createBarcodeDrawing('QR', value=x, **args),
+            }
+            code = 'code128'
+            if node.get('code'):
+                code = node.get('code').lower()
+            return codes[code](self._textual(node))
+        elif node.tag=='name':
+            self.styles.names[ node.get('id')] = node.get('value')
+            return None
+        elif node.tag=='xpre':
+            style = self.styles.para_style_get(node)
+            return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
+        elif node.tag=='pre':
+            style = self.styles.para_style_get(node)
+            return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
+        elif node.tag=='illustration':
+            return  self._illustration(node)
+        elif node.tag=='blockTable':
+            return  self._table(node)
+        elif node.tag=='title':
+            styles = reportlab.lib.styles.getSampleStyleSheet()
+            style = styles['Title']
+            return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
+        elif re.match('^h([1-9]+[0-9]*)$', (node.tag or '')):
+            styles = reportlab.lib.styles.getSampleStyleSheet()
+            style = styles['Heading'+str(node.tag[1:])]
+            return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
+        elif node.tag=='image':
+            image_data = False
+            if not node.get('file'):
+                if node.get('name'):
+                    if node.get('name') in self.doc.images:
+                        _logger.debug("Image %s read ", node.get('name'))
+                        image_data = self.doc.images[node.get('name')].read()
+                    else:
+                        _logger.warning("Image %s not defined", node.get('name'))
+                        return False
+                else:
+                    import base64
+                    newtext = node.text
+                    if self.localcontext:
+                        newtext = utils._process_text(self, node.text or '')
+                    image_data = base64.decodestring(newtext)
+                if not image_data:
+                    _logger.debug("No inline image data")
+                    return False
+                image = StringIO(image_data)
+            else:
+                _logger.debug("Image get from file %s", node.get('file'))
+                image = _open_image(node.get('file'), path=self.doc.path)
+            return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
+        elif node.tag=='spacer':
+            if node.get('width'):
+                width = utils.unit_get(node.get('width'))
+            else:
+                width = utils.unit_get('1cm')
+            length = utils.unit_get(node.get('length'))
+            return platypus.Spacer(width=width, height=length)
+        elif node.tag=='section':
+            return self.render(node)
+        elif node.tag == 'pageNumberReset':
+            return PageReset()
+        elif node.tag in ('pageBreak', 'nextPage'):
+            return platypus.PageBreak()
+        elif node.tag=='condPageBreak':
+            return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
+        elif node.tag=='setNextTemplate':
+            return platypus.NextPageTemplate(str(node.get('name')))
+        elif node.tag=='nextFrame':
+            return platypus.CondPageBreak(1000)           # TODO: change the 1000 !
+        elif node.tag == 'setNextFrame':
+            from reportlab.platypus.doctemplate import NextFrameFlowable
+            return NextFrameFlowable(str(node.get('name')))
+        elif node.tag == 'currentFrame':
+            from reportlab.platypus.doctemplate import CurrentFrameFlowable
+            return CurrentFrameFlowable(str(node.get('name')))
+        elif node.tag == 'frameEnd':
+            return EndFrameFlowable()
+        elif node.tag == 'hr':
+            width_hr=node.get('width') or '100%'
+            color_hr=node.get('color')  or 'black'
+            thickness_hr=node.get('thickness') or 1
+            lineCap_hr=node.get('lineCap') or 'round'
+            return platypus.flowables.HRFlowable(width=width_hr,color=color.get(color_hr),thickness=float(thickness_hr),lineCap=str(lineCap_hr))
+        else:
+            sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.tag,))
+            return None
+
+    def render(self, node_story):
+        def process_story(node_story):
+            sub_story = []
+            for node in utils._child_get(node_story, self):
+                if node.tag == etree.Comment:
+                    node.text = ''
+                    continue
+                flow = self._flowable(node)
+                if flow:
+                    if isinstance(flow,list):
+                        sub_story = sub_story + flow
+                    else:
+                        sub_story.append(flow)
+            return sub_story
+        return process_story(node_story)
+
+
+class EndFrameFlowable(ActionFlowable):
+    def __init__(self,resume=0):
+        ActionFlowable.__init__(self,('frameEnd',resume))
+
+class TinyDocTemplate(platypus.BaseDocTemplate):
+
+    def beforeDocument(self):
+        # Store some useful value directly inside canvas, so it's available
+        # on flowable drawing (needed for proper PageCount handling)
+        self.canv._doPageReset = False
+        self.canv._storyCount = 0
+
+    def ___handle_pageBegin(self):
+        self.page += 1
+        self.pageTemplate.beforeDrawPage(self.canv,self)
+        self.pageTemplate.checkPageSize(self.canv,self)
+        self.pageTemplate.onPage(self.canv,self)
+        for f in self.pageTemplate.frames: f._reset()
+        self.beforePage()
+        self._curPageFlowableCount = 0
+        if hasattr(self,'_nextFrameIndex'):
+            del self._nextFrameIndex
+        for f in self.pageTemplate.frames:
+            if f.id == 'first':
+                self.frame = f
+                break
+        self.handle_frameBegin()
+
+    def afterPage(self):
+        if self.canv._doPageReset:
+            # Following a <pageReset/> tag:
+            # - we reset page number to 0
+            # - we add  an new PageCount flowable (relative to the current
+            #   story number), but not for NumeredCanvas at is handle page
+            #   count itself)
+            # NOTE: _rml_template render() method add a PageReset flowable at end
+            #   of each story, so we're sure to pass here at least once per story.
+            if not isinstance(self.canv, NumberedCanvas):
+                self.handle_flowable([ PageCount(story_count=self.canv._storyCount) ])
+            self.canv._pageCount = self.page
+            self.page = 0
+            self.canv._flag = True
+            self.canv._pageNumber = 0
+            self.canv._doPageReset = False
+            self.canv._storyCount += 1
+
+class _rml_template(object):
+    def __init__(self, localcontext, out, node, doc, images=None, path='.', title=None):
+        if images is None:
+            images = {}
+        if not localcontext:
+            localcontext={'internal_header':True}
+        self.localcontext = localcontext
+        self.images= images
+        self.path = path
+        self.title = title
+
+        pagesize_map = {'a4': A4,
+                    'us_letter': letter
+                    }
+        pageSize = (841.8897637795275, 595.275590551181)
+        self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','rotation':'int','title':'str','author':'str'}))
+        self.page_templates = []
+        self.styles = doc.styles
+        self.doc = doc
+        self.image=[]
+        pts = node.findall('pageTemplate')
+        for pt in pts:
+            frames = []
+            for frame_el in pt.findall('frame'):
+                frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
+                if utils.attr_get(frame_el, ['last']):
+                    frame.lastFrame = True
+                frames.append( frame )
+            try :
+                gr = pt.findall('pageGraphics')\
+                    or pt[1].findall('pageGraphics')
+            except Exception: # FIXME: be even more specific, perhaps?
+                gr=''
+            if len(gr):
+#                self.image=[ n for n in utils._child_get(gr[0], self) if n.tag=='image' or not self.localcontext]
+                drw = _rml_draw(self.localcontext,gr[0], self.doc, images=images, path=self.path, title=self.title)
+                self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
+            else:
+                drw = _rml_draw(self.localcontext,node,self.doc,title=self.title)
+                self.page_templates.append( platypus.PageTemplate(frames=frames,onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
+        self.doc_tmpl.addPageTemplates(self.page_templates)
+
+    def render(self, node_stories):
+        if self.localcontext and not self.localcontext.get('internal_header',False):
+            del self.localcontext['internal_header']
+        fis = []
+        r = _rml_flowable(self.doc,self.localcontext, images=self.images, path=self.path, title=self.title)
+        story_cnt = 0
+        for node_story in node_stories:
+            if story_cnt > 0:
+                fis.append(platypus.PageBreak())
+            fis += r.render(node_story)
+            # Reset Page Number with new story tag
+            fis.append(PageReset())
+            story_cnt += 1
+        if self.localcontext and self.localcontext.get('internal_header',False):
+            self.doc_tmpl.afterFlowable(fis)
+            self.doc_tmpl.build(fis,canvasmaker=NumberedCanvas)
+        else:
+            self.doc_tmpl.build(fis)
+
+def parseNode(rml, localcontext=None, fout=None, images=None, path='.', title=None):
+    node = etree.XML(rml)
+    r = _rml_doc(node, localcontext, images, path, title=title)
+    #try to override some font mappings
+    try:
+        from customfonts import SetCustomFonts
+        SetCustomFonts(r)
+    except ImportError:
+        # means there is no custom fonts mapping in this system.
+        pass
+    except Exception:
+        _logger.warning('Cannot set font mapping', exc_info=True)
+        pass
+    fp = StringIO()
+    r.render(fp)
+    return fp.getvalue()
+
+def parseString(rml, localcontext=None, fout=None, images=None, path='.', title=None):
+    node = etree.XML(rml)
+    r = _rml_doc(node, localcontext, images, path, title=title)
+
+    #try to override some font mappings
+    try:
+        from customfonts import SetCustomFonts
+        SetCustomFonts(r)
+    except Exception:
+        pass
+
+    if fout:
+        fp = file(fout,'wb')
+        r.render(fp)
+        fp.close()
+        return fout
+    else:
+        fp = StringIO()
+        r.render(fp)
+        return fp.getvalue()
+
+def trml2pdf_help():
+    print 'Usage: trml2pdf input.rml >output.pdf'
+    print 'Render the standard input (RML) and output a PDF file'
+    sys.exit(0)
+
+if __name__=="__main__":
+    if len(sys.argv)>1:
+        if sys.argv[1]=='--help':
+            trml2pdf_help()
+        print parseString(file(sys.argv[1], 'r').read()),
+    else:
+        print 'Usage: trml2pdf input.rml >output.pdf'
+        print 'Try \'trml2pdf --help\' for more information.'
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

BIN
trml2pdf.pyc


+ 12 - 0
views/web_printscreen_zb.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- vim:fdn=3:
+-->
+<openerp>
+    <data>
+        <template id="assets_backend" name="web_printscreen_zb assets" inherit_id="web.assets_backend">
+            <xpath expr="." position="inside">
+                <script type="text/javascript" src="/web_printscreen_zb/static/src/js/web_printscreen_export.js"></script>
+            </xpath>
+        </template>
+    </data>
+</openerp>