2009-10-20 10:52:23 +00:00
# -*- coding: utf-8 -*-
2008-06-16 11:00:21 +00:00
##############################################################################
2010-05-11 12:56:31 +00:00
#
2008-11-15 06:14:55 +00:00
# OpenERP, Open Source Management Solution
2009-10-14 12:32:15 +00:00
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
2008-06-16 11:00:21 +00:00
#
2008-11-03 18:27:16 +00:00
# This program is free software: you can redistribute it and/or modify
2009-10-14 12:32:15 +00:00
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
2008-06-16 11:00:21 +00:00
#
2008-11-03 18:27:16 +00:00
# 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
2009-10-14 12:32:15 +00:00
# GNU Affero General Public License for more details.
2008-06-16 11:00:21 +00:00
#
2009-10-14 12:32:15 +00:00
# You should have received a copy of the GNU Affero General Public License
2010-05-11 12:56:31 +00:00
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2008-06-16 11:00:21 +00:00
#
2008-11-03 18:27:16 +00:00
##############################################################################
2006-12-07 13:41:40 +00:00
import sys
import copy
import reportlab
2008-06-30 20:47:46 +00:00
import re
2006-12-07 13:41:40 +00:00
from reportlab . pdfgen import canvas
from reportlab import platypus
import utils
import color
import os
2010-11-23 15:11:24 +00:00
import logging
2009-04-28 10:24:21 +00:00
from lxml import etree
import base64
[FIX] report: break lines and splitted words with reportlab > 3.0
From reportlab 3.0, empty plaintext paragraphs do not lead to a break line anymore.
Before release 3.0, paragraphs having tags but no plaintext leaded to a break line.
This patch aims to recover the behavior of reportlab releases < 3.0, as
<para><font color="white"> </font></para> is used in allmost all rml reports
The current patch is not considered as clean, but we did not find any better solution.
If someone find a parameter to pass to reportlab in order to bring back the old behavior of reportlab
he is welcome to provide the better patch.
Besides, in reportlab 3.0, splitlongwords has been introduced as parameter,
to allow to break long words. The default value is True.
This parameter seems to break the columns headers
(it splits the text within the column header)
We therefore take the choice to not activate it, as it was not present anyway in reportlab < 3.0
To test the good behavior of this patch:
While having reportlab < 3.0 (2.5 for instance), print a draft invoice
Then, upgrade to reportlab > 3.0 (3.1.8 for instance), print the same draft invoice.
The generated pdf must be (allmost) identical, in particular concerning spaces.
Specifically, the space between the partner address and his phone.
2015-01-22 13:14:36 +00:00
from distutils . version import LooseVersion
2009-04-28 10:24:21 +00:00
from reportlab . platypus . doctemplate import ActionFlowable
2011-02-07 12:57:23 +00:00
from openerp . tools . safe_eval import safe_eval as eval
2010-10-01 14:07:24 +00:00
from reportlab . lib . units import inch , cm , mm
2011-02-07 12:57:23 +00:00
from openerp . tools . misc import file_open
2010-11-26 18:30:43 +00:00
from reportlab . pdfbase import pdfmetrics
2011-12-21 09:24:46 +00:00
from reportlab . lib . pagesizes import A4 , letter
2010-11-23 15:14:44 +00:00
try :
from cStringIO import StringIO
_hush_pyflakes = [ StringIO ]
except ImportError :
from StringIO import StringIO
2012-01-24 12:55:12 +00:00
_logger = logging . getLogger ( __name__ )
2007-01-07 21:24:58 +00:00
encoding = ' utf-8 '
2006-12-07 13:41:40 +00:00
2013-03-11 15:51:47 +00:00
def select_fontname ( fontname , default_fontname ) :
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 . warning ( ' Could not locate font %s , substituting default: %s ' ,
fontname , default_fontname )
fontname = default_fontname
return fontname
2010-12-03 16:45:53 +00:00
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 )
2010-12-20 05:50:40 +00:00
2010-10-01 14:07:24 +00:00
class NumberedCanvas ( canvas . Canvas ) :
def __init__ ( self , * args , * * kwargs ) :
canvas . Canvas . __init__ ( self , * args , * * kwargs )
2013-05-10 15:19:34 +00:00
self . _saved_page_states = [ ]
2010-10-01 14:07:24 +00:00
def showPage ( self ) :
self . _startPage ( )
2013-05-10 15:19:34 +00:00
def save ( self ) :
""" add page info to each page (page x of y) """
for state in self . _saved_page_states :
self . __dict__ . update ( state )
2014-03-26 09:15:29 +00:00
self . draw_page_number ( )
2013-05-10 15:19:34 +00:00
canvas . Canvas . showPage ( self )
canvas . Canvas . save ( self )
2014-03-26 09:15:29 +00:00
def draw_page_number ( self ) :
page_count = len ( self . _saved_page_states )
2010-10-04 13:35:06 +00:00
self . setFont ( " Helvetica " , 8 )
2010-11-24 10:10:30 +00:00
self . drawRightString ( ( self . _pagesize [ 0 ] - 30 ) , ( self . _pagesize [ 1 ] - 40 ) ,
2012-04-24 06:10:09 +00:00
" %(this)i / %(total)i " % {
2014-05-21 09:20:37 +00:00
' this ' : self . _pageNumber ,
2013-05-10 15:19:34 +00:00
' total ' : page_count ,
2010-10-01 14:07:24 +00:00
}
)
2008-06-30 22:23:47 +00:00
class PageCount ( platypus . Flowable ) :
2012-07-16 07:52:17 +00:00
def __init__ ( self , story_count = 0 ) :
platypus . Flowable . __init__ ( self )
self . story_count = story_count
2008-07-22 14:24:36 +00:00
def draw ( self ) :
2012-12-14 12:38:03 +00:00
self . canv . beginForm ( " pageCount %d " % self . story_count )
2008-07-22 14:24:36 +00:00
self . canv . setFont ( " Helvetica " , utils . unit_get ( str ( 8 ) ) )
2010-09-27 13:17:41 +00:00
self . canv . drawString ( 0 , 0 , str ( self . canv . getPageNumber ( ) ) )
2008-07-22 14:24:36 +00:00
self . canv . endForm ( )
2008-06-30 22:23:47 +00:00
class PageReset ( platypus . Flowable ) :
2008-07-22 14:24:36 +00:00
def draw ( self ) :
2014-10-21 08:54:23 +00:00
""" Flag to close current story page numbering and prepare for the next
should be executed after the rendering of the full story """
2012-07-16 07:52:17 +00:00
self . canv . _doPageReset = True
2008-06-30 22:23:47 +00:00
2009-04-28 10:24:21 +00:00
class _rml_styles ( object , ) :
def __init__ ( self , nodes , localcontext ) :
self . localcontext = localcontext
2008-07-22 14:24:36 +00:00
self . styles = { }
2010-05-14 13:13:55 +00:00
self . styles_obj = { }
2008-07-22 14:24:36 +00:00
self . names = { }
self . table_styles = { }
2010-05-14 13:13:55 +00:00
self . default_style = reportlab . lib . styles . getSampleStyleSheet ( )
2008-07-22 14:24:36 +00:00
for node in nodes :
2009-04-28 10:24:21 +00:00
for style in node . findall ( ' blockTableStyle ' ) :
self . table_styles [ style . get ( ' id ' ) ] = self . _table_style_get ( style )
for style in node . findall ( ' paraStyle ' ) :
2010-05-14 13:13:55 +00:00
sname = style . get ( ' name ' )
self . styles [ sname ] = self . _para_style_update ( style )
2013-06-03 09:30:48 +00:00
if self . default_style . has_key ( sname ) :
2013-05-27 16:20:51 +00:00
for key , value in self . styles [ sname ] . items ( ) :
setattr ( self . default_style [ sname ] , key , value )
else :
self . styles_obj [ sname ] = reportlab . lib . styles . ParagraphStyle ( sname , self . default_style [ " Normal " ] , * * self . styles [ sname ] )
2009-04-28 10:24:21 +00:00
for variable in node . findall ( ' initialize ' ) :
for name in variable . findall ( ' name ' ) :
self . names [ name . get ( ' id ' ) ] = name . get ( ' value ' )
2008-07-22 14:24:36 +00:00
def _para_style_update ( self , node ) :
data = { }
for attr in [ ' textColor ' , ' backColor ' , ' bulletColor ' , ' borderColor ' ] :
2009-04-28 10:24:21 +00:00
if node . get ( attr ) :
data [ attr ] = color . get ( node . get ( attr ) )
2013-03-11 15:51:47 +00:00
for attr in [ ' bulletFontName ' , ' fontName ' ] :
if node . get ( attr ) :
fontname = select_fontname ( node . get ( attr ) , None )
if fontname is not None :
data [ ' fontName ' ] = fontname
for attr in [ ' bulletText ' ] :
2009-04-28 10:24:21 +00:00
if node . get ( attr ) :
data [ attr ] = node . get ( attr )
2008-08-11 09:27:55 +00:00
for attr in [ ' fontSize ' , ' leftIndent ' , ' rightIndent ' , ' spaceBefore ' , ' spaceAfter ' ,
2008-07-22 14:24:36 +00:00
' firstLineIndent ' , ' bulletIndent ' , ' bulletFontSize ' , ' leading ' ,
' borderWidth ' , ' borderPadding ' , ' borderRadius ' ] :
2009-04-28 10:24:21 +00:00
if node . get ( attr ) :
data [ attr ] = utils . unit_get ( node . get ( attr ) )
if node . get ( ' alignment ' ) :
2008-07-22 14:24:36 +00:00
align = {
' right ' : reportlab . lib . enums . TA_RIGHT ,
' center ' : reportlab . lib . enums . TA_CENTER ,
' justify ' : reportlab . lib . enums . TA_JUSTIFY
}
2009-04-28 10:24:21 +00:00
data [ ' alignment ' ] = align . get ( node . get ( ' alignment ' ) . lower ( ) , reportlab . lib . enums . TA_LEFT )
[FIX] report: break lines and splitted words with reportlab > 3.0
From reportlab 3.0, empty plaintext paragraphs do not lead to a break line anymore.
Before release 3.0, paragraphs having tags but no plaintext leaded to a break line.
This patch aims to recover the behavior of reportlab releases < 3.0, as
<para><font color="white"> </font></para> is used in allmost all rml reports
The current patch is not considered as clean, but we did not find any better solution.
If someone find a parameter to pass to reportlab in order to bring back the old behavior of reportlab
he is welcome to provide the better patch.
Besides, in reportlab 3.0, splitlongwords has been introduced as parameter,
to allow to break long words. The default value is True.
This parameter seems to break the columns headers
(it splits the text within the column header)
We therefore take the choice to not activate it, as it was not present anyway in reportlab < 3.0
To test the good behavior of this patch:
While having reportlab < 3.0 (2.5 for instance), print a draft invoice
Then, upgrade to reportlab > 3.0 (3.1.8 for instance), print the same draft invoice.
The generated pdf must be (allmost) identical, in particular concerning spaces.
Specifically, the space between the partner address and his phone.
2015-01-22 13:14:36 +00:00
data [ ' splitLongWords ' ] = 0
2008-07-22 14:24:36 +00:00
return data
def _table_style_get ( self , style_node ) :
styles = [ ]
2009-11-28 11:58:09 +00:00
for node in style_node :
2009-04-28 10:24:21 +00:00
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 ' ) ) ) )
2008-07-22 14:24:36 +00:00
return platypus . tables . TableStyle ( styles )
def para_style_get ( self , node ) :
style = False
2010-05-14 13:13:55 +00:00
sname = node . get ( ' style ' )
if sname :
if sname in self . styles_obj :
style = self . styles_obj [ sname ]
2008-07-22 14:24:36 +00:00
else :
2013-11-08 21:27:09 +00:00
_logger . debug ( ' Warning: style not found, %s - setting default! ' , node . get ( ' style ' ) )
2008-07-22 14:24:36 +00:00
if not style :
2010-05-14 13:13:55 +00:00
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 )
2008-07-22 14:24:36 +00:00
return style
2006-12-07 13:41:40 +00:00
class _rml_doc ( object ) :
2011-11-21 13:33:46 +00:00
def __init__ ( self , node , localcontext = None , images = None , path = ' . ' , title = None ) :
2011-11-07 15:45:56 +00:00
if images is None :
images = { }
2011-11-21 13:33:46 +00:00
if localcontext is None :
localcontext = { }
2009-04-28 10:24:21 +00:00
self . localcontext = localcontext
self . etree = node
self . filename = self . etree . get ( ' filename ' )
2008-07-22 14:24:36 +00:00
self . images = images
self . path = path
2008-09-24 15:34:10 +00:00
self . title = title
2008-07-22 14:24:36 +00:00
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 :
2013-05-27 16:20:51 +00:00
2009-04-28 10:24:21 +00:00
for font in node . findall ( ' registerFont ' ) :
name = font . get ( ' fontName ' ) . encode ( ' ascii ' )
fname = font . get ( ' fontFile ' ) . encode ( ' ascii ' )
2010-12-20 05:50:40 +00:00
if name not in pdfmetrics . _fonts :
2010-12-29 15:51:33 +00:00
pdfmetrics . registerFont ( TTFont ( name , fname ) )
2013-05-31 11:46:12 +00:00
#by default, we map the fontName to each style (bold, italic, bold and italic), so that
#if there isn't any font defined for one of these style (via a font family), the system
#will fallback on the normal font.
2008-07-22 14:24:36 +00:00
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
2013-05-31 11:46:12 +00:00
#if registerFontFamily is defined, we register the mapping of the fontName to use for each style.
for font_family in node . findall ( ' registerFontFamily ' ) :
family_name = font_family . get ( ' normal ' ) . encode ( ' ascii ' )
if font_family . get ( ' italic ' ) :
addMapping ( family_name , 0 , 1 , font_family . get ( ' italic ' ) . encode ( ' ascii ' ) )
if font_family . get ( ' bold ' ) :
addMapping ( family_name , 1 , 0 , font_family . get ( ' bold ' ) . encode ( ' ascii ' ) )
if font_family . get ( ' boldItalic ' ) :
addMapping ( family_name , 1 , 1 , font_family . get ( ' boldItalic ' ) . encode ( ' ascii ' ) )
2010-11-23 15:14:44 +00:00
def setTTFontMapping ( self , face , fontname , filename , mode = ' all ' ) :
2009-02-07 19:26:06 +00:00
from reportlab . lib . fonts import addMapping
from reportlab . pdfbase import pdfmetrics
from reportlab . pdfbase . ttfonts import TTFont
2010-05-11 12:56:31 +00:00
2010-12-20 05:50:40 +00:00
if fontname not in pdfmetrics . _fonts :
2010-12-29 15:51:33 +00:00
pdfmetrics . registerFont ( TTFont ( fontname , filename ) )
2012-12-14 12:38:03 +00:00
if mode == ' all ' :
2009-11-24 14:44:05 +00:00
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
2012-12-14 12:38:03 +00:00
elif mode == ' italic ' :
2009-11-24 14:44:05 +00:00
addMapping ( face , 0 , 1 , fontname ) #italic
2012-12-14 12:38:03 +00:00
elif mode == ' bold ' :
2009-11-24 14:44:05 +00:00
addMapping ( face , 1 , 0 , fontname ) #bold
2012-12-14 12:38:03 +00:00
elif mode == ' bolditalic ' :
2009-11-24 14:44:05 +00:00
addMapping ( face , 1 , 1 , fontname ) #italic and bold
2009-02-07 19:26:06 +00:00
2008-07-22 14:24:36 +00:00
def _textual_image ( self , node ) :
rc = ' '
2009-11-28 11:58:09 +00:00
for n in node :
2009-04-28 10:24:21 +00:00
rc + = ( etree . tostring ( n ) or ' ' ) + n . tail
return base64 . decodestring ( node . tostring ( ) )
2008-07-22 14:24:36 +00:00
def _images ( self , el ) :
result = { }
2010-12-06 12:40:52 +00:00
for node in el . findall ( ' .//image ' ) :
2009-04-28 10:24:21 +00:00
rc = ( node . text or ' ' )
result [ node . get ( ' name ' ) ] = base64 . decodestring ( rc )
2008-07-22 14:24:36 +00:00
return result
def render ( self , out ) :
2010-12-06 12:40:52 +00:00
el = self . etree . findall ( ' .//docinit ' )
2008-07-22 14:24:36 +00:00
if el :
self . docinit ( el )
2010-12-06 12:40:52 +00:00
el = self . etree . findall ( ' .//stylesheet ' )
2009-04-28 10:24:21 +00:00
self . styles = _rml_styles ( el , self . localcontext )
2008-07-22 14:24:36 +00:00
2010-12-06 12:40:52 +00:00
el = self . etree . findall ( ' .//images ' )
2008-07-22 14:24:36 +00:00
if el :
self . images . update ( self . _images ( el [ 0 ] ) )
2010-12-06 12:19:35 +00:00
2010-12-06 12:40:52 +00:00
el = self . etree . findall ( ' .//template ' )
2008-07-22 14:24:36 +00:00
if len ( el ) :
2009-04-28 10:24:21 +00:00
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 )
2008-07-22 14:24:36 +00:00
else :
self . canvas = canvas . Canvas ( out )
2009-04-28 10:24:21 +00:00
pd = self . etree . find ( ' pageDrawing ' ) [ 0 ]
pd_obj = _rml_canvas ( self . canvas , self . localcontext , None , self , self . images , path = self . path , title = self . title )
2008-07-22 14:24:36 +00:00
pd_obj . render ( pd )
self . canvas . showPage ( )
self . canvas . save ( )
2006-12-07 13:41:40 +00:00
class _rml_canvas ( object ) :
2011-11-07 15:45:56 +00:00
def __init__ ( self , canvas , localcontext , doc_tmpl = None , doc = None , images = None , path = ' . ' , title = None ) :
if images is None :
images = { }
2009-04-28 10:24:21 +00:00
self . localcontext = localcontext
2008-07-22 14:24:36 +00:00
self . canvas = canvas
self . styles = doc . styles
self . doc_tmpl = doc_tmpl
self . doc = doc
self . images = images
self . path = path
2008-09-24 15:34:10 +00:00
self . title = title
if self . title :
self . canvas . setTitle ( self . title )
2008-07-22 14:24:36 +00:00
def _textual ( self , node , x = 0 , y = 0 ) :
2009-08-17 12:37:47 +00:00
text = node . text and node . text . encode ( ' utf-8 ' ) or ' '
rc = utils . _process_text ( self , text )
2009-04-28 10:24:21 +00:00
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 )
2012-07-16 07:52:17 +00:00
self . canvas . doForm ( ' pageCount %s ' % ( self . canvas . _storyCount , ) )
2009-04-28 10:24:21 +00:00
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 )
2010-09-27 10:09:55 +00:00
return rc . replace ( ' \n ' , ' ' )
2008-07-22 14:24:36 +00:00
def _drawString ( self , node ) :
v = utils . attr_get ( node , [ ' x ' , ' y ' ] )
2009-04-28 10:24:21 +00:00
text = self . _textual ( node , * * v )
2009-06-26 11:27:44 +00:00
text = utils . xml2str ( text )
2014-07-02 15:29:13 +00:00
try :
self . canvas . drawString ( text = text , * * v )
except TypeError as e :
2014-07-03 07:36:46 +00:00
_logger . error ( " Bad RML: <drawString> tag requires attributes ' x ' and ' y ' ! " )
2014-07-02 15:29:13 +00:00
raise e
2009-04-28 10:24:21 +00:00
2008-07-22 14:24:36 +00:00
def _drawCenteredString ( self , node ) :
v = utils . attr_get ( node , [ ' x ' , ' y ' ] )
2009-04-28 10:24:21 +00:00
text = self . _textual ( node , * * v )
2009-06-26 11:27:44 +00:00
text = utils . xml2str ( text )
2009-04-28 10:24:21 +00:00
self . canvas . drawCentredString ( text = text , * * v )
2008-07-22 14:24:36 +00:00
def _drawRightString ( self , node ) :
v = utils . attr_get ( node , [ ' x ' , ' y ' ] )
2009-04-28 10:24:21 +00:00
text = self . _textual ( node , * * v )
2009-06-26 11:27:44 +00:00
text = utils . xml2str ( text )
2009-04-28 10:24:21 +00:00
self . canvas . drawRightString ( text = text , * * v )
2008-07-22 14:24:36 +00:00
def _rect ( self , node ) :
2009-04-28 10:24:21 +00:00
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 ' } ) )
2008-07-22 14:24:36 +00:00
else :
self . canvas . rect ( * * utils . attr_get ( node , [ ' x ' , ' y ' , ' width ' , ' height ' ] , { ' fill ' : ' bool ' , ' stroke ' : ' bool ' } ) )
def _ellipse ( self , node ) :
2009-04-28 10:24:21 +00:00
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 ' ) )
2008-07-22 14:24:36 +00:00
self . canvas . ellipse ( x1 , y1 , x2 , y2 , * * utils . attr_get ( node , [ ] , { ' fill ' : ' bool ' , ' stroke ' : ' bool ' } ) )
2009-04-28 10:24:21 +00:00
2008-07-22 14:24:36 +00:00
def _curves ( self , node ) :
2009-04-28 10:24:21 +00:00
line_str = node . text . split ( )
2008-07-22 14:24:36 +00:00
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 : ]
2009-04-28 10:24:21 +00:00
2008-07-22 14:24:36 +00:00
def _lines ( self , node ) :
2009-04-28 10:24:21 +00:00
line_str = node . text . split ( )
2008-07-22 14:24:36 +00:00
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 )
2009-04-28 10:24:21 +00:00
2008-07-22 14:24:36 +00:00
def _grid ( self , node ) :
2009-04-28 10:24:21 +00:00
xlist = [ utils . unit_get ( s ) for s in node . get ( ' xs ' ) . split ( ' , ' ) ]
ylist = [ utils . unit_get ( s ) for s in node . get ( ' ys ' ) . split ( ' , ' ) ]
2008-07-22 14:24:36 +00:00
self . canvas . grid ( xlist , ylist )
2009-04-28 10:24:21 +00:00
2008-07-22 14:24:36 +00:00
def _translate ( self , node ) :
2009-04-28 10:24:21 +00:00
dx = utils . unit_get ( node . get ( ' dx ' ) ) or 0
dy = utils . unit_get ( node . get ( ' dy ' ) ) or 0
2008-07-22 14:24:36 +00:00
self . canvas . translate ( dx , dy )
def _circle ( self , node ) :
2009-04-28 10:24:21 +00:00
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 ' } ) )
2008-07-22 14:24:36 +00:00
def _place ( self , node ) :
2013-04-08 13:25:10 +00:00
flows = _rml_flowable ( self . doc , self . localcontext , images = self . images , path = self . path , title = self . title , canvas = self . canvas ) . render ( node )
2008-07-22 14:24:36 +00:00
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 :
2012-02-08 17:02:17 +00:00
raise ValueError ( " Not enough space " )
2008-07-22 14:24:36 +00:00
def _line_mode ( self , node ) :
ljoin = { ' round ' : 1 , ' mitered ' : 0 , ' bevelled ' : 2 }
lcap = { ' default ' : 0 , ' round ' : 1 , ' square ' : 2 }
2009-04-28 10:24:21 +00:00
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 ( ' , ' )
2008-07-22 14:24:36 +00:00
for x in range ( len ( dashes ) ) :
dashes [ x ] = utils . unit_get ( dashes [ x ] )
2009-04-28 10:24:21 +00:00
self . canvas . setDash ( node . get ( ' dash ' ) . split ( ' , ' ) )
2008-07-22 14:24:36 +00:00
def _image ( self , node ) :
import urllib
2010-12-03 16:45:53 +00:00
import urlparse
2008-07-22 14:24:36 +00:00
from reportlab . lib . utils import ImageReader
2010-12-03 16:45:53 +00:00
nfile = node . get ( ' file ' )
if not nfile :
2009-04-28 10:24:21 +00:00
if node . get ( ' name ' ) :
image_data = self . images [ node . get ( ' name ' ) ]
2012-01-24 12:55:12 +00:00
_logger . debug ( " Image %s used " , node . get ( ' name ' ) )
2010-11-23 15:14:44 +00:00
s = StringIO ( image_data )
2008-07-22 14:24:36 +00:00
else :
2011-05-30 09:03:35 +00:00
newtext = node . text
2009-04-28 10:24:21 +00:00
if self . localcontext :
2011-05-30 09:03:35 +00:00
res = utils . _regex . findall ( newtext )
2010-08-30 09:59:46 +00:00
for key in res :
2011-05-30 09:03:35 +00:00
newtext = eval ( key , { } , self . localcontext ) or ' '
2010-08-30 09:59:46 +00:00
image_data = None
2011-05-30 09:03:35 +00:00
if newtext :
image_data = base64 . decodestring ( newtext )
2010-08-30 09:59:46 +00:00
if image_data :
2010-11-23 15:14:44 +00:00
s = StringIO ( image_data )
2010-08-30 09:59:46 +00:00
else :
2012-01-24 12:55:12 +00:00
_logger . debug ( " No image data! " )
2010-08-30 09:59:46 +00:00
return False
2008-07-22 14:24:36 +00:00
else :
2010-12-03 16:45:53 +00:00
if nfile in self . images :
s = StringIO ( self . images [ nfile ] )
2008-07-22 14:24:36 +00:00
else :
try :
2010-12-03 16:45:53 +00:00
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?
2012-01-24 12:55:12 +00:00
_logger . debug ( " Retrieve image from %s " , nfile )
2010-12-03 16:45:53 +00:00
u = urllib . urlopen ( str ( nfile ) )
2010-11-23 15:14:44 +00:00
s = StringIO ( u . read ( ) )
2010-12-03 16:45:53 +00:00
else :
2012-01-24 12:55:12 +00:00
_logger . debug ( " Open image file %s " , nfile )
2010-12-03 16:45:53 +00:00
s = _open_image ( nfile , path = self . path )
2011-01-04 10:13:35 +00:00
try :
img = ImageReader ( s )
( sx , sy ) = img . getSize ( )
2012-01-24 12:55:12 +00:00
_logger . debug ( " Image is %d x %d " , sx , sy )
2012-12-18 11:50:43 +00:00
args = { ' x ' : 0.0 , ' y ' : 0.0 , ' mask ' : ' auto ' }
2011-01-04 10:13:35 +00:00
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 ) :
2008-07-22 14:24:36 +00:00
args [ ' height ' ] = sy * args [ ' width ' ] / sx
2011-01-04 10:13:35 +00:00
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 ( )
2010-10-04 13:35:06 +00:00
# self.canvas._doc.SaveToFile(self.canvas._filename, self.canvas)
2008-07-22 14:24:36 +00:00
def _path ( self , node ) :
self . path = self . canvas . beginPath ( )
self . path . moveTo ( * * utils . attr_get ( node , [ ' x ' , ' y ' ] ) )
2009-04-28 10:24:21 +00:00
for n in utils . _child_get ( node , self ) :
if not n . text :
if n . tag == ' moveto ' :
2008-07-22 14:24:36 +00:00
vals = utils . text_get ( n ) . split ( )
self . path . moveTo ( utils . unit_get ( vals [ 0 ] ) , utils . unit_get ( vals [ 1 ] ) )
2009-04-28 10:24:21 +00:00
elif n . tag == ' curvesto ' :
2008-07-22 14:24:36 +00:00
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 )
2009-04-28 10:24:21 +00:00
elif n . text :
data = n . text . split ( ) # Not sure if I must merge all TEXT_NODE ?
2008-07-22 14:24:36 +00:00
while len ( data ) > 1 :
x = utils . unit_get ( data . pop ( 0 ) )
y = utils . unit_get ( data . pop ( 0 ) )
self . path . lineTo ( x , y )
2009-04-28 10:24:21 +00:00
if ( not node . get ( ' close ' ) ) or utils . bool_get ( node . get ( ' close ' ) ) :
2008-07-22 14:24:36 +00:00
self . path . close ( )
self . canvas . drawPath ( self . path , * * utils . attr_get ( node , [ ] , { ' fill ' : ' bool ' , ' stroke ' : ' bool ' } ) )
2009-11-26 06:30:20 +00:00
def setFont ( self , node ) :
2013-03-11 15:51:47 +00:00
fontname = select_fontname ( node . get ( ' name ' ) , self . canvas . _fontname )
2010-11-26 18:30:43 +00:00
return self . canvas . setFont ( fontname , utils . unit_get ( node . get ( ' size ' ) ) )
2009-11-26 06:30:20 +00:00
2008-07-22 14:24:36 +00:00
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 ,
2009-04-28 10:24:21 +00:00
' fill ' : lambda node : self . canvas . setFillColor ( color . get ( node . get ( ' color ' ) ) ) ,
' stroke ' : lambda node : self . canvas . setStrokeColor ( color . get ( node . get ( ' color ' ) ) ) ,
2009-11-26 06:30:20 +00:00
' setFont ' : self . setFont ,
2008-07-22 14:24:36 +00:00
' place ' : self . _place ,
' circle ' : self . _circle ,
' lineMode ' : self . _line_mode ,
' path ' : self . _path ,
2009-04-28 10:24:21 +00:00
' rotate ' : lambda node : self . canvas . rotate ( float ( node . get ( ' degrees ' ) ) ) ,
2008-07-22 14:24:36 +00:00
' translate ' : self . _translate ,
' image ' : self . _image
}
2009-04-28 10:24:21 +00:00
for n in utils . _child_get ( node , self ) :
if n . tag in tags :
tags [ n . tag ] ( n )
2006-12-07 13:41:40 +00:00
class _rml_draw ( object ) :
2011-11-07 15:45:56 +00:00
def __init__ ( self , localcontext , node , styles , images = None , path = ' . ' , title = None ) :
if images is None :
images = { }
2009-04-28 10:24:21 +00:00
self . localcontext = localcontext
2008-07-22 14:24:36 +00:00
self . node = node
self . styles = styles
self . canvas = None
self . images = images
self . path = path
2008-09-24 15:34:10 +00:00
self . canvas_title = title
2008-07-22 14:24:36 +00:00
def render ( self , canvas , doc ) :
canvas . saveState ( )
2009-07-22 10:46:16 +00:00
cnv = _rml_canvas ( canvas , self . localcontext , doc , self . styles , images = self . images , path = self . path , title = self . canvas_title )
2008-07-22 14:24:36 +00:00
cnv . render ( self . node )
canvas . restoreState ( )
2006-12-07 13:41:40 +00:00
2010-12-03 16:45:53 +00:00
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 ) :
2012-12-14 12:38:03 +00:00
return self . width , self . height
2010-12-03 16:45:53 +00:00
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 )
2013-11-12 13:04:12 +00:00
# Workaround for issue #15: https://bitbucket.org/rptlab/reportlab/issue/15/infinite-pages-produced-when-splitting
original_pto_split = platypus . flowables . PTOContainer . split
def split ( self , availWidth , availHeight ) :
res = original_pto_split ( self , availWidth , availHeight )
if len ( res ) > 2 and len ( self . _content ) > 0 :
header = self . _content [ 0 ] . _ptoinfo . header
trailer = self . _content [ 0 ] . _ptoinfo . trailer
if isinstance ( res [ - 2 ] , platypus . flowables . UseUpSpace ) and len ( header + trailer ) == len ( res [ : - 2 ] ) :
return [ ]
return res
platypus . flowables . PTOContainer . split = split
2006-12-07 13:41:40 +00:00
class _rml_flowable ( object ) :
2013-04-08 13:25:10 +00:00
def __init__ ( self , doc , localcontext , images = None , path = ' . ' , title = None , canvas = None ) :
2011-11-07 15:45:56 +00:00
if images is None :
images = { }
2009-04-28 10:24:21 +00:00
self . localcontext = localcontext
2008-07-22 14:24:36 +00:00
self . doc = doc
self . styles = doc . styles
2011-11-07 15:45:56 +00:00
self . images = images
2008-07-22 14:24:36 +00:00
self . path = path
2008-09-24 15:34:10 +00:00
self . title = title
2013-04-08 13:25:10 +00:00
self . canvas = canvas
2008-07-22 14:24:36 +00:00
def _textual ( self , node ) :
2009-04-28 10:24:21 +00:00
rc1 = utils . _process_text ( self , node . text or ' ' )
for n in utils . _child_get ( node , self ) :
2009-05-25 05:24:18 +00:00
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 ]
2011-11-04 15:57:59 +00:00
if not n . tag == ' bullet ' :
2013-04-08 13:25:10 +00:00
if n . tag == ' pageNumber ' :
txt_n . text = self . canvas and str ( self . canvas . getPageNumber ( ) ) or ' '
else :
txt_n . text = utils . xml2str ( self . _textual ( n ) )
2011-11-04 15:57:59 +00:00
txt_n . tail = n . tail and utils . xml2str ( utils . _process_text ( self , n . tail . replace ( ' \n ' , ' ' ) ) ) or ' '
rc1 + = etree . tostring ( txt_n )
2009-04-28 10:24:21 +00:00
return rc1
2008-07-22 14:24:36 +00:00
def _table ( self , node ) :
2010-03-24 16:32:22 +00:00
children = utils . _child_get ( node , self , ' tr ' )
if not children :
2009-04-28 10:24:21 +00:00
return None
2008-07-22 14:24:36 +00:00
length = 0
colwidths = None
rowheights = None
data = [ ]
styles = [ ]
2009-04-28 10:24:21 +00:00
posy = 0
2010-03-24 16:32:22 +00:00
for tr in children :
2008-07-22 14:24:36 +00:00
paraStyle = None
2009-04-28 10:24:21 +00:00
if tr . get ( ' style ' ) :
st = copy . deepcopy ( self . styles . table_styles [ tr . get ( ' style ' ) ] )
2011-06-14 08:27:11 +00:00
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 )
2008-07-22 14:24:36 +00:00
styles . append ( st )
2009-04-28 10:24:21 +00:00
if tr . get ( ' paraStyle ' ) :
paraStyle = self . styles . styles [ tr . get ( ' paraStyle ' ) ]
2008-07-22 14:24:36 +00:00
data2 = [ ]
posx = 0
2009-04-28 10:24:21 +00:00
for td in utils . _child_get ( tr , self , ' td ' ) :
if td . get ( ' style ' ) :
st = copy . deepcopy ( self . styles . table_styles [ td . get ( ' style ' ) ] )
2008-07-22 14:24:36 +00:00
for s in st . _cmds :
s [ 1 ] [ 1 ] = posy
s [ 2 ] [ 1 ] = posy
s [ 1 ] [ 0 ] = posx
s [ 2 ] [ 0 ] = posx
styles . append ( st )
2009-04-28 10:24:21 +00:00
if td . get ( ' paraStyle ' ) :
2008-07-22 14:24:36 +00:00
# TODO: merge styles
2009-04-28 10:24:21 +00:00
paraStyle = self . styles . styles [ td . get ( ' paraStyle ' ) ]
2008-07-22 14:24:36 +00:00
posx + = 1
flow = [ ]
2009-04-28 10:24:21 +00:00
for n in utils . _child_get ( td , self ) :
2009-05-25 06:42:45 +00:00
if n . tag == etree . Comment :
n . text = ' '
continue
2009-04-28 10:24:21 +00:00
fl = self . _flowable ( n , extra_style = paraStyle )
if isinstance ( fl , list ) :
flow + = fl
else :
2008-07-22 14:24:36 +00:00
flow . append ( fl )
2009-04-28 10:24:21 +00:00
2008-07-22 14:24:36 +00:00
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
2009-04-28 10:24:21 +00:00
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 ( ' , ' ) ]
2008-07-22 14:24:36 +00:00
if len ( rowheights ) == 1 :
rowheights = rowheights [ 0 ]
2008-09-08 08:30:33 +00:00
table = platypus . LongTable ( data = data , colWidths = colwidths , rowHeights = rowheights , * * ( utils . attr_get ( node , [ ' splitByRow ' ] , { ' repeatRows ' : ' int ' , ' repeatCols ' : ' int ' } ) ) )
2009-04-28 10:24:21 +00:00
if node . get ( ' style ' ) :
table . setStyle ( self . styles . table_styles [ node . get ( ' style ' ) ] )
2008-07-22 14:24:36 +00:00
for s in styles :
table . setStyle ( s )
return table
def _illustration ( self , node ) :
2010-12-03 16:45:53 +00:00
return _rml_Illustration ( node , self . localcontext , self . styles , self )
2008-07-22 14:24:36 +00:00
def _textual_image ( self , node ) :
2009-04-28 10:24:21 +00:00
return base64 . decodestring ( node . text )
2008-07-22 14:24:36 +00:00
2010-11-02 08:00:19 +00:00
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 )
2008-07-22 14:24:36 +00:00
def _flowable ( self , node , extra_style = None ) :
2010-11-02 08:00:19 +00:00
if node . tag == ' pto ' :
return self . _pto ( node )
2009-04-28 10:24:21 +00:00
if node . tag == ' para ' :
2008-07-22 14:24:36 +00:00
style = self . styles . para_style_get ( node )
if extra_style :
style . __dict__ . update ( extra_style )
2009-04-28 10:24:21 +00:00
result = [ ]
[FIX] report: break lines and splitted words with reportlab > 3.0
From reportlab 3.0, empty plaintext paragraphs do not lead to a break line anymore.
Before release 3.0, paragraphs having tags but no plaintext leaded to a break line.
This patch aims to recover the behavior of reportlab releases < 3.0, as
<para><font color="white"> </font></para> is used in allmost all rml reports
The current patch is not considered as clean, but we did not find any better solution.
If someone find a parameter to pass to reportlab in order to bring back the old behavior of reportlab
he is welcome to provide the better patch.
Besides, in reportlab 3.0, splitlongwords has been introduced as parameter,
to allow to break long words. The default value is True.
This parameter seems to break the columns headers
(it splits the text within the column header)
We therefore take the choice to not activate it, as it was not present anyway in reportlab < 3.0
To test the good behavior of this patch:
While having reportlab < 3.0 (2.5 for instance), print a draft invoice
Then, upgrade to reportlab > 3.0 (3.1.8 for instance), print the same draft invoice.
The generated pdf must be (allmost) identical, in particular concerning spaces.
Specifically, the space between the partner address and his phone.
2015-01-22 13:14:36 +00:00
tag_text = ' '
plain_text = ' '
2009-04-28 10:24:21 +00:00
for i in self . _textual ( node ) . split ( ' \n ' ) :
[FIX] report: break lines and splitted words with reportlab > 3.0
From reportlab 3.0, empty plaintext paragraphs do not lead to a break line anymore.
Before release 3.0, paragraphs having tags but no plaintext leaded to a break line.
This patch aims to recover the behavior of reportlab releases < 3.0, as
<para><font color="white"> </font></para> is used in allmost all rml reports
The current patch is not considered as clean, but we did not find any better solution.
If someone find a parameter to pass to reportlab in order to bring back the old behavior of reportlab
he is welcome to provide the better patch.
Besides, in reportlab 3.0, splitlongwords has been introduced as parameter,
to allow to break long words. The default value is True.
This parameter seems to break the columns headers
(it splits the text within the column header)
We therefore take the choice to not activate it, as it was not present anyway in reportlab < 3.0
To test the good behavior of this patch:
While having reportlab < 3.0 (2.5 for instance), print a draft invoice
Then, upgrade to reportlab > 3.0 (3.1.8 for instance), print the same draft invoice.
The generated pdf must be (allmost) identical, in particular concerning spaces.
Specifically, the space between the partner address and his phone.
2015-01-22 13:14:36 +00:00
instance = platypus . Paragraph ( i , style , * * ( utils . attr_get ( node , [ ] , { ' bulletText ' : ' str ' } ) ) )
plain_text + = instance . getPlainText ( ) . strip ( )
tag_text + = instance . text . strip ( )
result . append ( instance )
if LooseVersion ( reportlab . Version ) > LooseVersion ( ' 3.0 ' ) and not plain_text and tag_text :
result . append ( platypus . Paragraph ( ' <br/> ' , style , * * ( utils . attr_get ( node , [ ] , { ' bulletText ' : ' str ' } ) ) ) )
2009-04-28 10:24:21 +00:00
return result
elif node . tag == ' barCode ' :
2008-07-22 14:24:36 +00:00
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
2010-12-19 20:11:27 +00:00
from reportlab . graphics . barcode import createBarcodeDrawing
2010-12-20 17:58:02 +00:00
2010-12-19 20:11:27 +00:00
except ImportError :
2012-01-24 12:55:12 +00:00
_logger . warning ( " Cannot use barcode renderers: " , exc_info = True )
2008-07-22 14:24:36 +00:00
return None
2009-07-01 08:28:50 +00:00
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 ' } )
2008-07-22 14:24:36 +00:00
codes = {
' codabar ' : lambda x : common . Codabar ( x , * * args ) ,
' code11 ' : lambda x : common . Code11 ( x , * * args ) ,
2010-12-19 20:11:27 +00:00
' code128 ' : lambda x : code128 . Code128 ( str ( x ) , * * args ) ,
' standard39 ' : lambda x : code39 . Standard39 ( str ( x ) , * * args ) ,
' standard93 ' : lambda x : code93 . Standard93 ( str ( x ) , * * args ) ,
2008-07-22 14:24:36 +00:00
' i2of5 ' : lambda x : common . I2of5 ( x , * * args ) ,
2010-12-19 20:11:27 +00:00
' extended39 ' : lambda x : code39 . Extended39 ( str ( x ) , * * args ) ,
' extended93 ' : lambda x : code93 . Extended93 ( str ( x ) , * * args ) ,
2008-07-22 14:24:36 +00:00
' msi ' : lambda x : common . MSI ( x , * * args ) ,
' fim ' : lambda x : usps . FIM ( x , * * args ) ,
' postnet ' : lambda x : usps . POSTNET ( x , * * args ) ,
2010-12-19 20:11:27 +00:00
' ean13 ' : lambda x : createBarcodeDrawing ( ' EAN13 ' , value = str ( x ) , * * args ) ,
' qrcode ' : lambda x : createBarcodeDrawing ( ' QR ' , value = x , * * args ) ,
2008-07-22 14:24:36 +00:00
}
code = ' code128 '
2009-04-28 10:24:21 +00:00
if node . get ( ' code ' ) :
code = node . get ( ' code ' ) . lower ( )
2008-07-22 14:24:36 +00:00
return codes [ code ] ( self . _textual ( node ) )
2009-04-28 10:24:21 +00:00
elif node . tag == ' name ' :
self . styles . names [ node . get ( ' id ' ) ] = node . get ( ' value ' )
2008-07-22 14:24:36 +00:00
return None
2009-04-28 10:24:21 +00:00
elif node . tag == ' xpre ' :
2008-07-22 14:24:36 +00:00
style = self . styles . para_style_get ( node )
return platypus . XPreformatted ( self . _textual ( node ) , style , * * ( utils . attr_get ( node , [ ] , { ' bulletText ' : ' str ' , ' dedent ' : ' int ' , ' frags ' : ' int ' } ) ) )
2009-04-28 10:24:21 +00:00
elif node . tag == ' pre ' :
2008-07-22 14:24:36 +00:00
style = self . styles . para_style_get ( node )
return platypus . Preformatted ( self . _textual ( node ) , style , * * ( utils . attr_get ( node , [ ] , { ' bulletText ' : ' str ' , ' dedent ' : ' int ' } ) ) )
2009-04-28 10:24:21 +00:00
elif node . tag == ' illustration ' :
2008-07-22 14:24:36 +00:00
return self . _illustration ( node )
2009-04-28 10:24:21 +00:00
elif node . tag == ' blockTable ' :
2008-07-22 14:24:36 +00:00
return self . _table ( node )
2009-04-28 10:24:21 +00:00
elif node . tag == ' title ' :
2008-07-22 14:24:36 +00:00
styles = reportlab . lib . styles . getSampleStyleSheet ( )
style = styles [ ' Title ' ]
return platypus . Paragraph ( self . _textual ( node ) , style , * * ( utils . attr_get ( node , [ ] , { ' bulletText ' : ' str ' } ) ) )
2010-04-02 08:49:26 +00:00
elif re . match ( ' ^h([1-9]+[0-9]*)$ ' , ( node . tag or ' ' ) ) :
2008-07-22 14:24:36 +00:00
styles = reportlab . lib . styles . getSampleStyleSheet ( )
2010-04-02 08:49:26 +00:00
style = styles [ ' Heading ' + str ( node . tag [ 1 : ] ) ]
2008-07-22 14:24:36 +00:00
return platypus . Paragraph ( self . _textual ( node ) , style , * * ( utils . attr_get ( node , [ ] , { ' bulletText ' : ' str ' } ) ) )
2009-04-28 10:24:21 +00:00
elif node . tag == ' image ' :
2010-12-03 16:45:53 +00:00
image_data = False
2009-04-28 10:24:21 +00:00
if not node . get ( ' file ' ) :
if node . get ( ' name ' ) :
2010-12-03 16:45:53 +00:00
if node . get ( ' name ' ) in self . doc . images :
2012-01-24 12:55:12 +00:00
_logger . debug ( " Image %s read " , node . get ( ' name ' ) )
2010-12-03 16:45:53 +00:00
image_data = self . doc . images [ node . get ( ' name ' ) ] . read ( )
else :
2012-01-24 12:55:12 +00:00
_logger . warning ( " Image %s not defined " , node . get ( ' name ' ) )
2010-12-03 16:45:53 +00:00
return False
2008-07-22 14:24:36 +00:00
else :
import base64
2011-05-25 08:09:21 +00:00
newtext = node . text
2009-05-22 06:05:41 +00:00
if self . localcontext :
newtext = utils . _process_text ( self , node . text or ' ' )
2011-05-19 12:59:41 +00:00
image_data = base64 . decodestring ( newtext )
2010-12-03 16:45:53 +00:00
if not image_data :
2012-01-24 12:55:12 +00:00
_logger . debug ( " No inline image data " )
2010-12-03 16:45:53 +00:00
return False
2010-11-23 15:14:44 +00:00
image = StringIO ( image_data )
2008-07-22 14:24:36 +00:00
else :
2012-01-24 12:55:12 +00:00
_logger . debug ( " Image get from file %s " , node . get ( ' file ' ) )
2010-12-03 16:45:53 +00:00
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 ' ] ) ) )
2009-04-28 10:24:21 +00:00
elif node . tag == ' spacer ' :
if node . get ( ' width ' ) :
width = utils . unit_get ( node . get ( ' width ' ) )
2008-07-22 14:24:36 +00:00
else :
width = utils . unit_get ( ' 1cm ' )
2009-04-28 10:24:21 +00:00
length = utils . unit_get ( node . get ( ' length ' ) )
2008-07-22 14:24:36 +00:00
return platypus . Spacer ( width = width , height = length )
2009-04-28 10:24:21 +00:00
elif node . tag == ' section ' :
2008-07-22 14:24:36 +00:00
return self . render ( node )
2009-04-28 10:24:21 +00:00
elif node . tag == ' pageNumberReset ' :
2008-07-22 14:24:36 +00:00
return PageReset ( )
2009-04-28 10:24:21 +00:00
elif node . tag in ( ' pageBreak ' , ' nextPage ' ) :
2008-07-22 14:24:36 +00:00
return platypus . PageBreak ( )
2009-04-28 10:24:21 +00:00
elif node . tag == ' condPageBreak ' :
2008-07-22 14:24:36 +00:00
return platypus . CondPageBreak ( * * ( utils . attr_get ( node , [ ' height ' ] ) ) )
2009-04-28 10:24:21 +00:00
elif node . tag == ' setNextTemplate ' :
return platypus . NextPageTemplate ( str ( node . get ( ' name ' ) ) )
elif node . tag == ' nextFrame ' :
2008-07-22 14:24:36 +00:00
return platypus . CondPageBreak ( 1000 ) # TODO: change the 1000 !
2009-04-28 10:24:21 +00:00
elif node . tag == ' setNextFrame ' :
2008-07-22 14:24:36 +00:00
from reportlab . platypus . doctemplate import NextFrameFlowable
2009-04-28 10:24:21 +00:00
return NextFrameFlowable ( str ( node . get ( ' name ' ) ) )
elif node . tag == ' currentFrame ' :
2008-07-22 14:24:36 +00:00
from reportlab . platypus . doctemplate import CurrentFrameFlowable
2009-04-28 10:24:21 +00:00
return CurrentFrameFlowable ( str ( node . get ( ' name ' ) ) )
elif node . tag == ' frameEnd ' :
2008-07-22 14:24:36 +00:00
return EndFrameFlowable ( )
2009-04-28 10:24:21 +00:00
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 '
2008-10-22 09:55:01 +00:00
return platypus . flowables . HRFlowable ( width = width_hr , color = color . get ( color_hr ) , thickness = float ( thickness_hr ) , lineCap = str ( lineCap_hr ) )
2008-07-22 14:24:36 +00:00
else :
2009-04-28 10:24:21 +00:00
sys . stderr . write ( ' Warning: flowable not yet implemented: %s ! \n ' % ( node . tag , ) )
2008-07-22 14:24:36 +00:00
return None
def render ( self , node_story ) :
2009-04-28 10:24:21 +00:00
def process_story ( node_story ) :
sub_story = [ ]
for node in utils . _child_get ( node_story , self ) :
2009-05-25 06:42:45 +00:00
if node . tag == etree . Comment :
node . text = ' '
continue
2008-08-11 09:27:55 +00:00
flow = self . _flowable ( node )
2008-07-22 14:24:36 +00:00
if flow :
2009-04-28 10:24:21 +00:00
if isinstance ( flow , list ) :
sub_story = sub_story + flow
2008-07-22 14:24:36 +00:00
else :
2009-04-28 10:24:21 +00:00
sub_story . append ( flow )
return sub_story
return process_story ( node_story )
2006-12-07 13:41:40 +00:00
2006-12-08 09:07:43 +00:00
class EndFrameFlowable ( ActionFlowable ) :
2008-07-22 14:24:36 +00:00
def __init__ ( self , resume = 0 ) :
ActionFlowable . __init__ ( self , ( ' frameEnd ' , resume ) )
2006-12-08 09:07:43 +00:00
class TinyDocTemplate ( platypus . BaseDocTemplate ) :
2012-07-16 07:52:17 +00:00
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
2008-07-22 14:24:36 +00:00
def ___handle_pageBegin ( self ) :
2012-12-14 13:44:55 +00:00
self . page + = 1
2008-07-22 14:24:36 +00:00
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 ( )
2012-07-16 07:52:17 +00:00
def afterPage ( self ) :
2014-10-21 08:54:23 +00:00
if isinstance ( self . canv , NumberedCanvas ) :
# save current page states before eventual reset
self . canv . _saved_page_states . append ( dict ( self . canv . __dict__ ) )
2012-07-16 07:52:17 +00:00
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
2008-07-22 14:24:36 +00:00
self . canv . _pageNumber = 0
2012-07-16 07:52:17 +00:00
self . canv . _doPageReset = False
self . canv . _storyCount + = 1
2006-12-08 09:07:43 +00:00
2006-12-07 13:41:40 +00:00
class _rml_template ( object ) :
2011-11-07 15:45:56 +00:00
def __init__ ( self , localcontext , out , node , doc , images = None , path = ' . ' , title = None ) :
if images is None :
images = { }
2010-10-06 13:25:34 +00:00
if not localcontext :
2010-10-07 09:51:27 +00:00
localcontext = { ' internal_header ' : True }
2009-04-28 10:24:21 +00:00
self . localcontext = localcontext
2008-07-22 14:24:36 +00:00
self . images = images
self . path = path
2008-09-24 15:34:10 +00:00
self . title = title
2010-10-05 07:27:09 +00:00
2011-12-21 09:24:46 +00:00
pagesize_map = { ' a4 ' : A4 ,
' us_letter ' : letter
}
pageSize = A4
if self . localcontext . get ( ' company ' ) :
pageSize = pagesize_map . get ( self . localcontext . get ( ' company ' ) . paper_format , A4 )
if node . get ( ' pageSize ' ) :
2009-04-28 10:24:21 +00:00
ps = map ( lambda x : x . strip ( ) , node . get ( ' pageSize ' ) . replace ( ' ) ' , ' ' ) . replace ( ' ( ' , ' ' ) . split ( ' , ' ) )
2008-07-22 14:24:36 +00:00
pageSize = ( utils . unit_get ( ps [ 0 ] ) , utils . unit_get ( ps [ 1 ] ) )
2009-04-28 10:24:21 +00:00
2010-08-05 02:32:28 +00:00
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 ' } ) )
2008-07-22 14:24:36 +00:00
self . page_templates = [ ]
self . styles = doc . styles
self . doc = doc
2010-10-04 13:35:06 +00:00
self . image = [ ]
2009-04-28 10:24:21 +00:00
pts = node . findall ( ' pageTemplate ' )
2008-07-22 14:24:36 +00:00
for pt in pts :
frames = [ ]
2009-04-28 10:24:21 +00:00
for frame_el in pt . findall ( ' frame ' ) :
2008-07-22 14:24:36 +00:00
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 )
2009-04-28 10:24:21 +00:00
try :
2009-11-28 11:58:09 +00:00
gr = pt . findall ( ' pageGraphics ' ) \
or pt [ 1 ] . findall ( ' pageGraphics ' )
2010-06-22 09:45:40 +00:00
except Exception : # FIXME: be even more specific, perhaps?
2009-04-28 10:24:21 +00:00
gr = ' '
2008-07-22 14:24:36 +00:00
if len ( gr ) :
2010-10-05 07:27:09 +00:00
# self.image=[ n for n in utils._child_get(gr[0], self) if n.tag=='image' or not self.localcontext]
2009-04-28 10:24:21 +00:00
drw = _rml_draw ( self . localcontext , gr [ 0 ] , self . doc , images = images , path = self . path , title = self . title )
2008-07-22 14:24:36 +00:00
self . page_templates . append ( platypus . PageTemplate ( frames = frames , onPage = drw . render , * * utils . attr_get ( pt , [ ] , { ' id ' : ' str ' } ) ) )
else :
2009-04-28 10:24:21 +00:00
drw = _rml_draw ( self . localcontext , node , self . doc , title = self . title )
2008-11-15 06:14:55 +00:00
self . page_templates . append ( platypus . PageTemplate ( frames = frames , onPage = drw . render , * * utils . attr_get ( pt , [ ] , { ' id ' : ' str ' } ) ) )
2008-07-22 14:24:36 +00:00
self . doc_tmpl . addPageTemplates ( self . page_templates )
def render ( self , node_stories ) :
2010-10-07 10:11:27 +00:00
if self . localcontext and not self . localcontext . get ( ' internal_header ' , False ) :
del self . localcontext [ ' internal_header ' ]
2008-07-22 14:24:36 +00:00
fis = [ ]
2013-04-08 13:25:10 +00:00
r = _rml_flowable ( self . doc , self . localcontext , images = self . images , path = self . path , title = self . title , canvas = None )
2009-06-15 06:35:23 +00:00
story_cnt = 0
2008-07-22 14:24:36 +00:00
for node_story in node_stories :
2009-08-27 10:04:54 +00:00
if story_cnt > 0 :
fis . append ( platypus . PageBreak ( ) )
2008-07-22 14:24:36 +00:00
fis + = r . render ( node_story )
2014-10-21 08:54:23 +00:00
# end of story numbering computation
fis . append ( PageReset ( ) )
2009-08-27 10:04:54 +00:00
story_cnt + = 1
2013-11-12 13:04:12 +00:00
try :
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 )
except platypus . doctemplate . LayoutError , e :
e . name = ' Print Error '
e . value = ' The document you are trying to print contains a table row that does not fit on one page. Please try to split it in smaller rows or contact your administrator. '
raise
2006-12-07 13:41:40 +00:00
2011-11-07 15:45:56 +00:00
def parseNode ( rml , localcontext = None , fout = None , images = None , path = ' . ' , title = None ) :
2009-04-28 10:24:21 +00:00
node = etree . XML ( rml )
r = _rml_doc ( node , localcontext , images , path , title = title )
2009-05-06 21:36:12 +00:00
#try to override some font mappings
try :
2009-11-24 14:44:05 +00:00
from customfonts import SetCustomFonts
SetCustomFonts ( r )
2010-11-23 15:11:24 +00:00
except ImportError :
# means there is no custom fonts mapping in this system.
pass
2010-06-22 09:45:40 +00:00
except Exception :
2012-01-24 12:55:12 +00:00
_logger . warning ( ' Cannot set font mapping ' , exc_info = True )
2009-11-24 14:44:05 +00:00
pass
2010-11-23 15:14:44 +00:00
fp = StringIO ( )
2009-04-28 10:24:21 +00:00
r . render ( fp )
return fp . getvalue ( )
2011-11-07 15:45:56 +00:00
def parseString ( rml , localcontext = None , fout = None , images = None , path = ' . ' , title = None ) :
2009-04-28 10:24:21 +00:00
node = etree . XML ( rml )
r = _rml_doc ( node , localcontext , images , path , title = title )
2009-05-06 20:18:13 +00:00
2009-02-07 19:26:06 +00:00
#try to override some font mappings
try :
2009-11-24 14:44:05 +00:00
from customfonts import SetCustomFonts
SetCustomFonts ( r )
2010-06-22 09:45:40 +00:00
except Exception :
2009-11-24 14:44:05 +00:00
pass
2009-05-06 20:18:13 +00:00
2008-07-22 14:24:36 +00:00
if fout :
fp = file ( fout , ' wb ' )
r . render ( fp )
fp . close ( )
return fout
else :
2010-11-23 15:14:44 +00:00
fp = StringIO ( )
2008-07-22 14:24:36 +00:00
r . render ( fp )
return fp . getvalue ( )
2006-12-07 13:41:40 +00:00
def trml2pdf_help ( ) :
2008-07-22 14:24:36 +00:00
print ' Usage: trml2pdf input.rml >output.pdf '
print ' Render the standard input (RML) and output a PDF file '
sys . exit ( 0 )
2006-12-07 13:41:40 +00:00
if __name__ == " __main__ " :
2008-07-22 14:24:36 +00:00
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. '
2008-07-23 15:01:27 +00:00
2011-11-22 08:58:48 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: