reportlab-3.3.0/0000755000175000017500000000000012661063767013057 5ustar rptlabrptlabreportlab-3.3.0/docs/0000755000175000017500000000000012314632712013772 5ustar rptlabrptlabreportlab-3.3.0/docs/reference/0000755000175000017500000000000012661063723015735 5ustar rptlabrptlabreportlab-3.3.0/docs/reference/genreference.py0000664000175000017500000000232112661063723020737 0ustar rptlabrptlab#!/bin/env python #Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/reference/genreference.py __version__='3.3.0' __doc__ = """ This module contains the script for building the reference. """ def run(verbose=None, outDir=None): import os, sys, shutil if verbose is None: verbose=('-s' not in sys.argv) cwd = os.getcwd() docsDir=os.path.dirname(os.path.dirname(sys.argv[0]) or cwd) topDir=os.path.dirname(docsDir) sys.path.insert(0,topDir) from tools.docco import yaml2pdf yaml2pdf.run('reference.yml','reportlab-reference.pdf') if verbose: print('Saved reportlab-reference.pdf') if not outDir: outDir = os.path.join(topDir,'docs') destfn = os.path.join(outDir,'reportlab-reference.pdf') shutil.copyfile('reportlab-reference.pdf', destfn) if verbose: print('copied to %s' % destfn) def makeSuite(): "standard test harness support - run self as separate process" from tests.utils import ScriptThatMakesFileTest return ScriptThatMakesFileTest('../docs/reference', 'genreference.py', 'reportlab-reference.pdf') if __name__=='__main__': run() reportlab-3.3.0/docs/reference/reference.yml0000644000175000017500000002330712120332300020377 0ustar rptlabrptlab.t ReportLab API Reference .nextPageTemplate Normal .h1 Introduction This is the API reference for the ReportLab library. All public classes, functions and methods are documented here. Most of the reference text is built automatically from the documentation strings in each class, method and function. That's why it uses preformatted text and doesn't look very pretty. Please note the following points: .bu ()Items with one leading underscore are considered private to the modules they are defined in; they are not documented here and we make no commitment to their maintenance. .bu ()Items ending in a digit (usually zero) are experimental; they are released to allow widespread testing, but are guaranteed to be broken in future (if only by dropping the zero). By all means play with these and give feedback, but do not use them in production scripts. .h2 Package Architecture The reportlab package is broken into a number of subpackages. These are as follows: .df reportlab.pdfgen - this is the programming interface to the PDF file format. The Canvas (and its co-workers, TextObject and PathObject) provide everything you need to create PDF output working at a low level - individual shapes and lines of text. Internally, it constructs blocks of page marking operators which match your drawing commands, and hand them over to the pdfbase package for drawing. .df reportlab.pdfbase - this is not part of the public interface. It contains code to handle the 'outer structure' of PDF files, and utilities to handle text metrics and compressed streams. .df reportlab.platypus - PLATYPUS stands for "Page Layout and Typography Using Scripts". It provides a higher level of abstraction dealing with paragraphs, frames on the page, and document templates. This is used for multi- page documents such as this reference. .df reportlab.lib - this contains code of interest to application developers which cuts across both of our libraries, such as standard colors, units, and page sizes. It will also contain more drawable and flowable objects in future. There is also a demos directory containing various demonstrations, and a docs directory. These can be accessed with package notation but should not be thought of as packages. Each package is documented in turn. .pageBreak .h1 reportlab.pdfgen subpackage .h2 reportlab.pdfgen.canvas module .getClassDoc reportlab.pdfgen.canvas Canvas .pageBreak .h2 reportlab.pdfgen.pathobject module The method Canvas.beginPath allows users to construct a PDFPathObject, which is defined in reportlab/pdfgen/pathobject.py. .getClassDoc reportlab.pdfgen.pathobject PDFPathObject .pageBreak .h2 reportlab.pdfgen.textobject module The method Canvas.beginText allows users to construct a PDFTextObject, which is defined in reportlab/pdfgen/textobject.py. .getClassDoc reportlab.pdfgen.textobject PDFTextObject .pageBreak .h2 reportlab.pdfgen.pdfgeom module .getModuleDoc reportlab.pdfgen.pdfgeom .h2 reportlab.pdfgen.pdfimages module .getModuleDoc reportlab.pdfgen.pdfimages .h2 reportlab.pdfgen.pycanvas module .getModuleDoc reportlab.pdfgen.pycanvas .pageBreak .h1 reportlab.platypus subpackage The platypus package defines our high-level page layout API. The division into modules is far from final and has been based more on balancing the module lengths than on any particular programming interface. The __init__ module imports the key classes into the top level of the package. .h2 Overall Structure Abstractly Platypus currently can be thought of has having four levels: documents, pages, frames and flowables (things which can fit into frames in some way). In practice there is a fifth level, the canvas, so that if you want you can do anything that pdfgen's canvas allows. .h2 Document Templates .h3 BaseDocTemplate The basic document template class; it provides for initialisation and rendering of documents. A whole bunch of methods handle_XXX handle document rendering events. These event routines all contain some significant semantics so while these may be overridden that may require some detailed knowledge. Some other methods are completely virtual and are designed to be overridden. .h3 BaseDocTemplate .getClassDoc reportlab.platypus.doctemplate BaseDocTemplate .pageBreak A simple document processor can be made using derived class, SimpleDocTemplate. .pageBreak .h3 SimpleDocTemplate .getClassDoc reportlab.platypus.doctemplate SimpleDocTemplate .pageBreak .h2 Flowables .getClassDoc reportlab.platypus.paragraph Paragraph .getClassDoc reportlab.platypus.flowables Flowable .getClassDoc reportlab.platypus.flowables XBox .getClassDoc reportlab.platypus.flowables Preformatted .getClassDoc reportlab.platypus.flowables Image .getClassDoc reportlab.platypus.flowables NullDraw .getClassDoc reportlab.platypus.flowables Spacer .getClassDoc reportlab.platypus.flowables UseUpSpace .getClassDoc reportlab.platypus.flowables PageBreak .getClassDoc reportlab.platypus.flowables SlowPageBreak .getClassDoc reportlab.platypus.flowables CondPageBreak .getClassDoc reportlab.platypus.flowables KeepTogether .getClassDoc reportlab.platypus.flowables Macro .getClassDoc reportlab.platypus.flowables CallerMacro .getClassDoc reportlab.platypus.flowables ParagraphAndImage .getClassDoc reportlab.platypus.flowables KeepInFrame .getClassDoc reportlab.platypus.flowables ImageAndFlowables .getClassDoc reportlab.platypus.flowables AnchorFlowable .getClassDoc reportlab.platypus.flowables FrameSplitter .getClassDoc reportlab.platypus.tableofcontents TableOfContents .getClassDoc reportlab.platypus.tableofcontents SimpleIndex .getClassDoc reportlab.platypus.xpreformatted XPreformatted .getClassDoc reportlab.platypus.xpreformatted PythonPreformatted .pageBreak .h1 reportlab.lib subpackage This package contains a number of modules which either add utility to pdfgen and platypus, or which are of general use in graphics applications. .h2 reportlab.lib.colors module .getModuleDoc reportlab.lib.colors .h2 reportlab.lib.corp module .getModuleDoc reportlab.lib.corp .h2 reportlab.lib.enums module .getModuleDoc reportlab.lib.enums .h2 reportlab.lib.fonts module .getModuleDoc reportlab.lib.fonts .h2 reportlab.lib.pagesizes module .getModuleDoc reportlab.lib.pagesizes .h2 reportlab.lib.sequencer module .getModuleDoc reportlab.lib.sequencer .h2 reportlab.lib.abag module .getModuleDoc reportlab.lib.abag .h2 reportlab.lib.attrmap module .getModuleDoc reportlab.lib.attrmap .h2 reportlab.lib.boxstuff module .getModuleDoc reportlab.lib.boxstuff .h2 reportlab.lib.codecharts module .getModuleDoc reportlab.lib.codecharts .h2 reportlab.lib.extformat module .getModuleDoc reportlab.lib.extformat .h2 reportlab.lib.fontfinder module .getModuleDoc reportlab.lib.fontfinder .h2 reportlab.lib.formatters module .getModuleDoc reportlab.lib.formatters .h2 reportlab.lib.geomutils module .getModuleDoc reportlab.lib.geomutils .h2 reportlab.lib.logger module .getModuleDoc reportlab.lib.logger .h2 reportlab.lib.normalDate module .getModuleDoc reportlab.lib.normalDate .h2 reportlab.lib.pdfencrypt module .getModuleDoc reportlab.lib.pdfencrypt .h2 reportlab.lib.PyFontify module .getModuleDoc reportlab.lib.PyFontify .h2 reportlab.lib.randomtext module .getModuleDoc reportlab.lib.randomtext .h2 reportlab.lib.rltempfile module .getModuleDoc reportlab.lib.rltempfile .h2 reportlab.lib.rparsexml module .getModuleDoc reportlab.lib.rparsexml .h2 reportlab.lib.set_ops module .getModuleDoc reportlab.lib.set_ops .h2 reportlab.lib.styles module .getModuleDoc reportlab.lib.styles .h2 reportlab.lib.testutils module .getModuleDoc reportlab.lib.testutils .h2 reportlab.lib.textsplit module .getModuleDoc reportlab.lib.textsplit .h2 reportlab.lib.units module .getModuleDoc reportlab.lib.units .h2 reportlab.lib.utils module .getModuleDoc reportlab.lib.utils .h2 reportlab.lib.validators module .getModuleDoc reportlab.lib.validators .h2 reportlab.lib.xmllib module .getModuleDoc reportlab.lib.xmllib .h2 reportlab.lib.yaml module .getModuleDoc reportlab.lib.yaml .pageBreak .h1 Appendix A - CVS Revision History .beginPre Code $Log: reference.yml,v $ Revision 1.1 2001/10/05 12:33:33 rgbecker Moved from original project docs, history lost Revision 1.13 2001/08/30 10:32:38 dinu_gherman Added missing flowables. Revision 1.12 2001/07/11 09:21:27 rgbecker Typo fix from Jerome Alet Revision 1.11 2000/07/10 23:56:09 andy_robinson Paragraphs chapter pretty much complete. Fancy cover. Revision 1.10 2000/07/03 15:39:51 rgbecker Documentation fixes Revision 1.9 2000/06/28 14:52:43 rgbecker Documentation changes Revision 1.8 2000/06/19 23:52:31 andy_robinson rltemplate now simple, based on UserDocTemplate Revision 1.7 2000/06/17 07:46:45 andy_robinson Small text changes Revision 1.6 2000/06/14 21:22:52 andy_robinson Added docs for library Revision 1.5 2000/06/12 11:26:34 andy_robinson Numbered list added Revision 1.4 2000/06/12 11:13:09 andy_robinson Added sequencer tags to paragraph parser Revision 1.3 2000/06/09 01:44:24 aaron_watters added automatic generation for pathobject and textobject modules. Revision 1.2 2000/06/07 13:39:22 andy_robinson Added some text to the first page of reference, and a build batch file Revision 1.1.1.1 2000/06/05 16:39:04 andy_robinson initial import .endPre reportlab-3.3.0/docs/reference/build.bat0000755000175000017500000000015612120332300017505 0ustar rptlabrptlabdel reportlab-reference.pdf python ..\tools\yaml2pdf.py reportlab-reference.yml start reportlab-reference.pdf reportlab-3.3.0/docs/00readme.txt0000644000175000017500000000047112120332300016113 0ustar rptlabrptlabThid directory holds documentation. For end users, it should contain a number of PDF manuals. For people working with the source, this directory will be the destination for any manuals built. If you don't see the pdf manual you expected or you wich to ensure an up to date copy run the script tools/genAll.py! reportlab-3.3.0/docs/make.bat0000644000175000017500000000601112120332300015356 0ustar rptlabrptlab@ECHO OFF REM Command file for Sphinx documentation set SPHINXBUILD=sphinx-build set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\reportlab.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\reportlab.ghc goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end reportlab-3.3.0/docs/images/0000755000175000017500000000000012120332300015220 5ustar rptlabrptlabreportlab-3.3.0/docs/images/replogo.a850000644000175000017500000006366412120332300017225 0ustar rptlabrptlabBI /W 283 /H 181 /BPC 8 /CS /RGB /F [/A85 /Fl] ID Gb"/lGEc',,kl8"&2NBd%Kaogum165&NYZBITmkcQDo:\lEnA GJETphXmY&(BXdG&0O5g!!*-(#S8+DJ,fTO":,P]5_&h8!X&eu$f\(VI^5_q jm)jbosgIBN01+GE&G/,0E`$J,.ogcq?_*e\?bYbO$E4*_M&A1cC@I!9Dd\' +4?4QDr'3BbF=2qLTa,V3(b)tC;HhK!DHiEs= #e!]_gC"9\WeUZ]%d_rZDm+pZHhMjerV*pJn(tTrkH8+&G'_fGA&n<1gUDI\ )T%dl>;gEVr7uSSD=[2`\)2)Y\8d.Vp$:55>ISM;g9k_IXBE5_odUJ&4fhbj +t4u*CCeUS/R,f.kn4Mb(GB+%B[KmgIC4%Q_Cr\%1,1M'_1Dj^kKfbZDr.!E F(WairP]R[bk1DXB:hnj5(2_GWLnY9BgP/'PUTNpnM"tB`!I)(N#O]nba:,[ 7un]KC=L=e,=d\00l5LHTgOSF))>aLTKrYb%NI015l^i^qt9:L_$;(Sl-nh/ pYP5l]D"DU][No_Y;b?do<#LYHN!Ng-V^$t+XoX`J[UDW6NstgiU^:#aXJYf @T/-h#kbk9#UMApV<$^um2e#j'7`g:&J1#=2rAubQ**M!it6f3,*@pVZY%I6 6o/H!J4.k`9lkR_-;U4ln3a&Bj2[3$3u/IFT]*eDlR2scpu.!iLu\C$^CPtn H@SNS^AI?QDnk,W"$D00h*&FIlDn4&G3't(Uo5[]m!,=p/AQ+U,=dd\o,oLh `[7$Kifm(afHdLC\5iol9&c'@AXlrl+e2TcVCt*"D43P1/tBd4IK''*Z:Y1g &tsE#;eD`AXDo:8;HP?kPAJ]<++5EP`)3&A0_Gc=bj+0GNAo^=jcrnRI.>2- ?@.[$o*RI.f9aJ`&3E/CRVaf0!I//;.6.N@dhWJ":#_Dm4P7SLk/f_K3$fY" @lB&>=ZT!#RpKMq"Z6@j)ODfZ2)o?9+0oW[2)q0V"P i5SJT2Aq@18&2N,MORue((.$!o41l.*at,LGSoZG'&i)k+_>o\;RS\8iUnSbf.@["h4W^daDC< [+g5DH18l\"e-&NZV?P&#\?tUg#Q>8N;^H/+c'&47a2<,ZndtDk/m0)o'Y9I "Af)p(^a#(U5qJ$"NO<.;GoitP^.[:Han@R-"EJk':6\TnrLq,.c]R(i1M-HV6>$DWiJnWu`pbXf8^#tsp$Blf[o2f0&&E8Fj;UkH;2KPeTN'9<-` _E>O(dS$eNYO@/]?Jm>IbNhuC)(3i7QGjt[o_n[]],>Y46C;2%ZOfU,n6RL6 pQXGh.Ts48AWn&r=TE#APblWI!jfTH5qs'iZ7I([Rcm#)4]s3/IWG@.!hl8% k"5Ai2hk31"fITC&?Wl%R3r7DZT<9r#jE`;DgM?VnOPTHJ6NEF=DX0oV=;jR N"ee-'gN(,,1IMA,*\r!P!#ZJb/Yi%P$?h-bS)ApoF1$2S`.D2UXU#ifa/5t gE^EM\-Jt.FMpZj7&UVY^aA_J@B/D`\QM+D5?Y%:r8SkQX)mILmG#,5Qa$@m 8\`%IN+JM>F2lkn-6bE9+'C"P4$:%88J]Hko.1njrgkG16n$[KbII#_p%Qu7 AE+ku0`TXT)a,W-TbqN<#jU93V[&HNckm'+eCt(+$2Wka!3/NpDgDtsPjuY! &Osl;iABPJ;B\[/"UG*'SNM&U8$BW">.&+qW`H6*^OH,o4LAeL 6:XCa%o6PiB$Hd`4)!?TBp0l=C<`"Pp/ m\$/,\foSn:tn@>>.&+rXN:c=IYfebFG+3NbiMdW>Wh$%Tb9lJ,Z=l?0N&Gh %hOq[K8PLeJg*I1A&jUDW1GZEB0I@H?K&bTn$qD<]C*82Gk'c!.Um,ORPB1S X.(7fr^"6`JoT&6a&>eUf-b?fn#),:EYL950ZtXh\U7\pG(iO\9lOq# XrZT?%Us\o*&sSo7H!XR3"?ul#`4f!Zb])99lfrHjSo#-^]+6,^OPUTa&(g] ,XjL"\_WnqMA+tu)_%J0.!LUQk"/iU(.j?`YBf'ePuh98R"@p`^A)0l[V`R2 gPKuukg6#2UgK&_!SG#.Y9G=NE!Qjc;%hIeB-:05)Dr'p+snP*bJ%1&i@((p'?IgAp1[a)GE\GTFH2IL`l^S2o(A6m?6E<2l$L ;Y'R(,(@?ZKiF0"q<+@tT&PK,F%g#(oSZil5CQj_]O[q`Br_?MX^AjBUQ7T" @`4'cHhKk=]U=,@T<#7>mE&],fEsJ/^mkC9$&OG7q&6HTG\$(lqac@H(VP3,j+0.uF2VMDhWa371^%^Af^3o`WX]fl=THUV8 ;J-N15UCZnZ$lNCS@!1iRd72#ac[r!.4H&c>e#3_/&6:Xk!k\T$ULAU46i)h KH%IBj[)gcEr,Fm8/F9"JG-bqWJ=P0'Sa_a8O+\a'e<<9=gB:gLMs_TH*TS32)&7f!:AMCG&[J)1AofJl$\;g^ZSq!^Cknt;Dc rq^D)=Ld1@AECt!d)V]q@7Ekg^RdT8Q7,V)DV_mO20\KDKS0K@4tMr8-me_+ 6ALSS;34Fo$;PSrC"&qrfWZNdMAT:eiJ/N9h/.>n:)McoCDR=g&EbB?cmQGOOBGCXu1L3)ObU2-@^S6cq05 nc5;/mQ%?`p.hBGdaHQ]G81hMC-X9k5CORVD$e$*2eaH?-X<`[^HO@dZM4=l hab9!R=Or%o^Ut^'0p7ELITD^<^"+ Y!l5XU?A.u?="`9krB9aJ"#6+F6Cj4:db>5(9+:4@Z;6SRum`F3IA4r#nnWs&Oa4CR=Gqu:(/!q"AuW/k>2KVoY(R4ecMKT7N&8fNoIBR'&aTu _%a8nlP$gt1hjg.A&aJ$sW.\U= KOa]?e`5]MM*PY*DnCl6E32BB-7:0ra%\H4"(mN22(EhnjW*?28pfK@J`[[!MH!B8[HQK"!jf o&R]D%nHJm"5FeBE%?@jo3S$g"8jar0l.N@RCY?!>0pJlFiV":, &g1[(4*Jhq4I[qd=t#2]@%_3GZgHMV8%Q>l$=k!K4DE4hF[E))8cGMmOmSJk?qlY!GSG '_ngSBXGW%WF4CmbBfH-3VQK-:?/N?Na1r%YB8a/6fP63Waj9$Pl!n +l7j>8A\fLp2K7U;6T>E8cDG6!4WXc7"?Hb4Wk7fh,^l Gu-2k?3!Y7cm+Skj[`5QE>n)0T9M@!8U^1;abaC7_SidkoJoUefH/9?E m[7^Z+i%f8.H9CJ_lri9)WY8p75:$OAAe5ELDN`>?bUp7P;"RLL(age=oA'T O+^%JPcaC(_^&i&\OMW$?*I^&jCHZ47FAA,!\WBYV+\4^X;sfD#_(f_`)u%D ioZYkE\3NqppFke.Pj4>CKh1_F!5H&-r[]0L18NU;5m^/LH4?c?Q`Oc+.f> e'lc1r:!(0,K&T&5#R9!O*0a;.9*5W'_;b]%M6T=E8Z8c8n+!r1V8tF-R:'I +$4X^F::4AG[id#s#kMi'IFbKBbr[3@`!p_(!L,Yf9&7*"3cmV!eZ2bigph@OdFSrfZ>_)pa+!F0[?c:Jha3e$ Wb9YLH1U1ceu]n+M;lM9Y4]K4f5O5c]&>)%37YT_leK$l5PsfR1M=uAFuG9k S2bN*&eV9`ZAkZR>FRNf$5=o8Vb`p:>IHrQH3h7\I94<8qod?[6Y2su'8LFG A#!QR2/8@+="a7_215o7IMTF;cpp)Z910VdA>cG-X&c)k)<>H!hS%ARq=Ku>CCY/RHP>@o_)Usb^#_d/YQ.l\.UBW-ac[tIp$9)0ldcnS 7'Q`g\Mef2ib4WcY?R`Dh*LdI@Sfe '.EM&=(sh7GC/YXT4glkDrBMHTQ5bpueo<4-Jk7ZJI6i4snO\K1c? oYqhgpuXipp4\#IjiWi=;P_CgDniu2oB(rc?XlqA[q3$bf`Gq?"eh]Y"fM.j l)109b*/G:ZL[;4B'K.OcM)%!,a1qTu2iPK0LQMp$BO&AkR /d?ud!>#gE>IRAG0akrjfr;0hqML2`"qA_A0-B`WJcuO6Y"?Od\T>#Gm\6$d cGr?W:`VT=aI+hsh7@aLN>lMG0*e#)8m"T5H@!.X'&cYVJo?#.k^fU"@(uX: lQ?`MW4E9K-9rP&dNX0:*P9Hb5c^2&>@u-K(!AdN`f,_TFEH?aGskaV*NJ8_ acl#'21JE1amp(+#hr=aABE]AHY^aP8/M(<`0K13r&T_6?b_'DD+tcQ._2#c o!)N7HFV*<4*ID<,IQUdD*b#^J?&%QJHP[iQI+[i9+RQQi,QW!3d'^eXBRT]Uk^G:S0iH o&W5ZLp3V&4To+BADdNcV57$RIJ`_dl`IXBHI-,gm`DtYj)C0""m(6!\CCq3 i@8gGr-=;eL(N$>3.:Y1m#fWIRhrr),tg)R[!1uqfWqG_nl-8MBbrl$iF5@C NZC3iG3rI-+1#t+2O05B^=p>O^Up#Z`Q1X;6G,+9:,YQ j,\EPgj,*AG8$(0Sj%Adh!*:cc^6EhkB;`8:HV=@30E-=bEaa7[r3X3;R5`7 R58(f4aQaf0k3\3(kH$Q1^)QTJ3Rr+[Z,e(h7GW*].20N4aQaFrUndSGOM[u BYh"Q0>-ekW)9=7_;s\Y7VGG1>&==R1G$g9n!4?>eAt*TB,# jN'oV->hJDaXhe7gqRRPVJ16(:s-\.*_MYF;7q-n^;?q#m^_LoAV=2P?_iVp :/O>E_@-qdlLW=ds5Jqi+OjX,MFZGE`PR+IS*G[1THSel[VQhbF8Yh'S)[bU ?b`1!;RQ`=.'j9,3I3Z'hRnge2s.e1kic&gZYpDE2fI9eC4`kuE;9[k[Vboc 1XGPBIYD+#'3"Ji%Yi\`6YP>h3IetT7EVpJR1Lnro[/!OO$uh@C\l>b5[ub)Ft?"Pa)^W6[TA$TKFo,u/Hu&4g%\!A:pA @aL!>i[8[qI)UO2pANF:$>#.)cU3-+Sk@9YE8J?Zru=%>Zu=ini^MrU/8-`) @XApFT'o-$9GJaQZY%HJK*GI;*3BO4ZtZqmKX/#1TiK-n*O,cBRP_iI7$Qa$S X`VMIn3,=hshgs0.:8h45VC=NY[UjX;d Y9?emeEPG[)`26$AA5J[JQ&jK!(([Qi.2,Rk0/Z'5S>9>3Urpn9UINUK*[0Z JQ!QY!+i8H9"8C\;1=fB:]Bh+V.jiWkVW`,>B(2-I^H:gJB0&'oi U-/,7f.U+%&2H_&*V!*/VLpfo',!ZHB.2C%':WAFd1Ff@e#!Qh,&/6=r8(^< 9P_X_W>;T\JH2aS!=Kb7^4oq*G#b_P*?Jqp*F(eL,uXuL0Me3Lc^qghiPK74 c.e+T'5u[9ER^paY&0>IZ?V8%B_An@R4SHeQ!LW877>&l)_bY"TeV$BT).OQ UN1Dj'dJWA&M>N929>m$:pk&g0k855HU^C4TqKE0@DfIiRd9+X6I>p`Ctc71 JH?"J$,JCHi8"nb4S[K3Dn0Y;Bm3kM!=JUZBEe4m5-jMk,c)g-Nh:#!a TBm94\@=MYf]Z&!2Vn9D)K=+oN#7,$L-\-'OA&e*V.""_0]12>](l\nEWS+- !V#iM?@$>Ti5*>kmUcorgDi_D*20=L7ocQgOZu\A;Gb8,4RiQnYeL*mO7gY# Uo(#(QYLA?BXCYS"e3#"]68&.Gu6peHGbIHBQ?`Jg.o7^/,=NGMi\'-Vrg00.2G^[*0]]nqE_tWNNF#2#%!mQAY;hhPea$M1s=X-9,IC)KVG`]rlZUg+VY*tq7"U!Q2&mO*aKaGEocZE ]Cl%L@$"bLo%eNIaktPon)BIib4Tb=1+0:`C7k[:)jL$U\D+`-TjE!&"oQ'& 2/=BKiQZRfWmQ7;7j!0OcpJ?>`^cH?#P]'$2/CbOdBtC_ju54b4oQLU6L*JC [>2LbOlZKjOcD/jjs_l/'qISlRH#Z,5oIkN><%87U<.'bmUKa.I)$K8V'c`s NoN(q4OAK&K^7UQ`8NZ6a&k\fBQ?B^.FQM444Sn2m+ATAqtKDBr/p063-!r5 ++C^MCTmbPM8mpW)-=XmJP_A1I+Enb$ISZrRV[V_Kk CY&:UGkEq!P@Wt!4H#ktE+[Y#hR:C[H0(pTP_,\,hisWP5?b[YE^epm9ZqHjCn:o4n`c3rk#fQt@#VkiF-;9f"(drO> [%^q;)`G@hN]Z[?l`MF#MELR];lC'_N0OdW:QY+nFK&$!SQDVfPZr"3"KL^n=&,J$7"O) nB_rmGCrD03[>6d=;3@+Bi)o3iU=E@omid3JT0lY%NO-9It*oDVt7XfUu7S% g:"$dg@1X`U):lX-\3*Albe/?aU]@N^n$=$D;?o!.@[B=/0BA`Pq,qJG3sOs 2,/%eEG7CWrTpl:@P=>D^^6a\.o861[VXpiW.-oR.SX,"Nq>"L"H&'^:fQLS $A@tXOt;+rVi4c+otUY?'J!4CVmL?tRZJBtOH651Q,7bhKL:J&J_IK%`9EVp >\hroV:n05puA&8L[lY%eEG;T,pD7a@Y=/Hk.C)rRhnBTnOrdp)cInqjb?jr "`="nM_?u%d"!)++sI^&L@cYor.Vmi5F84YAaF#S^sD'ja?9$MIR=TnofuW4 %.fs&*orQ7l9R$QD_u%J'Q0aXKi*[K9_O"2k"WBWOgnU"X']H>1Z!eB5 56lus/g"=c6h`m1 >dD.s:7XFC$-3+XQYNU&X]d"]qLNFXFMnN-;5O]pYB9$WYXo5Z(+N:^@)7GJ =3q;nMfud7RsXhD5gU:R(IlOQ29(9F.C]HXGt4)?6A89e23M;-eWm`X$Y6]a 'IrhH`ujJFl:f.U,I_QJ@nm;W'Urp[PIS`ZjbTYQ0f5h$f1p2<\5@tbLj_=A @N5tuatEt@i7b`$G2ihK.TOaBnZ4@tIt86j0@Z%_No9>).lAj?=XW!gB2N0; =kCaSKM^Vp085dlImFbC`G[`fa?%_+qF@][H0,un8XV;;>i@+&Uja`F.A\B@ +iT:?"&5A9qIp(t]CAb3:1fu3%5CT2Ppl4\/+!NUStNmc[VXV0qXB^b/QuNu ["C8c,:?2J4Se'%>j(>_%8PID:tl*B>)aFXGer[qX)irH'U8lsd8A1(4_TcW -^Jk[6m$qR%soug4o!rf]EK"u^a+A&Fj\R7kf`\thB:"g4NK"PN2P#fU6W/L 5IlenYmLp[/XUJXpBU+Oh9&0CRBf)l58i.;ml r7mfGKeI>\gkHl8%7.[)lJ^jC`qsJk+.SLnW`?)8Q&iLGXbCu%h&rDIa(Z@> QTPMA7_ZH=&[a'HldDV&[VXVn>kn5A01:1#:(pnf9@c]u8nC4 FrOV33A)pZ':c7?l#7V\3lPn*o`"" Fo80CW@o1/fL0R,9d01#gJQqZ!uJ4=K_];(%HnO3EE++`^\DB\NZGH:ab&&t "ZEp,r'O>BF8hnRK4^qG/,MJ7UDW3-,SPr3]ioX^r'c[te#am)QCCt'7:"kb @Zq.;IDPK4GTr0?U)p$YAK)=V$K@;q'g7t%)LEoDXcRhP!VaW#,t@_%+sF'T^p5)iE!d5o.IICs6m$qf &o4/$lk/EjI%9-o@6q+rLje7!KddiG%CM7`#_T-XX_M:2`Dc&]&q,^8Z;rks )#UQ!Ie2"U.h1+!(kX0HeS..?Y['G9R<`[BJ7qt4e5N9.\odU5h+Fmk L$g(a,W;X#^2XiPSHT9NoLEdR`39suS$1>2+MPAEP"pB=0Gi$O."+K.+lmm\ Qrh4Y)p5QHlXgpTG[sc/6?,EG&/_@a\$XQ'*]S^O>l;qNA8tm=BLSc>=s6Wh C460/j0*h``GXkL_:O:KA6rX5il.T@V5!7@ /R*u.c,AIRoFHs%Q)QgBo$u8#Xd!pJj(1j`MFXD(Gtf'ME9+H,l(*%TM.u*b E("_!,NWqQ'>r'&'I`I_hu2u7Hg\^m(T1f5P\Mn-ORh=2:k86)f.1&28)4$6 SbS^9;+Wc2Yts6qS+Ja*Y:#(7M b3$MSbS,Ol/IRW(b-TJ-2?9^tkK^Se80``,cYTb>%sF([PFGrG=upmd"JmI+ rY\*Y@oe3nPQh7)&r.,5;:VR>SK\-;i^#],*.m9#6n%AedVr?KSUaG#nIr%_ 'SPIH]H+gZ-LT/HS^?.K1aZP+?d,65Uth1fOqT6rA`=u'O]e+9!4de.[-ugp hu/SnosAn]3%`_9Kanm;I4(EQ>84rG*mYD;2K)][&SP!BK=_\.]R5 )TM)q<]j6)#'U?FSVU#2(qGLL,`,#,'Bodr&3M$1h07cHXhC]:,s)@%lWKaV B$=G8H?N%(.M]16p$-\ceu:F\)6tlmP]#$XUr7?s=p1mW5k7d\$#6E^M$iDo HXm/A#c50U5NBG=;C%Ypas>)"a[3Od;U>b/+fH)Z]..&WIP-@1HO?5"NWLU9 agjMs=X?gf2#a'YVnNB>9lfsgmr="3pYUHbIn+="$8i5dWd:lLb(^sO*f$R@ D#ChgRj!]7iJ(X0Bfi9Gm^6%u)#1oH^u'q;:K6QVmoQ.!;l 2C;_\=+um5q2nnRgL'sN(H=`=,!Q@2H"@b+=aCVq"W^%ne(i`B]C?i.,g4ni )?MjKEG/=a^=:Y3JkG?4ZI$^d.m+`FL/WZ3Pl1D8J7rNWgn2BSIk=d,J/>Sr XNS]o&/V=6kF[6qpmi'W.:KbNX!k!.![-G0(G8+n+OV#InWg\[e@Wa]-iAZ* _&/kIX&lKb)PL$,=9O8BU)s(=V?GkUBuDGZVe8Z(d&+mal#!5PB&tVUKciH' $Y@cqnTtWZ/4cUCG/JNrK1*gi&G8<(e_1+D.Z2WT@MXtGi>Y2CT$YWc&-d4R A>(D$/g&5qlGDN@V'$7S."mbGP.tYRi^h:Y2/?6Qr)XSrKp(J/S._r#(S"=& e2*OLZUSi1UnjZABK37g/ftOXkFeDjUj[auAc/>aJ`O`Y_\G,X=M,PO_V%<, Cu;sTfT.d'T>4>]]Di/=]B9oJ3#8GPq5U ]Q@Hh*[qoa=t=lBYUOMI]G(@FaIPE%fOWj0W8o;L\Y>#cKj:rr%DaU'JQu@A Uu](\-_71Z5dkL^po#a9AL*45ErQhI!N8^[CtuNTZf[N_51%:f+__]a?0>l] V.9U[;N=(`Ym)YfrA)G&3fUVDm=+`/?h&:RFj$(&(VM!d;W\bib?A'F_RO4) =-c1[n40-oBKO<.mC1uaSt>CX4:[K@0PEl>\QH+G-t+Mh@4W=;j^0B_X%Y1M 7VR:`E.<`BUVjVuTc^:e3lFo)%mdrb"/H2:)+,-8Ec/$pe.55=M5m)=oC'Be ?:t4[5Q;TMV>a>0MM@5^_hSb420a>)Dc'\6,:1j3CW#s-U?&iqKu=ee/%eU3 &tTJHCr:Hoan"VF)T\6u+8Y0$YJ0QR_.9-R,TbD/S-'.i`%55_,P%p:i1A8A BG:M&PWV38>uSt!J5Cbt5URX9GYhm"WdBhi13*D5X8?PUSm<@OmUTX!e0/E& T%J]en2MnZ=3)p#779Ul;/]Xl)G[9@6A1MmOpf#HcFCCX6 0D2!\a1[V1Ab7\nc=S^:DHbA8i!sH4I:r\N8b;8YX`&n8H]"Cc*E"A%O4&LU p"*:N,F$k"=-@hFPUGJOVAI6W$L%dFME;J!;BG.GPKm#P_-K5qYp["LQ8!0q 15hOTKUrf$h#'rtqNEu>@)9-[b;?h0&N!"\'1cjlP4e+o:ik;@c?UG;*4hRh P+8nd1tn;YQCi(/F_rL&:2nO@2\[eI,3/H-rquSj+Cu_?a0X!Bn6c4g?bB-b !X0tn9gG,3Mm+bD]KT!bTqF)ME/1HG9l&@`jW+,1pk0h?QVdG4G9?S*R564J Ab"d!Zp$t%VT,*pB1""j23*n+]2)inDr.!e(5c.Y#=Wa3)4F_/7k7lg"6>sh 62=W*Hq6Rt>pF\%(mX^Ihu4B9`>)@NlC[3k-C?L=d's.\5s[E7cF$8:*V_62 bambCR9f%OW61(]8$ll^g7d:7XF7dtfI56ElCO(HI%F1`93- X2u*`,_Kl/\2baTf3NUZT:j)_*'&U`9C\fs$mL`.O#TEH+;FS"5R7fF;?5ga O&DZZ@0+mg"d%ngkK]V\H1Jb%"'2\i4D`c\N>@XJP!'Gf,!p:$B]6Ut[$-Z9 Ukb.tPI6eRAQ*u6Up(>J&=Y2hGZgsj<'EKu3IB>XCBGi[;pYJ>XnlAA4ZMXNN.5Pd]WHBMMcZJ4>;jLleEV63ra%BLRjFASF=0E%*2n 9Pm1Tgh^7aL1qP-aY1F,dUe3%%uWiK)![6udj!\!s#lOE1q3]WD@EHWA=EqX ^`Eku/73&J>H.rDa'3%;&6(W#_d\&nSd:$2im(LY[hYB3H^5g6Rmm"uf!IUD caY&=o7>2R6YKtkA?aRg9+.JOO[bp[agr3;AFUpV(!)GnN1dqZnjK386(E]d?rY7(m*00&l+s]*, e]WE#l)3fC.9$JY#H_ZW@U9M&PY6OmiuS0[5Q1cRh1gS3#O!Ke?l?h3V*dmP D^d+ane4hQW$@]a>I22>.)7SBe;WZ.h$"12Xp.Hqq5S(fZiZ<8GQSDDPs)DR F`.,o'hX/aV>sd8e[gVh$Xk.Q0t6/_Zjr@O\Zp,^EcKp'"aU_EUE11'>e"qB <\1-=2T?,KKEL>S!Q^lt58DB%1euB\k&s15B9Z)U/6Pg'G`ir*hF+0q!IMOH jtmjT5r);']\refqt2V"C/+V@#SoR;mY6b7+I]aP#:-*^gU:tQ[,Hfq_P?/j Zmdker,C%Q8#RM%VFk?XC=]K.I;nnaj&?iHBh(,0B0&u&'L.TP<82X@6F]f& VeudSOgMWGE/2K]7^Or$BPDN"b/PG>Tf@)ms8CjTV/2AG;\lsB;5dn,P114O E6XU!KS#qdjF0?QotP:RNTM7kOoR1N8u$h3mC.s`:S0h10;i?m0+>*_VF?@V _.6JAq"#CWb!h=Q!Z%hnq24jB9;;FN"Y1^,NX#A_L L+)9+L^,MifP\9\=\KMbh)\tfoi,F7fdJDXd[i"@(DUOTBn-=9d X::cBqk?2ePRM.IaYSKfEpse+qA*dr>VI?S/e2BB;_@W$Z[]i!,u2jIIA.]B'X?[P__CIr_IX,:#)4.]3BaAH"saW:%%R6a.^)AS93 i*!&T1tKrM)8-+!;4d:)k)=7J@]t_IJ)kL7"5&:0F(\1*E^jjA 7e2@'iPSK/(k[q\mUXY8SbhO(%3%3]K5Qa5%$MVPa[:-?,!42.+PCYnSA:Q4 Q!:1>-Q+T7Z&Y'(Wm7(m0%YG`jR6,GR$:I??sZUu.NV_27Lfmkr#1ZZZi WH,Q>k(iF\?bUnU7T9Yr%UfrV)4N0oY,NC;8SN5b,GBB\[ap*9pfVdW-?T1M :Ob^G;>@hOpKeqi"Eu1)4su&^&uZ6g)>!\U39?YGTnIed1V)@r#1F'$JFL?H >)J[NJ_/s`g_+/;bs/hGD;u1<36$WK%(hhE685!e!+YSujL&dTg9`(`Rk8>5 Id-"[06UhOG?g7/"aL]=hu2te8A_ZDb75LOJCeGIDs"e`]carRNYE*H1br=u gO]p,70W\a\%bAa:^EqJ`26t0JtaGO-o#:l1XMGdDB<>`ok`#0%4h`h6km0H @W-$8'4mg^h0!1DGe%'CnVDAb&Z%EPr=#qsM)l08A4%pNq=sao4a-o*Vd"e*?r8"'e/OWt)a:8>=o _UO1Ora01Be&Hft$FD="U9p:eP8:K'1,ku@\N^u`-Z`YtHc,t'#giZg8-@8F #iUmj7kfN5X_g2njhShXRr.,-k.7.U$9#bjXS4sZo07a#b?oM+K*Mo"]\lVS B.M-W\Tm1=-'#9$k.IZs-k)C:lX0Z1O.Dn?Cp#:hj+dA^6K"A'7m-QI)=f;c 1=U7J*P%r+C]G)GL\tO%[qs_A06-[MeJKW-KJ>[IYM& V5[AB)mr)/LhM;P=Y]`!`\0uYbAnb'@[/aBLJ,-%Z@lDhDIIS6HVfpR7>!>ibS7@or+$PWaeC6TK`EYS] CLe:+^0!UdA7!.d:g=Mnn,RiG+E+>63=TV#@W@O2HQX!#+:B8(,=LP5$9D4c/!gs BSGNB_g1IoV+YM..S;/(C*EAb5'PsFXb3Ddb#/mjQ4J"Uo/lERZZ+m?";a!P EPiT6V5C,)pkSDoW"CLICdR)WDq#q1c&38h?5sSlre5G45(dN[S<>8'@C5rp Atl;Lm+@GlSrT815(,K'g'uI^ql5R?*-7;DAoo_)#T/hWV1r,dX]),mBm>Ap <7-]kQ#Xc0)i`ElZi)L6\.q>Nf3NSLRAlTTfN,j;DGJ<5I=h"@HB)5H:2^8* Pc;H4VrVebqA)!oU7$.F8!WBbFc3?L*/'MiBk\GAk^isGW-MU.`Hn:q[9/@t .L8VCiNVQ]G:JeGe1,i&V(HDafnCW4ki;:2d-'d`J-Bq4EC^IKTTsdOacFG4 fRS#hcY]=+Os\poaZURc@JN&M,&[Vl'L97.J)gi;Jifnm%$S8s1g!)D$&fHd S%pOVkiAm+Te#U7Wpprp%';O8K43:_O<9r/> s8)&bjXQXgd[1C;m2Th435p#Weq!0Lj^H]>-a$<6Srj0$AP.&'$ZKVgA::\R8/F/-,A:uDj?NY=,7uXR %hFp`csJt[TFP*tJ.H\9UCh5<,4u3aEYlDa"Hj 4F-Me@p0YJ''A\-V=I#&Rh)?'asIrLC%uLbo5$UTfVF(Gb",o4W,m>i*,jn: Cm*DJ'C4D,#BEq[$2[&i9_opc"BBmTG=1LhbP`LneG+f(H"ql-*q[U:-:AeRbta)]$@/, ?,Ifm^a:+XZI6N`=r)DY.C9D>1<_?m/M>LS3!*;j"MU8HkCu:^ef.W*j4*1$>=$q1$oni%QCPruZ!rEQaIkP,25QcV 3n_A$[bZ\^P8l\%UaSLa:HF&6G`maf(#j3mZ"M@PiCM4'TqG3V!$iF/H$tJ^ A5puQrgng@10_D?[VXW9lF>gCj2X)rg^&`ZNfQeQr+*?OB_7%BkI1#Z)(A\k fYjOB8/OPr%?S,:!ji$Z6?tEV=NodlG]CtBUF)rX'G2'H@!M%jYsX9V+ff:* R_T3*G`',TF]Y"u".?#[R6jme7\OHqY$?fQjd3=mUiQdqR%H9![*g:dM5@!H n%%U&C>drqmmU71?7Lc^Z=V57`O/qe:+dE6s35J2K/u=EXQR[qhTBt+!T%!I TM(S%l)S?Kn9aot?*n8Ws&JR6D)G/.2+El6Q]oP;LjX1beTUlC`fY/mZq8G< C-7,C=1<6gY3A(H-@F$ub:,!AL,%F,@+MNWc47"MY)=#F7WWtOh01=N"pLkP FQsc2k7F;Lndc_fV6W__8FnTQC'K4ZLb#BN[OcPVNj_%1S7/_!fgT%6)8H8X0j3a8c(`s.m*^Md/>: S'1Pi[GoT@numN8.rIOro&NJO4cC=X"/mFmYKT*u<\m6V+-#!=)1fU^8RpdC 5ROM*9u9tX4"6.-+](TU0j$e>:K;0P?;?Sa-jJoMTYX5'5_D)I\&DE>,L+8' rqqoj/%%<>,WioGm #@X9Ar.V"9ltI%?)<*rM-#tn4>sa?Z6`t)L\RYg]W!F6*um ,ndbj]?WFOjBGlC]C3HQ=+M0=Vn^S^HrtkT2HiWPj3qG0GZZBHUeu(I]f>uF Dnd;WIQWn;=7Hd],R]ttfWfDS6bQp]*85ptL/!gKj<'MZeL7t3QACABP)R6q'3^1< D*Bf)e>Z@i<)X$>2*uBF#cR-TnI(o#XtNX:rEZD 6#'(OrfXI)1CI:oW1ieu]]dn?6-0G/8thQ`fc?%Yl"b9od,Q @?Q"`76$!&N97s56E\\cBQujRUqN<$Zn=ZK6(k$.TD@nBI'(3hQ6eb,GQM19P%6bj,gKBkpQe"Ojn#79*6M@WP>` Rl@'BM7bYr_$=:ZqXj#iSQk9_eXj`A#%-&4$EY1@Lh_cChq^+!"N0NZnH9U$ `>;n6-tTl[cGo&%+I:F#68\*js+9gUKJQ5]+qaD(q/@";!KBJ4i.6d&PV6=R 'ELHV\[OG!dSXdtQjQ9I_%_*6NDJCZ$q*:(n/u04S2kZ#8@-`$3"3/%mOMkA l"A;3W>46=$"r_$-efQhJYSfa"_cM+o2tY*UIUC)!epZt]u"VBfR96Di7fhf [Nm.F+eRsT>LuMhI^t9.Vi&?X)-7\(Q+B\,#b:GfqP]3>3?Q=24]=6WOkR73 ^JljMPq>)^NZP`/NU]&C9?]He#SESm7'e=G*2+t921<:>kdrXYLOuhCo$sOJ 2hs1D]O5!\Mt9r\misd#J[IDr"52.HP>nhG8XUM!kAUm2LYgs%1kET_$H\,G Z$Z65bGkrM#[N3iM`u\m[&bTN;c2>9lP+t9L)8SOXQ=6-O4>2@X]o;omu&-8 7.EoL#?M2]GnHXH=[J@(7rr=8`P:qOHbR,6gpepWNlu>3VsD'hKn$c%6f"[3 K#@rKEC5XPEIn6^jD[sOhR%mC[e9,q=Bs/YhP6(S7jh]!0JV9+K.C-@s#MW. ;j1T)2bA#)7sC^/X4ZpV>K)>hr93+FQ$+lU"r*L%Rd?ctPi;X/>hs,)QI\u\ *2(^b$lGZ)'sqjMpha;N^[+$[Z*Z&MTNZ#0g/Kq>H7?MZG%Z"GUO1B9PJ46U 7mee+Z&'-TcCgl76NAmU_iXs?pM)MrBU;d8ARB-T(C,mVNQIa:#bsNuOk08eg`bE2NXcGd+kUO+m$`+>< %5EH5*$J:8$5HaQCl)5`nYi>c;#kS:-XV2hWD]Ps%NK08f)S49]mB@W%UA%V7RTKcY10CIUN#2/\9t>XXnq.&(_:RY @K$&q]mKM)gdW6h4YR$:4m8cg+9DR&FW]Wd:B0KAs(V96GOhF'ft"&\%@ZU& Xo!5F\4o7kUi!s[!6p4Fku+m,hnD@,!c?Yu=h&FE"rd]TkcL.e;_p7h>XpSP *BJ;hEo/*3NkE2"X9!2`*'Sapo&Rp;Rl@g1!o3U=3bd0HR4Y+OnI!D$=Ms6q0iiPUF<-o:'FJ'\U)I!g=mXBAc8C,Kqd]YLhn%e)q'04(09 R&FM:l_M$^@?f&Qe>SKkGAd%Npt2JEd>I#G AF1>7;.jB+Rr[5WA>;VR;D18LZlq[8cpF+bo9cCb@ if!NtgY39'X_4K<=d1ISh7@a3>e+V#XGQ1+FQcPsb8]AOi9].'++3R/(-&^e :3A0NKtGdGH1R1F9;6QcUk5a@5oC!R0%&s'3jCQ&+B3`9HsBo]-E]u]i78.% '/S^p]@Rt<2YF\+n(s<[="[UZ+CD6X^HNJ=.A2Kq2l6:cd#gClJeMr6(5h"B Lk:RRN*tDKD6J<5RS35DCh45J;FK*tb*D;M^V=\"DCj9%^juTL8?"_R)I_rq gQHPfbEa_ZW6hTK^8fSAOY1[)Rt6hTDh+;[/U;*N8.5FL_HGTj ,@A04/R#NK5(.a=1H3OI2c%&dBK9Wi+1:rSX&c?CkK[A8>.(%kLP@=R=>oSE %/c2!,=ZeBg/ZmZ=Qj;K#sPe8h)^&br2AV/$Q!,`(RS;,F;QoP(c>erG*p$W +.n#Ykq#uC1i:rgb*4=mmnXt^G!ri4r5i0$!..+9D2U5c!F)[VWqA7lJ,Ji2 7>l36G+&\uSt8]j6(C]:_UdTH[;4C>Sio/9D!p)hQX)-d5SV"(X*sm=C&FPl le-.f>4n,<=K%)l/!_eng3/p@<69&-*'8>F9:%:!16o9cm^(5'X.:caQS4D2 V](mT?QLVi(r<4PWt5^,$u>0L)m][k'7ms_dA)?I#Gt:[PERS!ZHSn(En%\C b"02Bm^jDsD@@4VN'O][?4K97PI@ta=7?E8jiU"7TN.`h_%;cmP&0;.gB5<[ "Y`gX$PogI[Hc,46[.qO($Y8npncnY"HbRQeD7K&4L,unEF\"fiXhKq`5H'1 OT@n(G=U14@WGs,*M7W>)>q0;N>bN]3&,e:oDha=E8u!4lrcY#/O6>pB=:a+ 3/%T>[r1$W\T?g+X^&F-qUXG[8rOTAJlkSk8\:$'N_ ,7qA^'cNhdLCYIrs/<7qCpK09(.hA;,taEceZ7DfY@F^lNi#aV3D(W/q3,;j5cW92?o_]Ua5Z;X`"1(mRh5,qO2X-aSKNXgYd#g6- p?hSg+Bl$beLI@]4)!AVBnb1`'^%oC(4G8+8,DoE[r^`X8=0'O!O3n>>U_iU )=+k`S=k]/kKf25>.8BT0^'oSnC_HH`Q-$B0H"\"APNdIr&p;3Y[PF#&;XIN AM:c3djA1,Z')C(mZt$nW=g79pu$d4&bt"pi5(%cooY-p6BJf'^aZD4a>"h] +UA2ZoB+:BJ84,;[0,\*MWN$JYB&aI,=ddL[r5WLb8]AOi7tka0>IFj'"g+@ 9bp>Z$\,?\X]egT4MV+.('N*Z%+(eQZY#LT[tM<3R=ch\RUjO'V.%8re##hI k$pkDIL9rV.pXX*,YhKQXhiFsb*4>WY[FqXh3*qa@Q_VBTH8FQm&<.s73_$D [S>q.!K\%mAF?:EQYoB,Go[Hi\b.fr-R,?VU'0qO4- ,pT7*TN)9=$0J3j[;&'`%,&=@GGNE/X'j\jD43mA7:\b1TT[M7(JBu@JM)97 Xg_+p5t3%fOH>RnjHE@`$mlqQO`aS8-KN:4#LZ><4?;&I`@uf@r2*2XWno;G f/s1"Em0k'hkQ5r3cO#m4aZlj]"A&mEQIJ3gUHVb:HT42*tN?B"99&IbYs,u %3SW^G4"G;SSNk`$e;b,j)!DSTrc7o*NQT7K1_&J S3(qt9:%7f3j"IWKiZc[,2#4\W`5rqW)00h??p,Xo&[^S"%3+frRKLBgLN!) 4oY6:EH-!FD8qMP_)/)-!La\>e>Q4h,EuuAA>)V\.TNcbakj^XJ,AWB_31_5 KaS^.FmDMVM*'^ZC/!eg7(@VJE\/,M'I-.@9#<]I:ddl!P[LAg5_0!%.THi, 6Z@&;kYZ_8ji2][^E?M/Z=Sq//(UIcO4C:4#Dsqp+b)[!+e_Ab)FR0ildp(- YT_5*kb56dIE\9G45Wf%1eARA+:nMl2f=kgOZbJ+DVd.m_Q9(S=0>oFITs:! NHBFJr3EQK:jr67Khqp(cprcK]TEHHMEp,!qsT%&\`[SNSfIq8!qhkHQq704 nR+4DFZgH@[Q]3?r3^=l*]7\38kM^OM]CYei;EEBGke[]eWPUf%6PaP10@(< `JPCOjN,`F9:)JJQNe<6_oRrbW@3Zch7us4$;OH:>b='"C+N/"NY0?!*BJ9J imar+LG6jsPkfd,hr;V5UM8V7qe^IM#6*IJYP*d2Pi?aS7p+oD,IRnFFm@H_ >8FW/*'AK8aH7]op?ZMFY+9,l3Q?D2s1gCM>U4VF:nU)JmbLsH=qs_` M@nd+i6aiha$YrdB>G;[JnUZ)5AVcX2bU6G!mcVZNjWihJCoZ)_+"ab%SG&S $JSh-5$e]$oG;^)N6D4W*P88T_&5qhi!!*qb48[f_SB6lMA=SsW*g54fKnE/ I4J9qJ9+Vl?G3pmlZ0r_;a0SLo1$DYWQ'4sT'I=nOMp-&u8NWFlC@hq2_%W\/Lk7U3o! 7:j`4PtF\?8-gs`0`n'X-r[Zsjm!n%G]9&s:-g;=JqBInfAI"4Ngkcr_+QE" >QM8F9dZ1o(akq)nT!#(RZWE$H"EE_l$@78ML"bQD3l+^>CGZe+6D4e4l(#5 \(DrI)9YiKEH-!rGM]uX_3/RgK%6o'=8#IuC=R(LWejP9*W\&mn-J$P\(=<= 6-N5NE4?Q0Qb(4?:>_g[s9OYrj-i"a1mUc07AZ+!9q]DB+82o)fusgpqKMmbBfO.4Np=4[q,RqXcOSb2DI#W@Ar1HgEJSAHo3$ #=VahQ)3'L]7<2"&+'M0$t@f1JhR(#*u1BWGn46^AOaVPEnP)4 \HB]#S`4\7kU+k"f3FApK:Rfs7Oqu7jX]*l.pNdR`3>;SfejF9Y*eSa+06FLqjHR: )5cnZa\@Nb7b#&dTX*Ip*IAeAVF>/q,9PTuVc"$#oB/VH;8Y[h,'M>@CY+=_ H:VA7b`dE==X]YkOfIdL:732;ek0mAjdu@*-?J<1LU'E6V_S0KR)s6ujE%0$gaL:26ZDFTBP%3d$g&5TCo*95[BmT`q<%-C 7JG\h5NgLm9fmL_<2?!E^s0Zg6\hm9\0ZgSO*Z=QYXuPN8$U1;Q0aa)VNC90 H@VNt,;(O='#dP"9.%1dIQ,+0)F$$ajk;A[9lVaX3(GODYrngN6bYh3jgb1D [m*V8ekbJc8hB"`.&`XrF1,l9DS^o._d[4=N,gQ,KZe %[9VQOg8@%(`-F=(![(DO>jAhkNVdHQhkab^4'D?a(p.gCjhXTc(+SDQGX6Z Hi&Q+\i4`[(XG),7CWYFZ\HJ,ISd=\aT;VQ]_^ooX&c?[F?9`;8_CCae&sDZ $mSB2/mYhtd:+/SH[ocU0?&Pb8qg)&KSMmER"'`lR,Oe2~> EI reportlab-3.3.0/docs/images/Edit_Prefs.gif0000644000175000017500000002272512120332300017743 0ustar rptlabrptlabGIF89a>{f3̙f3f3ffffff3f3333f333f3f3̙f3̙̙̙̙f̙3̙ffffff3f3333f333f3̙f3̙̙f3̙f3ff̙ffff3f33̙33f333̙f3ffffff3ffff̙fff3fffffff3ffffffffffff3fff3f3f3f3ff33f3ffffff3f3333f333333̙3f3333333f3333f3f3f3ff3f33f33333333f333333333f333f3̙f3f3ffffff3f3333f333f3wUD"wUD"wUD"ݻwwwUUUDDD""",>{G HGp#JH"EX3f# Ңɓ(S|ر˖+cL?ۙիׯ~C }PJJիXjʵׯ`ÊKYN|ٷpʝKݻXӮ7߿ LÈ Wǐ#KL˘3k̹sA+6޼?+T5WmOo]7oAwou"?wrװ=K>aou ӫ_}wP|Ͽ'Z~h&i&qkm҄YX>gwHraoE@ d" EυbH'H4f#Ћ5!+y">衎K"ə`ddBN^'ץq $pۉ\phdq%qJ> gF2 e}F#3y$:as|xhdfޘY1d驨ꪬ꫰*무j뭸Z*BZYK?jEC&,F -RB,bfܪϮ ([m覫+,jkBo {HV - ^Jc6lgYYk0+Z!% Ģ7C:\DqkjL7-~K$5].C]RC3cp{tWSrx筷as[Y#+;3k8؈]8H/|6kls(4ZD29|9󻷷U$/[WԷux[Ϙ'^Ϲ }9k^z[$k#VǟMǎlxڇ_X3=B߶>u :Gl]k>cqO k_'Ƒ]j'+w+mf3zSg ɻ)ArWۡb /=F}mWBQ1-ؿ$1taL8zYEIz6kW81xD2!K?*"LW1t/6G?Q!w&7:э1dFIRL*WV򕰌,gIZLH%S%''\c,׏H uΑ"uQQ=)mUqԣp¦6ʐP8)uP@ jB{*Ԣ QP POT\b"5i+lNdp9Fߤ/us3fM*Hzֺ^節 `KMb:ld Xͬf7z hGKҚMhX٦嵰lgK%Xͭnw j?yYPpqԣ(@[Arz! mf6Z(姖Ӛ IDE᩽]ԃX_GGLJGC13C|1ɝ'S3o5S (~CP&dR&;/jqgӂxK+fg_,_s%=L"w1s̀iRiѬby_r5e;ayJ?49a |I!{V8׺B~A> !&H& h=V "FgQ~6MFwGhCb~-6ݹوQ3 ZH45Q]i`;|/ߨ^=ZG"ⱋlY뺝Q417mg[!u"[MoXᲘVZMuk6[{e?C[Lw|[\{_vGX 5 `n?}z3K}l~W[ޝޡɛ8p=cCdw]l7p.cod8ײ~񞿼Rc#]aNɵVg[Qu>;׷ɸnZȳ40.u§+>Ȣ!9wʥswێEd{x +7֧i?B >Qwl-M^N~rve1)eO\h.WϾ{}O OYqBJgot|jvo|Gf85}FWjuՀU}2wQuU(T~~ !(#(TV)8bLV3(e5S%h jK$hM O;9 ;x G]+PJUQXSB3`b8Qq[ldjlfZnцr8tHo(\xz|؇~hYj8XxfhP8O(vh^T_6cB|,%#)fh""Q^R#(XtV֡Mo Q6dL66&$ʔJ&MWfgv6pp ufUNc%P7e(`֎ шxb92yAGgre} r|Ec顐I6eLhs‘HƗAffaVx] Iӈ37aq3R#_!`U!/|FR1)/&zdv_{LbRLi&M0%!$`{8zLД$]gp nٕMWbL)5зgLbZ.b1ɒg`uB敏Q9$?ٙBqf0#QVvYHi-_%Ly^Rd9iɕYiuVyJrJyxdW-V!6/E/303s6vV-D@ǝ-!G9$3<\rYjɹA;o6ډt39Al!!giGYSv :D}T2y9:3:vaG%7Gt Q )#2 6nfx&4*J[C3 zANE_ c=ђ69 7;פRjOm=Z6z22 j, Zo]kEfJqSJ(z*#1z/3J05 qyHhJmhu5֖Ux٩B v;SGJ0~1A2pڢ[Qr/٧x'h YZoF 5H,:m*C([ZǮJgWHҥ}G#fDoGlipʞ6_¬PׇЪu_7o zGw4'DjnzJ13lvb$ICZ [F>'ykZɪʭ@;=ꥴ*Jz;p8:fnb"y,sfkHkΪr[SJg]7JhM.{<0ˠұ5B.mL:VymäEE4s&kڷL6P+36[?gt+4k vz*,$kwf",vZ9moKD{i# 3#˻F`D2k6  a괤;,Q;Kk[t{1{w >5Ú/ڻ˸|&W2+ʒrt7JCD+۫JW[3紏{óxzc+I;KX'ܪ[{9{brhGne;CVGy;5W!zD¬n8xs~x WZKD.,/ԝkÃC*B3 =G+7|B|@v˥ PK^;Bʸ o_<+flYj@8A^F~H[j^NPR>T^V~XZ\^`b{Afbl~nrt^u]]Uf!-ҕq)͕QKD!؜l=' "}: 190\|>ő!э8hab mTь^i |{$uQЍP՛]=-fΘLym&NN`EݮWfٸk¸)> f`P+`8Ve_xTzΎRH9oy9 ߁"?X&z(k,od42a1ϗXBLc2e.|NeI/"")L%<=XeKUh5 h&ImbQma-|~chbm~vގ!eAN1%]UyDI5?/&N+ CT(@S4XY^2aq>NUO σMjAar/48?MY]Oҭ.cr鍍iuЦ^biX/v6I)Ϟ D8pE~ "|? RcD b(1dH +nDqJ)[,̏ZIG,E ʾ gA|7W=0=Zq9VR:DHK5 WgD벬θoNvbݖ|RjW`C%4Ą/nY2@3jYfΝFFZ> d[ΤMƝ[n޽}+pōtd@͝?]tխ_Ǟ]vݽ^tDK ܼ4ݿ_|ǟ_~0@iÖkL=A0B '?1<SpdO+|HD1EW oj!+j@{qAYaaI'2>G3V JR0"$sH/$=<3J7߄)E"m z bM M4{$4ˌ3QEoNH=\ptA zIFOEQG"m;˳RDL}R5W7W-JIM]%XyH9:4YgY{1Nߣ5N ep2T3\1y\SMtSNWUpUՒ{R݁EX$5=3B 7]}qG> ^vn̏YӞ "\86Zr_6@ yn|U7=zT=X 2U0NxB?\.s6B֪~Ӻ Ӻgޡ-l=8,Ƕlz˒tF;#rQ&h8ǯ]1ncFx{"RlBx <~Ep /4N3\ uCdZ(E]RJ:?1tF<+:&JwO19MiNxta.%9;{G0ajK $F]oA"XKi͂eӖ@'PV'RerLd&ԡZHPQ] s46" D)AH*._e% 9/}~d?FJ- X Ou:.< -7K֝q¥;ѳ Cj K1 F '>ɴF4MPebЂ,v`n,HO?I [bϼ ʣ5&׷ܶt~{.Xk"\1fTֹaitO*Y>ĺwu<.f\TyUHNX_oҷL]~׿Fm*Z f\eOQAرG" 7 h33͖*cx]5:2`HVQuꊍz+-(muMZZ}#9EYt;_%jӶ(BSV󧲬_&u/+sV3b͂._uTtCJ|&=M g7 {{p95m/!i# <8stRԴ-a2̔4d9O|)Uy~mmJ\#R:q W>{ZQ#-\n|gt^ kBeg]: o7k|x ^UGW}WNw7zp3r\惊9)f^N)|O~-SO?|G_t4χ>(~}w?~IC3wo=~1k e0?0؈ 뛉@{?ȍ @! ?@D@  1 |A! きDPA!п ? H)|žP@p * 3,þh%Ȭ  p /|C;L5Ĉ0A0?,DhC&Aj9|pBD{ B-t!B@[QD"5pq$rDtTG utwG }x{Ǡ zC|Ȁ,X?slsȅdȸ&4~䍇 ,Ȁ˘ lHxF< 5 -B;Ԉ{ lH ȽC?IItaHDPC艱ZȄđpʙ0dZD$Jaǣ ɽqKCI Jd @ F(ʰ䒤Đ$GTL( qp?h$4DTMڴ?$IzdG$DNGLtX|ά:ԌNÔNDπdO\|OOɠ#DHG|DpzH3o ώXMɤ|E͘m+ILLD\E.LEP C ݌Ռ"$D F}!eN T=͍,T;TA]SDTިHT JLTNUIMP%UREUXiVuWXYZ[\]^_`aU\cEdUeef;z!clip2gif 0.7.2 by Yves Piguet;reportlab-3.3.0/docs/images/jpnchars.jpg0000644000175000017500000010215212120332300017533 0ustar rptlabrptlabJFIF,,C     C   v" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?7_߈_5O xKQa:v$ΉsK4W7;uUUB9v8Co#/oIo:ؙ.֫N8|cP>$ ߝio7x+Cԏ{vGi?ڧ;<{Fr:}OVM-dtT:ΪîdO|||]`xÃ#ݕv ㏍F|[t2ZU#> 35$/pFyQ>;Ab 2FHM [_{"(ԉ+g<dsΤ rr~)/\x_UI}k5/7ڮrzu/sss&v)fsOҀ a]z< w[HxꚎ#FB~{5jo7wc'=.-B.-wnMT^=z37^$v?|oj'O/Q1v<%|C Px]oN#+kWg ~}z#|d(9/]jeai<ڧn3 O: G4UfʡaHbWrQ=|O8#)mS?/j쇍~7 w;_ޑm??&_ڥm S/|ϱid.4.TrO@ 4?YxZ<,nî\`8ݑې  |d]g /ZQ40xB+[p.mg mdJZ~?}&2~sIxG6 sjiVAUc@Ω.L;w6cLe s^*ú4/܏jGlTvm7V^q&{ XU m|73EY H*&D6?2G?L2?7YZoۏl?s9:ON>7[Go4M.|r/5;[5%N l;x' h< ,BC6w\}'nIWlnTvcy='me-M~`S?5Juoã%kWRZA$n&Kyg&Vȥa`v =o[;<Ğ]oKY<ڣp]qWQkC Ķ$ɴLcb31v˕*~o \0_ idR="'ǃ߇?%޹ |d-ow$~TM7BV[6[1yύNhMR6j-tG0ʡPI =ILitq߇F_ޥZg?5j?[2xXEMWYP~9 yx⎗'Sd;`O&KB (,LRHëPg? |Hڅp.|4+Kou42lqPmoj^jVtyw\ʰ͑*,d} xg?-:t#hK57#sZX!T|UAfZxLӯ=-]_ЎU2:VWRYUெt5<]G,k-.pG pơGÀ;ǝq~(i N_Ğ 55k20 7a>y'&E߃|O`뚣a'<8hʇ;SMƉ^wVj3FA4TKc,:/ě<:?%<4VV$Bz)6C$~~[KҴ6:W&K*E $P]z{2T};Asj1Z 2bp dM{ĞW~%C֩#Re/*s$/N<=㈊FśK@ Ub>۟kKK|Gb5y!w{hc0690^9ʉ?w7 5cF/|ek~K?rfϹOnc ה{,SAU?hoag_o8-}fY31,ĒI&Qt/6˭B w ei_4wg]Tr ػw.[U}xV/ tRܲy$NĻ+?xv>$YWvxK%l2(;wî]) R9׋<0cx™X5co+3ҎV#il!T@@z}k{\xW,a)%_x4bv薠H܅pBV2^ˬj{CL۫k2u}A- r8?͕ Ŀksy,gemu?V EH aVGۅiIP>?L &x{I?(ķ>mBֲ Z*F9,FI m|Cu+ C:yIѰ#!  kc{_Bgn֏{yatq>R&K+ $TcOAZ KimUE!I>0XGu.yA-4_ |C=F C#3If%I$ |K^|m9>&|N>xnKBe$20e`G|4ߌ:7>/=^kdIKaCoU|d /x3jW6^(Z\L%uRDIɌ!|"dst}:{o |AK!|4V"14 H-I5V-C6uM}if% m73*1u7o^/MMT]3šK%GmJ Yk;o u+VZwbhc^h,7#pyV@'x@um3R}i'"pإ2(Z81V7$['|qMJ1-߁xPY^Y-#QbG$+<;Zxg6ik:^uC_YX*yZN+ݳT!Y|A1A5=WIRS%r+"2H8[e3A7 4rF8#ȴb*}ú]#0Kxf8jbq񧍵/>V ml*I ThIOy63_?k2km RB ck)=S]tB>ӝ_:HAd.} .hcfşnMvxuwPlOGtr\y^k_ۋZa>ͭŭKeig$k3)gs d0<B@8}su|t|='o}dLۮb6FR뻲~ w_R[H_kH'f"G/{ +Sas/Fڳ0rAGR?GOţbc7&'Wp 8- 84l?ghZWmmmٺ"$ j$Ku- ׏68%~XgGUnjM/>0^Zizۻ^rϧNJhch`ʾӾ`+ AginSR $:|v&ݱ*:ʀU>A.xK1`2G-$GWaFGE~|Jņ6nE5RM?-Jv\G(;YC72HW_o5-mo<[4ctPok +8ѡdP?6Y/@u?/=Gls\/}_{P|5Ƴ_#Z5-m/,ZQw`$=s2_k!Ŀ) M Z32#8㟍'7~n<'oKk WFcI{tmɪ'ƌDCȈLFj H{ϊmR’xN-A 40DZtRФ#[b[~վ?Ե_mjvvCDkhf"+<<ě|JgsR=.#VFUm퍨w➔|+&&|??5ֿ]Z8scV3ֺb<=>+ mƭ̒u\pfc4#@9|1A$cqHՌsjUx*`|C~kZ x4W7-Q"Ȅy+d 6o]o"%d\# d’ 8gx:Gu~-U< UCeDRw2B(ß0ⴣ7n!KuxMm_CvZ"$(ġT6[_r)i9ru Zd$ Uƌ )ztmf,=߉_×oB}JÈC(G[*+F)A1/5\}ޑ%׌k uHe8efVe$hI<8~ݭU]{Ñkzmm0sua 3}œȍ\A#o %ڣ_[F(^-aЃ}gnv.q9#[Z*G}NG\zWg:Wᯄ^_xrKE.gq*쬧A+g?/Qx-A쁷y;NYX`n?|ga2|=hY=uklFl+_gOđɧ|#Ze3%hfȿ4# Ãؚ? ?g|35Cᎇf2&-+MzޠJ|@<$x/'?L ^t x "?%2i7K(-o~x[ ^yl3iC5Ӡּ=:CĶwZ4+e<;㟅?<#lj!xGXeͣZ%αsisfpufȑ$|P%> cSx w䶏~DGy,RUPA H 3gf쵤xRڟ>hq˨.$2ĿvXe@z @9K뽟]"OQuxK] [Gf͜,$\3PΎ&jO o'|4A4{Z$Z = R:bfb %稩_Go ?7EMbO-łx8d:Ez?5=6o^7I$G.y7\}@I)>%Kɮɫi7ex|UMƩow-Ab"[x1 [&bUeSF 0|A/k^6Wp}oSm,~ ! ױY5O[kۻoDmG NV m)$l |)gZl:ٕ֯rp5<[}^vDxPuٴx Amis ,V$FKm5QMK/? ![!k3Ks/BI>~ڮ R??c.,)i2^Iy) xM[@~ ^-oO}#ö(4gH9.ck(#*IBKit 47Nѵ]/NO ]Of4TĖ /no/J@>v rKz¯٦H5I>xl@iwR gL-"2K;b|YF34gne=ZÖp g++aApA ڃ6ڗ5[fѯQk>t]kᴼ{a3;cVf 0I"iJ&]|qm_ Mj)Lj6v%Ǜĉ&LfuyU!ql߳ M _QK62 D%+Uq̊9gfw7׬? gV$O!uD6_J ?LBbkk9F\{P[5z|#tՇXqD+su !d0Y,e+G2y__|3zK#A߈cl[il;H"-|%e{~~3vßqGGuj3vyB-D0sœ#&gGQYa},ekԂ0T@C|V"ԵoZMee kRѦR{{8WlhഌJG}AV5ٛ_U.|'Kf$W2G# ʳJ61x|| ]gzoZ.g5"vy0D} f©8PI7ƚNJfob V[~ o}4;ogU cN+hOxYk:dWu,q)IrV,(ܘg W65|c%qE--V>jq\c ˟Am}#*-[{DsHnUK ~:4Z$ڧ›XƲ*K, Gc~r61u ILIrvU J52K+^z@0|&24MӼ_5yk᯶5W+adbt&?g(SwFfoٱMe|]{!;&M [G`tBc8Iٗzou?mR\\ƞ(Ŗ}4&ѭ{{yIgG,2|/T Ih^j!~.-rg"WglO|?';=4z|$̐F2@2XG-)gmOJsދ%4R+ě`J rEt,zů4:ZSX},lf+FUnngv@:h|mA6j7RŦ\iu s\3;%;;bBQyPۧf?lx7)|!O/K3ۼn:n_Q\(Z_.n AeLv(wH VR@/xX;zZωѴŵ"oĸ$4I>7o–R|V6w律w[xᎱ)4o4!s +WӼ';g=~&eG1,4ls({8f")#F@9*YjС}kM}6l9Z2$dE3^^77/ށijWjPundyX,c43|;ing_|KP]ŭZzR4ivH?DNF*O=C,²h%xO09@oi8k\<=+w+Qob"K~"2RC9-i^#)x'zޣJH _NC\-ų7*#, | i0j^_Q/69c;K0^Ms߳/?g=Hֵ iwl4{kHKɺ`F,fcw>~"x'g5~%mh}imL es([b@12$J=oYAGn...o9@r7x^Uh:,%w &)Ҵ}}QY[Ew"6[ykhz5;+ŦQMaoҨ%V"I'ufu3H>g옗rٷ--Q'ʓ-_u,[-J]BBۈ6<kƟ xw~2t4EhS*41ʻf @6wj=OǎOa^Ɨ1oUBmLiq~%ha>n|_ oxqPGKu1u&ʬPHaR5'_?K`| }®}<߱VaU<ѠޡMru&)$7Iv4-i/yk)! Uv3^|6n7db:'yW?~"x-Rӯugmf+!xK4)Rcw˳v ~&~;ү'obS$ Skl! ?P_Kis[[L6 Ϳ[Gc-!KX6wkV?vposy-Meh 6T2Tq9kK kxq|'o}Y*շ+ 9vUK|Ç;-&X :\ "wJcUkx1ñGjWgzK6)k&QhOl|GF3;eٽ >.x+GMwY홮2!y~%4o-Ήzqwwk[V(.g.S* +&C(Guw-wٴMpX^qqu⻈m$@ K$}"@HR}{ÿͧ5)4mŝ[Q'f+p !2iMw&<}/.|kj/\Aq"tHX%\3ß|iZƍ/nŪCKs$Ĵceo3.$rZɶDTGbA9㴆T[ƌ}𖻠/-hoǨF䗐O;VqKx`.yXv^;O7uGдSaЭ-fg^Q0[1dDА$WО&4O|6kmk"PX=k4/?5VujךNKT q$Fy3߉?jֺ5j7#%ҥȝ?.)Jnogğ4*Mc}mΧ{e{cnז o Vq,I ߣ"Q\> :3Wx3Rx&c$:ڷV@wB}'R񆍫Yj)<~ J<@dծeqqUAi2Tr?|)uJ[˭Ӿ+fH4tD^[QTFirVEhQEygK/x΅O ͬ^x/Z>yu fGLɐWAƿ/5xc%Vu.|-W3`ia]V-^2`#K2G xͫ:VdBʝp3n# B\_f Y"R_8Z o,>v0sxč'_Ԟ,Eηq:CZVm ph$fW\s3~_GmVXhm#12#Ã79pn?\Ӎ­.W_=:Rc+-լZr!C2  [CGG[-4;Vl{ YH$V=ÖHduv'> Ǎ]٬F + #l\$ԬunRҭH#_]24QAXzbN&Zw|kLѡ-ݥQ :czF.JGN|Dt}SĞO4ۋMY|=̷%lnƤ˹&di'F-_ wh%Ɲ%1^S/'FuSb X*էPO,V,0Ey/Uj/O.RVVPX\Zd<)Ur,|+$[+ί3ú_m⫨x%蚳|]Jt{HHgWhWzMϋ>Xuxi~,<տKqZ1!(A?e់ts\<~ttK$ IiDT"NL{H-6,+_|#RZޝaXZ)ehՂ$6A/ᶵGo j:ηkzL({zFUcHrqkS~=[W/o,<)K麿u mG`x]H$j%Xv"8WZě[-wP񽽾:ljn<>ַr<7+KDiU"D`>ho<#S:y8 SL7_kuyc^jw]^=q#WF%U+ Oq|sRe`k&x0{d7ݤȮgGM, O|eMZi_5>q,\7F]$Ć]ĸ; >6wj[ƗvmAl.d`^^WBK"IFU]'? |Hڅp.|4+Kou42lqf7 ʌ0 xY$ry}OH7xKb+s <1"w 1 ?4xrIVWaH|Yb% dWuTAsPX}^32h=t^KZX'G5XIl7.&ȱi(vȠ abPS\oOjq/g4(xdo#4~ K yŗآ<"{e3LXš^|O4?Cx0Mq_P|Gw86q nVV?yb7|Swa6#Gk1gl%iY@Y2Hn_[xFo?iF$ /|0KDt2U ЊFu%^mF-wt ڦX\*>=xnOv^8[h4~xKhh>]WB9G |3GO $ֵ_iGre.i.߅>,4 x]CMO\K3.κxLWU– 05|38"궚%~]gLDw,y,)?'x⏉^4L%zh X_/ JvJ:807៉7 ͢0?wH| M2_ǚaaY>GCb.LZkĒ|{Guz4UWlmefϚ(ؓ|B֗ß'uiR^){}>渘%ބbrT$I|iovgG= un{ ɦ\j66Rjq5j %_%aPr)yn>>heOkr|;׭D%kgG8.#܊ϗMwhگtYihZsWZח-IjH%KX>T@=Su~5=WZRxc\Ϥ[ 1,)52VE wƷdoVrn-BL% ydEW>:|77X<5c:-Nl2KUc@T`+66K+Zx`xkPoQ>kS^1IQ'eܬP̠?|LmFKӮgԡ_͍ndi_+kƓm T0k]ޭqi|I@֮4q䦳HeCcR9|O>$i In߃Gpы*Q,}$Mۘ ,Yk\SkoiAGw:MdR϶` fD(@K~&oڗtfi,?k`!බLHq `U~'> ?fKQկ~77rK3,ʼn$I'5W. 7\&El`iE7WpK$vZc/Eu⟀c3RЯZ^t} ج`&(iu~)|nĭNz_:RF HF>zg%&Sk.0k%&j> 2^YN~UhIF# T1Xv־Ask/hg+LW¯D]9wkH2ꡢ"L0~1EwmX-~xN[o$ƫ(J"GP /چƞ*v~n4Y/!2ܲiK]RlJ$w2řf`8[ß^x2 x}xI+[˒MZt2+0jI T[¿+UsxA5ɵK6I$H] erA:81=<+ U̫Yl$X,^$rەRĄTMu i_/|=IR~WS(ÖҰ*g˙Z6sX񇍼p><ͬoMֵXNXMkgo-1h8s ¤=b/5ϋ c6]c-7D8D"Z.C6\DӴi3&ሣ>#KkG[ݜGc1@2ΪmbF~E]jww_ϭ,+z/4xy~fxm.sG5VON}9-"O*H-Qi%#MB=lI kӾ5:x_ԭ#Hoo#qZ4MȢ_4R*ݗbG-"ci}j|E7PȺSl隸Ƀ2Gv> *!b^_~)]tdnڽ؁vy)ДMTeVOf /pXim-x ԴĸG2|ܐ>,~ @o;mWQE٥M>(y&Y]3oo)|j:w"D#4"eņ61̘$>0@G#^$5 luOu |c*>DZ#+XF<9nѲ:0`A#߳oÚυn@ѴKD{Oeɏ_fQQK"4,D]_>'EuoP|`x{Y.KdI9t hǟ ɥ&;I[LV5nf#as*L^oZ]jk⫗ᶷa%bj,H=q$8P4Vlf(`4v*΍i kfj/jVJKH4W?V>j5?gZ+}hp" RY6Υ#% cw^/}sE5~B/|_Z[-tYxVgYZFcouc~?MՎ\摥/֬,mIo-Y'&5m꿷xW&-;QtDՅs_jYݠ_K-@.Uad8FscޒĻ)4K*,oq л%Vid!nv,D#/8čwgZܧۗᇈ/ḿGgo6!xeRGXǃi+a&IkWxt];{ieif)o:y×w7$qz.OKt{GuAM649;WQTC[+N |ANu-.H[[]]:KqnDiBˆc<|FwOº:暶/5k#R+=.'kw : m(*6,4;:ƍ+YĚq7}&xny,DjvFw?p'}PVmK/OdXv3(\n,p'wUkSye >+-Kqqª 2AvIjm!Ў6_q0n}YUXMVÞ>-ȑDai<ui]4 Fu?ɭ[[ѼE "5t. FyW YPl?*<_bgZ6y - K mgc猜g5?jOcj^&՞}9ўK>MĊs$F {6*$9,3>i!eOU9WSw~/) E[IR?eVQ&vS7Xl ;XxS#Im F2FґXy qZ?MHEKn R= )t.X+L^3J0Xn=wH׮_[CsuYY4vk; BݛrYn0WQn-u/~'kűb$ 0Fn$!3d^+ѵc~ կ9nMF#4T M]|3 ]G^ }Ɵ곦SZ}CK.mƦTaeIB |Jt ^jZ֢Wz]+MXU1]DbKm;p!H|Xm™f"^;Tf9 s|"WƝ A?i^Y6xV[캂M݁62C(bJTaDo&pO YI})諥ڛ,k?6P𷗤o75ix${8dYW .ao݌u_t wZ|uusgH:R\"p$ni0M3^$Ҿ5|E5N]2D^%K[e/垞X!kW&ңҥҍ1̷H=}Ecsg£>$pVRotA_[O qk|@o :mi-baTglc|52[0Sԭna7I%ݳAsBRHaaϋ < Eug[K4 $ZFHt) X535oš'UtuAg8Wg1sf#'&`XxSyx45Os;CS[;ĉm hmd2nQ!\iwQFsL|]3gvwӤlxn.ItrrȎ _޻ΕxKH?Q[{v{KRK<>Fp#-K^}4U[v%aəbkeI2/80*k_i_'Q/$4WЕGVO@Eazmٿ_~O,xKvYEVI"<ኅ<[?x{qim+@zmơpwh* [$ Cp#ҿm&xcNJOZqV>T0| ̗r%/ؑy4jNHͽŀ?8RI*a}am^>o/kVvLtXuG[E)%.eX"}C~ u t"4`{)U^6C) EriZ?­o Ol|E둖hI{F@~@x;Zτ.?dkmՌH,C_~ΚޣnThzZ&b^j:l|W?i?%$YO[eUSkM0 ]Q%PD 8{Y<eSu}O״X8Vwh ME6#egSG7xo7^x_L4]Nm3[}Hof,ۭL1 ~6ϥi Kż꺜ݣhq{,%Ӵ\2](Q|OowD>z NQ&h0<-H6@ ^5<[Jմ_Jfnul;[hU![Ye1hCWQg4-:O_iD.mKu=2c&AgMLڳϋ>6x#6U$/3]c +2Cꪢ(\N>og;k>/uo^5 :xO:rYA!c 2%l 'fKx?9ێOq^Ha^uVkhw23ڄbHeۡh.p!cϾPb?b &WY2\_گZcuyڕ ؤ[IwM($?p:p h"x@oϞƓihv~3>a:ԏG@]ُ׆d)/H u=;YXյw0PyɧOŕUDF@2C1Oř~)h3c۟,]}i8%1889⼣SyWN6.p+eKbya]"vM(]aI]+2At4:]QdQq$3W4j\J4ᙤg/ZVzg» fMwZO<vYKRJ:}fh|Qbcޒ(߇Y-P M Kjl{UCg!@> }6kmPգTOgiupma<-|Q~!+I]7A؞SXۆנDi":#<'T Z1g;LB*~qU.UԴjznrK%vn 6sڪ0+dj%O(u52Bn-䷓k#  YA|åV^zocAl!=qYGqs-ŕ' ,/9];Pp8(gJR gylM1d*ŝ1 n Q/J-xYx/BVAXݳS;8U-o-?u6wjƷF4KO&е%Pw?m߰/*(?,Ҽ<ֿ |%jÖvZ)u8CWGee<b ^ծ6<15$dEkmo< pD.egs<%}BU69 eCV1Hqr +/-I?tC4l[r%|EIL]OEo#_mlv+*civXa7  q?|3 w|3AE59h SŞnSW^ѭÙ1ہW H1m~×6%֏{Ka"*)1]}7v' : [! %ǵY6"$v|iѴ+ǭGo iqz4:NKys=sݯ`fHY?N}BR[ h?ʺ}"_"(ČYʹ#$ dKmCvx'x";~?DbxOɍý0@'ॿ4˽ᧀ6O*$,#2GVV^ ޝzEwCxZ滵M0O,:]oT?tWw vxZ)֝k`Y$p[PPdc t_5?ז;>igG[Q9]1=y n]߳Vs-/]\|Qsng*ʟdB_J>) ڙuY_H`_+H` Tvc#'v7?msS*lOy|9i30kwƣDHUUPWxJ-bOkg ɭ66+i<֑l r\A3GmGMHm]3Qݬ LDֵ6RQՕz@mlemZݭզ!5zƁ5 Xs^?f6t&=2 [%嘅FXdYDnH}㟛 ['U5-żteK*1BG"#tQP$! 8 gd|d[:x]_aY5i[중q4*JHG!A U66[ [C׼IzNx4k&ܤM%5rn#.UQهo&i~.a|[cwKq0=7,e"8TQ(ٵdž [ëmͪMsX%AUWp'$p  7] RJvvv~K4),ňrHkȿnB xǂ aK>A3_FyA $:vZf>sVnGO[f5T"8E|1[/|3/ڟ<I[T4}>+KEX y,hfUKfj k^o4;J3Hq-oCM>.W7^( rE_YSA̸'/@<+ccUıx/Y.NZl:з`HU~̿!<ŸǦ\OR/,2K挑 ʳJ61Agf>|2%wLH7+8JUd[2R? vBKC Xka]k~/~(ڹk-Vhx[yGFP-g+|HG7O/QԬ/b𭵥?2IЬH)e# ?e߇o"V}*jNjb1聏FrF|CqyGT֯\W^Mc=p%3]YBiO>9v 4s"H=l|xi-Oqpvɔ>~^7,,|5ljʶz/-om3m1@F8 a w_>ǍO? < ,-߇44B/v gBݘ&>|I)%{쭿]x1ӭ\={ w*m Jjܗ=/ğ#GL:G'ͣ/ ïwXnċ4!4ȋ&IYRH؀$Bٻ> c6:|7hqеYJ_̻"c _5sodM;ZWۊ}s*\IiEF7|H[4_xK8_؏J=#yL;kE?-Һ& +.r]vX;Ap%x_`0UO //|jq\hZEfPKo *HՐNj< ~yMGD슞"n-SYaKGa [H{gI_мyx=,'VXkֺeF 3"18ɹQDjq@=}?e߃RmeKn,үOSJƟx2Aoj~Ֆ΋JR5d!,p7;ez'3$nQG_7[HL'ο |#_kkou>E1ꒄp#|qӑS#?> $/v_joJO?ëקPsa/A ʼo~k_-OSeGQƙqqwsGi"c%K19$zy&'HoكR?RgvjSŻ{φ [Z/`d#_ʻ呰;82E#ƿäP~ nW=ywߍ x~ԤEݻhLxvEOj>ҟ.0[#f(? Ͽ<#GN׃Rw&,)5=,-|pC~4\hǾK[R|sj7Ǭ#JuRrĢ*,|o;w-#H]G{&n oK1%I ^+GUt k-ܱq,A )xݖKk.gg+b>1=ֳYxWW;[ȵոI' φ}BS54{rfܰ**6/]xWOih:x3S26"&;70V9eM5ŌYl6ҧu%h '@F#Cq$=5O[I[MuK'oG"YW˴IXð|cxem GI4Rjֵ d񛛍y 咡U]WJ(~-> ]KJ/c_yM ԶX_%YD #VkZDZ#(Rd&#$\H5\m28#ƚ?lρtgKjh^"3*FF8 U@ڏ_Zjx1j֛{}i=Zvx-qLEZr2 (w}F.<x|<7f$hEbil\iI<|JǞ ԭ|O[k֓2"[#K Po~>7Ew3xOV[w`wuu%}wSL>$vӾ!FݠšMkum!kjIe/%)ԭ~%1MkȶcƮ_.3N7?Z|e_!oqEKx_Rĸa^F+ :캐Aܫ2+OQ -.(V[\V~|W{[LtM=JĬuu@N'E 2 |G(?"<=⿆6|gUm-@ \m_.3IXQ@>2G/߂/!x+_W|7i&Gń7V`d'Bݳ}̒ddEreportlab-3.3.0/docs/images/jpn.gif0000644000175000017500000002442112120332300016501 0ustar rptlabrptlabGIF89a!!!111BBBRRRcccsss!,!H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘7@``ΠCn@@: @X۸=lPmh-5KcϮnxe`ȕ*?W6ߧ7t }e:>xG/!TهTpK9T@)SXT)pZj$A paBZpWgZb)tcj:>D$cx*@eBhqAZ%bLrAP"h!Q*nl90Оm Xt6G%0vg gpD  @\{VCClD f@ږApD*i@%z[lB]s֩mw9@"I='Pzك+6mvX) v:;AJ/ պ nsڦHh$@*tk+Y-8P k@{~ tkZ9ڬOi @ @PI.y{ҵl#JGoQ`=6(P1 Z\@>4t5AUnaxO2@Sߝŀb`mBิjnP*׹ :C@:Cf2Z hр.O6obtA -.[k٬=a@ @/O \Ignmz{?E+s|jG }%?(M(=vA4 D20,HٜrMl2YrԺ`ñeH6&`NyKݫ @v{)Ag$zq]Weu/@mbB-n_`0Dr܈#|j] YƊ,p,4B1B@ ,S&b`dIbnDn  B2JHcCɥ"2KGBHrR)HH0}w#yɗ8 DCPc'D_µn%Tkd- +Td3 %WL fq ̟5S&be}|г!Ȃ_"dH㢌Z$ 5(NRUxcJve@M2B$(:U R'Wgd-ɑ)2+VyP>9/C<"ncDN.b'HףύtQiIo,|–/jj>_侱\ٌ|>g<Iz$ml81Zp~N^yk:ZJ()+! pGm.5H5ȩǒps^%3~9P*'ؙ*UW+$O2O ,y'&[`5ɞag!$?zk]MH&报YY1g֙Q2B@MԤmVz䔀 aBKI3 > 9O'l /MJrDnWICD))3k-:/uE\aӅƎ_,ٳl1B. <ڔ6*n{wA@rNvnq·{xMLljY"̅3ZH Ca3@h-8 2)Ij[v5DHS8*C2;3n @P9|<]9`ӂ 0_\Rb!^I!:껲J.hΆRyD݀Z`g,5@7k 0< C \h=\ΒDM&4M{0CKww (VF@\LOx[HBQ | . TT͑t'f@ p4D5M+>+IcR{]i#_I7< $NWN*}4wDZw! 0Hab7&f -@q|$.hq7 Uztwcwu&u's'PH0W'D@B2-st&csH}un'<؃Z%,&'A"$-/qx}aC}1X35ʧx`=veHq/$tg8RBzAvauՇ64Ax  * `c'US:q A|PB%Cv~!+@=5tR8nx32w/(6b*()s"97.p(\rBh96VCg5EC҄@(E2X6Yy oil\u-$2&Vb#V-i&(@p &(е&_r,ْ.0294Y6y8:  ٓ>@Bn;)nGYNƔJNY!!hrk[U=(j:r:TQ3pvl9\y&EsXȃ}Ƣ; x'#a525"trw*r Qwi3?tr% 2b)!&#(US%@b*Vw2XoJiƁt1qw( dв5YO~D7ѝ6C'#icu *+35?f^sCC n2+|XERh>3x 23ũ; PO )'^T tZB94[)U3ȉ16cd%u?aA@%rT;:PlW2;LF:T25/c68ʝ2;!H#؉ H-r0x:9Di*ty&1RJR ahC:1e:vpC'l_ĦcR&raht"?1ġF1jUf#887 :"U@MJ9cd6ѤTRZ6i%?G  *2h>TOEܱ}x:.-ziڹ3֐² q9 *cJ&[?qH 3G's{ YZH 9LǗ' * 2-"XSފ!84R1J; @G?c(+ j?S$ES,k@?Ҳ[hbY=C9t[v{&AnBn*.C h#a!@ӱ65~":Rpag/bƳ;ŚJt!7( ԏ\Lwp><gB3ܿszA-'޿}}i͔H$4Clٱ=@xB whҰ:߷*d]t 8qH 0 ~Gم\ S Rڠd)?X~h$nDL 8 a~cYI&G" QTN*lcLilψKn4E"*ޝ&ԑXkrs6 ''s<ߵ~iTœ¢L a{ypi&@ T'SnP>]xN ‘6L8i~Iw~rB Q3|h/R4-d.ݸ1/D/  Bҹ חPR?T_P%|ϯT#PdHPi'fjE`Ckp/ipf¢pis=\km')HԈm al렏,_&.LjM7=_?f߃< ,K}pDnU66TS#oolُ|mkσ7oo$N9p. 0ƞ,!A  D`B >QD-^Ę= A;"4PF-]t , HSaMN<0iĥE>Uæ' pVl_"(If!2(p@%D (iWOj l:e` 7<,XbO"PӀ "lZJt2'rV0gDC( ue!F9ԴJ W,>\rCJ h.q hn@an@Z?C- qn٬Z dP  ' BB5 𰁠 "q@`OTQF dP Bȳ U&p`ؑJ@C K+10#:2 r ЀX lF* b |3Hrе j: Qdz9/4, N'BR0I F?5l2'|5VXCKe%KDǯx ȇfsqA4Zg/ `\hMXCWL3QD=[55 2p%"5~ 5<.≯d6c-LRKU$L^͗mlu褿NY `>qdH ~򊺗 N:7̫ 0!Yz!t `یt6foy;?{r[̻ſz3JXj!l鞛 Nh0C?6ws&}AUm(nс؆6ܞSND/[y]MBGz;\Oh;{:D}X(3_v!GibK} `BgkR2%H(p0=@~]` M ƚD!29>RB<ʧDSc_Ї?b8D"шGDbD&6щI$ /9c:b~DTE3AHJ Ka xNB#3j| vMOCNhwQq8cEn!-/6@L)#EJtIBVaLY$T%c8 OҖ)H(Q&y-9̚XMAψL3Nf LjVӚ|lvӛ$JnӜD'5qӝיrƓOg>O~ӟh@{MlS& T}<(CcPV4@Tw J*b4C-ɦH";iJr:)yR\*S|0 2+!Hg$G[y"e'Ddib:ݱHVČ)eᤀ=!.c(QS!$S^+UUj%bFhŰLhCX~)^MeRHdgEX=&2苎e4$̈́ YdOZ s8e!<ԳqoIZZ:8Tu@&H$bւ)O|sYb4VAP p̆L2(Pu&Rd}ȿ݋6Io{U`8E\ ZA m袴N,V6L_D15<=>Ċ/SY dC2׮ qtA8Kn(Ir%QhZ[Dܨ#357K\$mnluPTvHdDL!}SQ0"v5|ĭ_0u ٠fuc';>4>ן̵Hyoֳl$@A lxg&FEfPQ'o\9 XA!&6Rj7b!(\sriYtlnd 8蚾ͭ{>v>]ķvna4 ƚ E=JA@yKԕ[ ^GIϛɱ"/Q}cW]j"tQ Β XĞki۹{/aON'χ||O~j廒~ůtտ澨?{I6#=׏")mrDyJtS@| *Ø > @[ ?m4> Ts> A[ $B{ =[;$,%#B'dA&BBK)«%0 .+C1DÑ"C\CL7cFaע(8eDc<lJ,: ʹHj,L gO,Og^^^ __+=K_q]>}_ $ZΛ]=`#F`Uv`6B9<&Ԋ`a#6MC>f1ta>*al2$At* ޕ=b*b%^bjb}bwbpbb"5'xb⅒E/!E 4^Ĺ64vc9'c.WdaJd-^d:>\a}dB] dsjd1dodKodPdQn eI>eD^UdX:eV[DIsSU ePQ9b&1=GVSRE~斃RfN.,}k0şflJSigFSr.StN. IvfH]T{g>;[Z}h ;reportlab-3.3.0/docs/images/replogo.gif0000644000175000017500000004030712120332300017362 0ustar rptlabrptlabGIF87a  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~!, H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JG&](FJ ԈP^ݪqi?ճW,ٲhC˖۷p㺝Gݻ+/x  aܽ>EK9rƉ ܷo޸mۦt6l׬YFm4iѠ=sƌٲeʒ!Cf1bÄW/^rE֬X\bJSF S'N2Y4IR$G)JTPTɠ>ƦY.W͞y 7iF lL3[o'0w\rB,t *'(%P#<"r!"HxRJ1cߑc9a` HZiجVM5̓ЦCoKq-< tMJuXJ)Ix7I$"h2t3,~NN8E)Vٚ4DlUp - ,̩ʉk)ݞ'$8#(rH!h"T#O:eif)8H6v%k 2L3ޖ[/i*k-J2('&dR%$I$HGsT6XUSi8̀hM6Uce`;Ld.8+fXw)mo,v &} 8 Gxq&tBr|U<㔣PlZ6ڄ5Դ f.,""8u(J(sIxhIy%GAGC>Vuud눣8~e]Z4pG87zp7ݡ-3+rX'v'Tt#M>u1}:!$Csdn6i(mrɄ=rVQ:p'ÉN`"F {D ,aNssCh|8'2h8 VQʍn?3iU؅jCBVauR4R;' vK( Q##fUn{FBZ9@{-@6nxK ڝ61RGo|csB WHp?KѾopK@Q"M7(A{JԢHMRMx塄$%9Up':h:4ӭi$]l Nqxͫ^)CQzMP%C-!l(+Ꭴ:8FNc02ҚMjWֲpc$I@~ !{sZP=)F "q IJ%5HLrAҬ z@iKL.QJdX('}& F@" )asN㞗MұFmۀFvd%Iyx0DQ?L-[8 j(Q~X18Fþf@xAtHȈFp#A{sؠ5x;2_8r@N'TdlM.Y$bD".VY],P:i8,19' = Ov!Ldb%VF`[8;VjhP#@Y@{%6P QB2!k9c(E)!8yo@~aeCBTX?Kġqe TpPᎠP${a#C#Spv`io47 B ` (\?P[1 0Ps0v7wI05k+(` Ұ "*] z`  bEU~Pp;cyyWd0_ +׃0AsDG `1w+ٕ}Eq#T#$UR|E8{st`(d*|Z T Aa .V#d#rI ؇[XSix`. o u%\r A`A]JKJI@JxF@ZPcU%tp:Պ. /T0 3` p P_Ȏ`ЎxKtPf\k,Pm (2 0`_>apD891{ci0[ )q@ p!p4`@Q߰F|UW#F'5E5_pjP &)q" cz8e$e$@ yUG>gR{`uDPr ȠL@ v`zp9ՓEwGe ]_ 07O|Ї[u`=@@y` `P `n P-ɐ;^[=pTP " qc p+`dRqb`e9؀jg@ ě%97 k '3>b 9(p %pra? <zEU5MI B 4t PQ G-IANeƕx;  7 pa 1ϰyf0*Тa sp 9NEy$9!!03f: - P:41yb0j {T p!R3 ; 𘐂XX4~{| 0P " : )0H5 Xz 3JK ` uc b 5а ڇZcu Ap Z ÀuC a n I-WxpppSP rbc*Z 3 } ʡ6Ux`" 0$Q !L@Cj 8wZ |PcIUTq K8'q057  R`H7U(Xwpv>p|D * y%9 P3a Y AP>=8c z 4 @5c?Μ Cb[ Ǽz t~ Y>``3"Iɒ] Ӟi`"8IA@??' &Q Я " )7Ok3+ ` p*Cȡr 1@V 03Xn`kp +X{0Y'M@XC KAP2@  rr\ ?Ȋ P 0 ސ 3XMK0 !9 X,_x@ [v`}2,:& ˈ #` )1 P }2 5# u 0IpC V y #BQm$w8`Nh@=@Q ˱ ` #@1 [|֊|J u@vT3 >40@⿐erO RK]<@a 9/@ ? @#0t\u<,r k0P$ྡK s?c c$  K @@VŰvV θkX"aG۠ A .7pr e~@ P PBѸ5\p ^{! |0 V:D  v S $8P r  p@p ` ` ڮ P!C 6 P@ E塂E_ p6&5[_n0 ݚ paLa P ` pD @ @D`ޚه5 j=P:c 20h@+ 0`D' <)L, t F@ Rlǹ` ` M @H[mH 0'@ `" 7j 3' )K,])I4i4IϡQ5zFF%Bd">NJTQC N9yN˗/]hb'[V(#EI5+]7&G)2d/c8bx:{ś.G<}%jԨRRj$g^5K-[n•+LQ#Fs.ҷtʼ ׭YP wONjj՚+w,X:z媕XOi.m~kIQD](\1#6M̛ӦF4ma$&e>aҤH EF%@Ę#C胏c=hn˧fB 2" 0‹+8 +"(p&bV#( B !n"(ŚxP/+AeJ0+QH1lJTh#yFX&,T؁YڬŖ<ݤZMN@|RYd%WX(e%VTi`ч'!H.;/NO")rIL@Q0IN ą4HdI$ϑlYEAЄAt$8AJA2C0+@*+止)Pb4tg 2" ` |xСnp LpF2/{,I#I(DP=g ]3Yhee]`yhɹ27_ F>8[yR VV9nSQ)wxSN1%lRJ)EPDRa'`.Q$o EG ZC;0;ÍXeˑs(C X7]/0j - *:!:y B<2Du0g!L2WR#\~IUƧ8X^%8Bc]֔g`>g,;I g=l1O8\@VPM Kf}8)Pm )D(DF=MpW.`Mx 7H(* KG$ĵDNpA!"!&L!K=!4t g(C/pa [p/*Da RBb Blc0l30`-4V Dϑh GįS6PHL8*R R|&PA#vQġ,F F1!qpqśH= (STB'qx ٮB; s؃ڸP O\D%(f5bz D.! > -2heZx h(CF%"uGPM! P ir@!*PIB$ x{l 0`\a]#1 0[xJ"eN! ɕE'^ ЁM,!RHcXE1*5Ap:2QQp1 0Lb | 1 Ek3CKFde! IFP̣IxJ ,4p$ J s 5c (B26~mT-aLTERxXFHC$!iR_*B|;łܫLA78TQe#bN~rF42\ L'1➕$ 2q $0/`"-?E7 n d@Q/|a ϵB7Q H^Ua K]U$dx%`@+C Q^LDytLcP"..8ښhA -(E+^֨l-! UXP3XUBEJ EXu6Qp .01CN (4!  NrC xR20TKP1A6A j8>$1 H^\玔 " ~4bLX@1Aě0I%I6ADq 7/CFE0b%*#l!$)a G)0uLH2uK r7jjU 8wm AٴcS 3ݸ# S@X'>1vR;i7Mul0)X vЅ7`q@P#=!<+E( }ڠ3bJzҠ4Aerð Hu/+ϸөP+ @Axj၇/j( g=/R[ EHL'OS90lepf*+R+{ك/`;F 4P/*.+3d6h3.x.,*XhnPh&:JpcJ+j: \H;ZDPㄿ8RX!Pnȉ]*6E8*Z.(W`Upi(d̎QЂ/P,0@.Qx8x/hS#Bw+c]h/sXbd7GFzH? 3?C'BxN ^ZtxHL8 3ر(H&u&P94S8IV8V,$QYLŝ<Ѓ=@( 7ا'T0O`؇X(1\,H A٪- gĐP0ʃu .xn0VЂK< -,+pUY(&p'E4 Dx"[H)o ~`38;CP3H ,wJPh#PeȇX :Vx(y)I_RT+qLȁar! ?xF}hPH)Qx_yJ0ыg14e1(JPP>Clك<=0hBMx'Jq2"N.82wP,$)*%9]ȇH #h.`qE!P` ,Hz2H69 E :8K9H](VxS8THۀ#-*)`U*ȂDH0$) NQPd8 1@h;X( OFmvRQ3͛7G-[eÎEQ8k탞ʉ>=8x7h6X6@1PS4`K9 1x8p2Ȃ>@{%7rI!(PPN4C1B<El9PBh܄HhHKȄApjpzpA/S ASK EMX~T(~E  (,O8+p]]MOx~ЄRxPP8ށNha ZPhfa;1xRO0BH(锉?H<:9ER>"1A;[.zDx%@J=:*$O AV8X]0aC/85x:k7̈8b U*Fқ"l0TU(fA[yHQ'/;/PQL%Վ,I%KHЀ SLxSI1X>x[}(z8D _Fh(jlKF9EZ5h(AH50.R Ä?Px|ȇep=B T6K7Єv jGw  S<_DPxDž{,B"PM I(J"Є*q)2*2%H u??0I=P;`i98p7@ZoZxe50/r23zyi@[X0'z`Z&7{Ň8!36<'//Ih6x_`} L uBSdYB vrRh_zΛ5L@k6r*_K^mIX*8R h$2EB'q \?_涃:Xs:Hk6gi:2(,hf(1>S6 3r+ 0:(Yuj&eo ! 4a!0U DH2@5h7:'AX%'pK=hosJp5͂*0~RoOIͤc!/8m|O4v2IՁXNJ 7}(Z+F0)H`Wh? @@as9wiv}.5oHT/8 2j.=xB}$h'ORԣ"H"e)!X<5dp,.ڊ!i@kD`H(6؄fy`Lh(2 %Ў+KP8h{r\R / rHv{M8EpKL!(ϧbM8b8*Tn%"EP788}Fhx@8anqn;_7076x$P<`v8>⪼s 1t(8#9{Jxw'Q\ 0@!m @#,/Apާ'q؆G KQ#]D5r.sZD(L5mԩLF=z{M'OD IR<]$Qc=oyNb KHVt騦Os.߿r&YԔR%J8I1?ժi"D *DС,,(Ϝ=|g pK,D!41Dqj) LO=%6AbR$oN8?s +AG@#0GNxGx"Zb҆h2'}@xH L2/|"4RH\ Aٲ[t2PD!PVDuBJtʄKF0%qDG1trC(z?X##ltElX4JC;Cl4X9p8԰cp  PAZ+lb6`8XS)zu#C>С qC g;E&/֙Hڱ PR (~ 8)Ha]޲%KhEQN?A _B@F+hUlHEa)JF.D> q"hюEQ;_,6э}s o賟6ґ;#HF/fV4SS!7W 9 05X1 E-FA On=e `bXwqaDȧR!p83|1V"?A~v0G?Ӡ?эe"`,Ʊف?`qDg;XVz=Qsp=rw8iDbm'A H>ށs#MkՏ|#xnq߱Q^'XOҏV,PmQJfCY(9!ݑl0L=;ZjهZ Hl%,[%22|dZp˼xHoP7 pӬ5#G'JvЄbh~.r?>;ԡ3\^ώ~4H@"I X0Xt61qdfԮ~J8]D$W.ºj!kB7f[v">r1;^`ٮn@CↃ rx,;V1 >`ڽ}WeX~k=7y2p-B8قTLzy uDD]r`Av펳/z|Px[{bocP=RFu7LShCn Z& a="d]1s1 A [e3@pМ2A߽EG*QC0~C@Fķxy/· Aԫ@VdyC #-QOxO q3 004PL|"1wcā>@gm{ ID:6 o>2A_ wA HA(i4Aɜ @#DIn? B](BquBI4A 0'0`' `tQM A|+\V"؁` !T B3О "ۛA@B[]!Z9T\FaF쀲؉A 13AAA`Ztɠ"P!V bb$G,^2 _Ap8"J' [.]a-ɌɁc l*3C!HӚI LA 24~d A-:PA܁#`/a>A81@ e%y3A`xȀpŽ!;́| $4J*Z4ƵABmHe$"Ad7 eU?l#D&B7HBI"fe:C dHA 1%NI(e(ЂѥE:I؁ 4d$ ]%ġ1qf1GFS)jBm)HXAT4ܮ;--έm޲C;Lx HTAZlXT^+jhelf1fHRHnHZg벮h֥]*  ՀzfCJ=DL\ktAk`Śkx }СAV*#瓊>)**F縀xFHLXkl/*iZUN&V]&"|ohV0 i$iX/l\A+;Lp4S6lP:hoy(kpd%0h"F"|) ! # H.e 3 |(0kxkZ/w s/j,7ie6|z&VB1PTV2"7)NriwAA A2|_Z, A((7mXf.lp/~/:ij&i&芮n: 1W !@k*%@L@L3x\b WsF} 'rl^%窯A / 9κڏ %o8'0K<DAHwP(;tJи2,;,;qȆrlklFCtMp$'OP111JrZB߳=3zT:,A|t3se.ukdd[:8sq8q 8sJ97(BM$C+H#˕go9w99DBmK30C3439׹9繞99#>?>:7?:GO:W_zK?+aoz ]ᧃ::s@;reportlab-3.3.0/docs/images/fileExchange.gif0000644000175000017500000011166412120332300020302 0ustar rptlabrptlabGIF89a+kk1R)))!!9s99RR!!B){9s!k!k!!!!!!)!Js)!))))9)J)R))1))!))!1)!J)!))!)11)Zk1c11s1c11!1)s1)11c1119919191J{999)91s99999BcBB!1B)B1)B9B9B9BB1BB9BBJBBkBBBBJ)kJJR!BR)R9R9R9RB!RBRRBkRJBRRJRRRRRRRcR{Z)Z9Z9ZB!ZBBZBkZJZRZRZR9ZRRZRkZcBcBcZccRccccccccsckB)kBBkJkcBkckckssRsRsZsZscskskskskssssss{ss{ZZ{c1{cB{sB{s{{{{{{{΄csBsR{Z{{{cΌks֌猌֌kss{9ޔ{ޔk9{{ޜR֥֜省R{{sRεֵƥ)オ)cZֽƌkƭsƽ9ƽƽΔεεBsޭ֜{΄9֥޵޽ZƥޜZk!,+_H*\ȰÇ#J@ Nc"?D\ssZ6 gj4U45aXvT5*0IEzcdhXz8U6XS&0dihlp),*ؽK2"ٕEucahp\Ng,P)vL^';quh,v͘tꪬ*֐ u)멸뮼+k5nPE"^8&N@*Bx< ^ 7G+1Sb;Srq#Lq'ז(r0/l33͌< p\M+^n>/X"_)rFZ>p]A xb);C..>fO9w;{7γ~̘oB섐G.f:q)).ㄺ!/:q .\:ktƯ~5 g]<c>GDbĹ41LD"('ZXTb.rTb lֈ`@PA= lA&zX7,׾r],bg+2-nw+%CL jHMr:R\ }C8F=Og=vR2TJq :C|Kw/X.0`+8}p' a3^؄1, K0=_4Y-McXL:ӿb|X՘M%KLcf8b_K𓥬a%O"˔430h&߸4pn3\@g:yuvPWf3Yz9GhAЁ3sь^Qw3n@Y{^Cқߜh::Ґ3_-k99ַuk^̾f`8 m$KΩc-T ζn{{9GAuF@!oC~~8.O8sWNN2ġF*gy+qǭ]0cN/y7!}|цt;KyEt;}x7+'=>DGnuog;v_K@Ehۗ~]*gLx?C<ҋ>[x1{2]O؆Kw$=GH#$xy8z{Pht~3#sx2HzWz8H~8чx|'qǃׇW3m\g{Xzb 8'St@8׈xztvxQH|ˈ,+'W|׋FhݧsXx蘎1'x 8Xvϧj8Hy-㌸Ȅ׍8|a wmq:hiwu)u.!bxx$22#i%'v)v9g7 (7G脵\Wwt yǔMM ШwKQPtb|@KvXX}Wrivڳ^iyR!`Rk)uyv y=ǎrGc (}@gm12=zhu(G%X}WZ}Ct2uXq!u,xhq)Y{shw|YY~ yuQ#HSאUYIi8%x8IG!؞ۘhKY8Ǟ)RщQW(~F yt!*.Zz9gQrrv؈x!hHVo(5*x+S2 F6aha.@M@8?sFCj`hְjffffgbi.@KjgvFi&h`i.I[g^+cmI}!0q] !~퇘Lp 4#P h|xXNj;),ژR qӲ; K11j C{K3Gxj&mR~h8Ùm o:WDx$듒S$qTayAw4; qsudVU/]x tZw@ K \P l\ E`*Sj}騘*;/ !,m{!#)i@< YqYX9ᙜ"OB<1cL@ l : P2@Jy/ʬy\z Yܿi<42 O ĄKo? A[% ɕvPGq!mҦ/V B3m֦tTb|@|\  uLwD}v&zW)ڐl\G(3'{{x9.Nk7&QS?4;R24ҺvGt1.[.Fp)Px=S/ "K0mQ` v :Ll:ܼ=VA {5Va^(Ӳ1@5E tpKe1-g5ZU0-l5A)z2L U!B/-@9З(+1+Oh\3nH*&C:.P0 C\,NCE:з!~ lu,r Gq6GaY1|ދ:R *TuNI-.,B ^p+.`)!AB"uP-zbl ש3/ A"עDU3Ix/A@ $ V##V{H6.c4 ijO0+]c@"#T$1Ԇ/` ļFםL  U~v~=(y!;,;NKCA?|3Rf@ ks tO0q/0 .1TEc* r!u`qfX-,"iAߢ8nx1."^^$cA e6Oe_د+_N UE,MdjԔ@Yit)+X!|7 M`KFt5(),r=qO1*.E:/q?n20?ގ^@.M8n$Ǖ͗zRяA ."_Cn/p@/.^͓*$ k n` @ p hl:x sH㢸 ߏ1=*<@#cC֔>#4[N4tNb-ŨM7Tj^N@mLѧ'S2~mrK7ԡ(kҚVfmk\ٺVw+\JWկ|_WVtWv:XWU^%귞}%pM>3tLȪnUc6J6mvl%U좙m=bE(-%lV-ps[6%.n+ =npRuΊG86eL${5Y?}&G΄ǾHߑ7}﫿-UaKLa"3%&g+T^e,up%,HcyFOmmgHnFMiY}e)Xr%sx$4%#e×Kp*pg5:1A엤ðUK.gڏcpO-dvnVh#Yp]M_lYws\5ld#9b'ObUa]I\R12ZՌ4;cX4_EI|5ߍWxA!h]腵 v\2/T6i21$v3fq͑Px5nͲh ~@w78XJ1{  ™g1iXB>l32V&kH!}r! a*޺n@R<*u0Sn֎-n-Y%3O7;sEoAf'xk^P`D.HM=jop,.%vuÆ6acyJ#E.Jy(I((( /(8kHѐk88C-yZ *3' C ;h': жx~P0=ÿ=о:о' /X h h08 3 tx~>ĠkAXx   ؍k?}qR `/ t9( (ȿz؁ҘO'%Û43fy&u¤IM *r(Nzxk0>D`LjtxD,: _-4#[*KhB&|HHP pژ0~`$5\~3C n뿫@ "l ĕxcpCLdrCr\1󧂊'!~'Drڪb305ہcAyBcف&:DEjY;m[5C6(1XJ7k:8FFLmċ*k''wyƯ#S$*)I  ;Kph:0ls|aˇv|;DjkS*{L *L$ʋ,'>жk D|9pYĊ= k` `9!:h;@B$шE0ηx s"x(q7<4,Kwp\pdR|Tc15 a4bp1v* =DEP%QGMQETHUTJeTTFTL]Q5*Y`2Ys™l0lLi-a "_q/P # `Q1 ͔hlZځ jO짯5(:6 Vab$rJ%d!,C i <EIZ/I m;)Pmꪦjjꫮ .Fkk걶 883B{"Jf!Y/6e*4^&3V`s<A_Qrf2b P lNkk6&.VNmՆmF kv. =k`fMDii.쥦6dZ]^i^k~꜈0o=F6ffP i&ooovopoov؍pTPXB攭hZn^V쇪zzFed鮈=mp@HzȆ:Ȇ tJэB!7r"7#/r$/omAkVS y}9$0depgڮSFJ(fr1=e c "OOWJBJ`UQ?9?GgtL$tNuM/3tns;1d@JIlVnoV\CEݚksꩨODյhLΊnW֛S >Άh ' @B/䋅PUcJ` {FEoOp?$w"_%wO$.T+7X2gi4u٢^qCuEluuرQ*^=^vsv gZJXȆҠML'?#d;^b?c85CFGX&?{K7gwzޞwo :5*'%TY0M{VYcz_);axsF^!&X0yF&:F|{pzp? lzuB&P(ng|}%u'}o'T^0"&򺩄iV^8eʞGd {j9Z,*o'7ۂJv7=UC$O|':6OŭV7zzwxW}?}Շzbxc EթCh"k2x^IŔA$D(+ 2$Y$e?`qh?v2}rL)uRDWU)9"dX$١jV ڲVɢcg-5tѹ7ƪ"JeyQ$PF`b)Rag'e[VrZ)sެd%M;ZƂxCɱ܄)qfE,(]X1%=TZk;Ϧ)ٽ]@C_,Z[n4l|)XVu8w=N |Wd_fYdID5dJsY&[JV?rրV&Zh}F#n+fW9hUmEIO/3?ӏ" YS=0^T+]'I#AD 01ņz 0d?GddL/p@O׼F'a_Zt %\ҙ]O+0w[iH*_'צ&ւ r&aj}i+JY*ʢ4К@cYմCHIT5<"0 4 ^PeR!^HN@9IP;\b⺤[>u8 m!ɁPR?At&t$ya cWB^ t,HκO,Dr:\׸b%u<7E\!T(?N6m]z~%YYr)tԵ8"!uP3֤A=6ch)=ZQK%ә9BMB LxO@ y}Bp>#@"97C/ɎP@8KIu  /gĮeW b5ddQ?o? |M ?p6(/$"[O7OIv=J(!pNUrB៼O?(.t()B?v{$)uy@BX"+_(Jlwu2 ^h%^HxQąX5Q \_hįQ#c=zȉ_SHI^0hF/PҌuJrܤ'CHQr<%*S9T D/ | 2')AˇFKpC/Je]=8\'Mra^z<K^ n#ZD?X~"-gJs\C/yA#cth:` Kk,$;*zq0d=2!!H\ȥW Maӑ씧:NmJ|W? QTULUQ*ԟ>"*VoUFu1KM vCbm"C F]~4O}[W.. }&xP 8Gb=kbiT\㱦RR窱hC91Q^MA= \#m$I.+(j;v;R2TW6W$HD[ѴX.x+񒷼=/zPyoz26?q†h ˍր#F2z .b)Y-]VpB\ f"(l<(̿DIwi.vh"H4M@cYiu1'Q6'&_LnXٽaɜaϾA Ё70zӬ5n^ozב9_#Q[٤Qa`ADxmEDc*t䘱bأI" D.'F-4e,g&I4 [.U=v(-Ǥ]a$T !S73~z`yM<%]Z(bDNBh :YK,ȓUGbbN4-`J4V,%8IZ XA+waRAIQ1M]X M 8%DID]L^%i~viC\^%a"Ҙ` E>4Pp监Нa^nDLZ~EL-[EUgեi 7v3f dfwe"(C*(2$B.@q/Vd H]PDR@L<D4^ބS2t^V`DLI Jդ`V Z؁ ֨~E"Lp}j脾]:hwV#xIvf JL^2qgz(& Fw5ܨ鐄&eҥIFhǿ F)z"j`hibƵM~M&6pZ/ef$tvq$uW(rE骶xQ| 8hDzt 04ܗpbE_EIWD jjf:*eʪ>B$DB*.p']05ǚ!"Ec) H KA1$ShKs0Foߌ3@Ԗ]F| ^~v)ڢ'ɖϡDZW=;C*A(74w%_׽>,,vZ`=NVFķX_η(F^ P`mFΞ,"Vc{,d C9\C40䐸G|DLB-ɿZQ$ ɴV.6rlNMcsP֍xA;9FD5ܑ/|I9uV^Ff+lB,C3/t EDcbQ!: %(Er1BCa%",6^fr^Y&@^iBqV EgؓL=4TmZh _]ڱDIq_1\_]Dlv̪wA, <.0 sAƐh+w?B0aq?Ƃ 0 F@؅{ 1PAJsE\k V M:<bI ZKCp% d1U[X u739Y73::s:3;/Ʀ>~ip=<0 ;,D>ܶu.z 7_6ڪ\6H|N|tU7G1Ć`NIYZZnAp![yӥzUI򐬬$7;7uq{W7+WQaL4hs`mL#y_iYP}%mclTeynEX^vbp^\4l$H9lQ"8■FVDTz)ǭ%]S0\Eݒ)4EyzU߷`*g=Ayz66IO [`g"S=Ev\1H񥓹4[9m#6h"%.k!J ^DG \ Lq7|`o&ե~KzgYk%~W'wWr@_D R.dK4uaqҥ=`|e_Iz䙏Ϸs"w!o_滟#.{yb5pЃ&xp+yf>cSLh }/ tN'1u+Ǜ{0tx(Kpy*1Mzw? 8=/tQ-W IŅB407ys4E<^ N@ b~4'y ͬsNԒdn$/I_>H>{Vk05@!Ap&-<*B֋y1cHQa೙_ j[ga(s%Ty<[-̲Z\AFYF8i,٦m&M8D5@#c%\H󷐡 ݀S.{Ğ?t;I~OrׯN?v59fM7qԹE;˗^Ң6]zMRK)dXOb,bД]N]z\{:`\Pz@I `ܸ ]˷A-&Uje˖Bsɞ~`kf⦝fz#!=x%fj‹'l :  4fSN8>;*O?RL3ib+tqSQ0qPDEbtQbabp"^'ЩmQؖ4[{\yݵpjֈϪdʊJ#yAJ\h(FI51@4څ2\$7ÝSuٝiJ,>L*?3vbi?cSщ%+bꠐX +zq!/@`2}쳗]eZqfl0^36:l)@@b1Y/tl43y!oz҅9v.uOyE^,St2ЉXĸk(Bտ(q '~Cxd{@‹cqBB$_p iJof`F}`|tHɊDvB8)>j .&I+a3oz;*(PWA/;7QBvbU6#C*?@F"{)BReGי3#f\3ȮH}f'`[` #z=&`ڈ1͇2Ly۠4/`;qB5.]WJd)?VY1LPGTn kXʊ$E@P&+q V~! G8"Ɇ?j*.lTF m>tIbdfM}F0fdl4Z̰J50cr4)Q9 I(u$ :h2|(#<,3b$h-*tRt/EŮg|*ybhB^*EHJ-o8\вĨJjB2+`|Sw8bL~fjT$3/ 닁La{cG;(ڎ~( h?*QqL(EG*Q-E] ӐZ4iJMR.OTE5jPřAC|U4ceURy'*}fՑ}f'sLu1QJoFI;ڎp(]iG pu0mH֦ؗ*I RȆ=>5iQZբ֢찇8cQTX?yMN!OSEGqE2R8~TbJ4 f=jOjr[*Jb1q 53#-LQRn(,h_ZQ'=N1S7})HQ46pK7!aRt$bZtop U4#8au1YF1sJg2e/uhȂ?"/- Sw Me4OQ "bzp i1,:xm`=+Zn%sHk``EE@6Т- -7ZфNk`0œ1^s2Te Bl’D)u؅r1aLXr\ƀ$,u%M^2@. b?^6hG)pPvvv=r(|A퇛 =Ar6p8 ȊhOj`A6nZ%iM~p' wxR 0" 6_%=Bu)$ LJX\%@qaјzt1_Z0U՜S>ad5$yַ8czha62i lpA+^~ ,A8|NG0`Apwqc=NB;Fe <|XD;PNzoG;y7}m ^0%N}Eܬ!bLN Ɂ񝦐ԯ 0|ȉ ;Is j 0叻,ʗ.[et{Kw:gb 2F_-QL ฀D  d ځ  d 8v !@~ŀL+prpuy}0Sj. >h.ALa(!5\- @,0|rD1(_ GS-^.TSG`)BB+n4Њ>ʮVD!j`4@ Bk  .ځAwa>!ĀX> (eA^`_/Bq' LQ߬qqQh* ATHA??F 8`?q8r% -`?bb_B#@ԀAB?خ(隮H>¨0@\RVaDa:m  !¡/QA>G )CX8ހary@mݲq-ؒ!{!c?B_$c)R,Ṳ|U Z$ PB$6 @330Y,> ¨f/` 1}$լSB%8X( ޠ>\aq:Aݢm* P:: Blq @ے-@@GF**a*B_`b2c:DnbA*6d6rHFC (ȫK@m5Ff6,ҠXa ګ<᣶T>v͂A͆1>+&JͨjJ-\a9,ԽؒP +h Q#PU/AJ0##XPhFbdԧ|2c?vlKU#cCPG=Wns5z : X > 2K`XͽQL.lJvj@lrL[RP#+.nVVvi,Ja 3)·3lfU~P)O"hni&>tVAvvTW_'Vb#I*Q˾(&:b}wTT˗~y$_a&a^4l ĆH1^,2'pja,BxaÅindm CF:dlzU{("niy=Ozv{AK n)1sS\ES1./]TkD8r2D"ԧz7 o+zqf&TСzX' ~y8`̂x Rvz#A6my>F& 0` `ęIz @ Y Y. fDmW04|RCJ cdv@Z$)@*@I;S@%)zYs`{En(RdϏl!fa !G:'b!A[84v}Dd:=(A &[0a6u\0%t‚*DmR46S1  2AD܀|C$'!':cq/w)o4 ^`BDLCbEC9AKOlo8E9(8?ּW½D3d"h]k1f`@haj#GF*ƪBU>eLI q^ D$,Jm 쁮kCV? djG#s z:~dȒMh(M(%zGa b"(&γ'b:<蠍J!bλb3 zx @=a hZ)aCQNJ”m b6b1\CFb@CB) x׍pux3"fĀX`ba| e';̅))BB& BdAbcF@D11p( @bB `kBIkM@zbgg90=*VBrvX>C?TQI/Y|eF9wfС:E{ k\hDy }lԆ"!q*ez.ă;RKG{>ݗv_0aZWJ7Gak;oNY% ` .%f5fB$EMRMIzu]H%܅Aw6Wy6ވ#x.fтVvؕNe cMm5[FP2ɴQFǥXQ`8g1ɔ]?uHhYw@ SZbY[u:;p喝~ j5ژb‰0¡JuRwq'!nv*kL/ȧJlĚSPIt3=1s]nmj@, ,b(jL6\oKм ̻o;[ /p? q=ˆG r(Bk]CLmot{lTq5r(, S& 'FtItL?]ѯB-:dmqV rؤl'l,[>ܱ(wvywsj=- .xN8'˲w&BF#ݥל ;q1ESݮ!D;a YˤI.$3"?}`>8&m.иv$ͤ{9&+ޏOw)P5M?>nxO,n,e`?e,%^g~ AHE+4rJ.թ{+ O8>9%=  !q 2ڜYF3XP1KGh)B4@b!k4_ekf6xMi:4qSB9O+0-⑊=! v(H|e)/c! ux qDx!+̟5J8!/NX5\ 8[`P [EtST,XCj#UD1LbL\&3hBsϬ4LXl=$|VıdH FԁTQ2@ƈ'NЊ>飑Ab"#f* :d3>Ff)~)`<7RHO#gGs &T؂H6H-R^܀E#  b@kPB|M`+3'<@Z)d,-ڥ?DkZm8WiOU3A[7g 'V0-HMhIۋ<G"@%"z Y#p1ɬ3GC=)ӔD\+Ҭ_* )5鲖iur]pR,Uny۔ p[\*]Rb $  |8&I cGRU,nGY!nFH5@S!S\Jv0rB&ÃaApwl&y h0LCHE@2YJ>U{"y(d|qpw 9|Q)S`KX?H!<(,/ ̲2CjԆ il>\5 H #!we-{/p\{]^Z7^nrku&޴+J6'zbI"d?&OƾQ#flONtf5{Om.v͠p4y?eN&XM$93 lݭ~%Qhh"Q@+GQ4*bS ہIH~&u._ak`p r&(A .X>;Wsi8@ b  R5WjW9@ZvI? V8Vwʡ* )mp,p򽩃mVچ4l+ۮA,CLokF+J !!CzZR1n(*cLe@@R1\"k.m:P2pl%fPW8,M佊M}B@/x!IG؜mZ+Wj,hR+>a )b=:&$/ yď/xxx} :I"y}Xt ⩔hKۨk>=?C.E!춚s!={dtI+b`֩H'@G=BaVH,o*!M#t"jB*Lc"&ǗbF".!-xa ^ `.q(a l|ݻ~TwyD2&حҶZ&HZ+l[+X<+q'r' -g+'gLK+Lt쵚2<߹"4!A['`ܷ=x@m+n}j˪!es!y`XU^Ȯn;AAQKv|r@M0~0kK sN0q-^0k;@^I0?/3ƏA_} م1$A?qVw|M/D$?@ߣ5*bQ| ݇Ik @Xok@;pyg%1;[@.`E35ށvy+4$1sߘZ&R[>-1!) !;"Nl !G4DOT1EWt"Akŋ:Jv8m8"Ic'fIA'GRkҭ~xm-x2jlj7Žᢞ7| p FE8|j@FFͺcjk 4; B#MHAÈDI%Ĺh"JnuX(^U`|U_)YacYhUZjWkviVo-/ܨ'p\Ef%js€R#+b :q_[9Rv !'vE_H~\ &h g{jc!k(A5[x {֌tb \w0Ӫ xi"ٳ`Ǽi` .2k܂"v@k&f/*@"YV?&}cz`+,l Bg+,G>xX"$>bhD"QIdiXA&U#%$җq%-|0Mstq,PJ\ 67@4{, &y~"R;!G+*_€ѣch( )akJ``ʔ'0 #B %.evFM2D&iP_YC&3LhFS|1S!*P+G_@$R2D"D3aud!x=T,lw}b%hCj冤8MS׫.fv:[{n.SNv4nW{Tqv1fr dc,]~\vtYE`I@@4x4H9^xZen'B)mWЁKAp#p:"-&* ^.&X>dd'~Tu\n2E?n"ۚiY=]u1L̡C},n愢2 te.J`K׸O C_Go>OCZdx-_B,X5m_F;Zf+ bW(wh :5k-\5j@,@Z;K@$ 䑹?ʢ+[ ˸ YHXxpHAtt|AlA BB|#*ɲ̿[&eHH"YCX[V/ؕ`VapPIlPMٽ.Q ƳPKkD)iϭIJz=9 6(C.+=%T jཬ<2M|8[ ΄d >;exiӺrPa6DQe 8@_- ̻b[}3N!Ψ{3LS3s Sn6BO# %Ar5S }} %DT,0Ql^`ުImK)4xT 2&}b<#ƻwueDYZ6LKt37s\ӼHH$;>C郾ghب)ԕ=MV)(#$N 22|4.:OaqFX'$F s{&NSo,[`Ӛ&Z`19j1@j4+ {}q0/ x/ݹ'Qc+Rq5-'J%sЌ|~92JBXxXhXI7`/iCQM̋dϏe\ [d3 gTY$D3kaA#TL}RJ 70xFb2j%vp@&#)zx[UB'p4&\ P2P+8C2 sI.ңe/Xhm3ULdI eMnM|mڬMkFj*nz\Vmu{-xo ب:0X`0(r2:~o`7xѭq2ɊvJRB%ZᩇY՛1+'Ho$JDY\Ѹ )oʣLjFRf.dx]jeqTO l82p~8O'CځrکbAl3z.pj#:8Qڌ 1/0s'P+JoIz aW `P'#,w|i3`DܿOu|N@v:@iWϭÜC:pwP~qn&oQx)X :xc?zuAv#%\z8p5/@hї4o)74U#K;L&}x8y荟yUcҗ-P呿Nλ𩯞{/K.>Cs;s߻cn5?/w~~ZV-_㟿IO65Մ@#!:ҍWfii6ʋ (}<En68D}P `>cJ+\h6Y7ܡq'!CATE<`SHh;sˁZ-] ̀D"l/Zc w֤U)т D#xE)zwxG+jD)b(H"dy!kT[g*II=g,$T6Ă \7H!WXg,`r׺j~sn壑؜lfL`$&jk] 륏Tm,m+a3ߔę~O]#K6@XR^J9kVEfυ,kӠki|c"(܏+*8YVclpE;} 3J=cS7N4 ܔUg)DE.>?-Pi#a˜ b;C٨h2żVALR\K>_Qp*iT;*3Og1ḑ7SyIWS?IOF45:vCќ^v8}f;rVuP3L(6Lk/:lTH$C,lkOhɜ׃kFQXTp{sBpP5 T$ w84=K 0VQjO}R%0k7/B"$@;8 6p,`_7\ԳWA~8~*Wz_ߛ|+Xԏ@ƴu&BMKyGquCNرM;:udE`9bqc&Sy%uCA9`% Jh; 7<X<]bs9usG?$tA %hJ7ZszŠXKJLnPJC!u;W-oiba sX|$46J(zL`/ě___lpB5LGգ· u@(^cEmJ]C m$upT:+j>n^37|~[ޗqcay#Ϫ-*oc-C0^ȑ':6vz?r S #nx@gUs6jb=GA?BF UP!5"r pZ4Tj 46`Xx8\H ^? !?A><@!! "Qi5 PE0_>"8< \'ҝ(]>UBصH]>Bp([p_8 3>ԩS^-KR]4iH} qi%deCqS%0"p@gz@ڱ ^i%p e@vcl)O*DBbbud)ɝ?[Ai[)dLj1ɥfeIHfUSQqyuaƿdG;]Me{IDuIuq ;ŝMf%@[%Gܵp`dT2f `3?$L|fo=8Hf:TQ؞Z ):pPՙFCPV\Ӻ;Td͟ -HԥQ̚d`Qb!2Rg!wVE`fg,E/*;XQ\EzIpKdQLO)d`^VP5Pß6jwŊ580d5蝪m ȇEF`PbLhgVt%J켞jzMjbD`(Ư. kP4LFd؇WyXѡ +jb{Ձ$)qY0_Z$>Շ5tN=  &k,kn@(.pz_CƬA>2,O@P, ,r@VbgVbPvɮ9(%6mȈЩP8:ӒU_BlnЁ(,mJp@ɺYĴXٚ٢mڊBij \h%](L⭩HmNHP< jl:nnJ.B ^!]TNI-ޒӆiԓp |lKڠ DR(r.*d|:D#*"N6UtѴL 'ȐǴjY#EDzʉPS ̜ zo/o/o+R =Xno,0]AA.*Tbpk4>#m=q>ʤLnp kI`jĘSѓgRkMIV(IN7401#[cqksq{q'q;11+qJ 1?qsWCZ6 *FRd(X%`4ra(&dL2#Sr#W2&_2#s&w&{r((r)))r*2*/"r++r-& k)+#+r$kdx";F$o/ a(e 3&"2l lb53j3OlͪVV6Ζs5s63ʲ::s<6c3=o=[<3;s?s==t>3~<9'46B3C?t:+tCC=sgsgֳF,GF۬85K5ƚ3G˚tI4@g4IkLstKtKtIl>X'PPQ#uR+R3uS;SCuTK"TSuU[UcuVkVGPsQ{W'X5Y5Y;reportlab-3.3.0/docs/images/redsquare.png0000644000175000017500000000061112120332300017717 0ustar rptlabrptlabPNG  IHDR$$nbPIDATxb@?b] eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{GIkIENDB`reportlab-3.3.0/docs/images/testimg.gif0000644000175000017500000000163412120332300017367 0ustar rptlabrptlabGIF87a ޱٳն̷˷ɹĻÞĜĚŘƕǔȒȐɏʋ̈́΀{szqxnukripgnY^X]V\FK>B;?9<8;48/2)+(*, H*,`# 4HC (İ&p^xP 0/<0 DPA H`1B Ph۷p;reportlab-3.3.0/docs/images/Python_21_HINT.gif0000644000175000017500000007067512120332300020333 0ustar rptlabrptlabGIF87aGv!)!!J!B1!k!{!!!)!!!!B!))!)J)Z))R))c)kB11111k111!11)B11)19J19k1{R999!9)!9)19)Z9)k91{99199J9ZJ9sRBB!{B1B9BB9cBB{BB{BJcBJ{BZRBcRB{ZJJ!ZJ1JB1JBJJBJJRJJJJR)R19R9R9RJ1RJcRRRZRRZZRccZRkZRZRZRZcc1c9cBcRcRcRccccccccckBkBkZkZRkZkkckkkkk{kskssJksJsZ1sZJsZcsZskZskcskskssss{s{B{R!{R{R{cJ{ck{kJ{sk{s{{s{s{s{{k{{{{{{{s{{ք焌ƥksJsks{ƌss{RsƔΔޜΜޜΥޥ1s޵J֭c޵εֵ絵RƜƭƭƽΥsνֵ֭sֵcs!!))99BBJJRRZZcckkss{{!,Gv@iK֩hdJ0![\vEŋ3jȱǏRk3]*&(uRˑfRLdM;oܧKh Ot+hℜ2Ē>1/-Iǂə~#nb ,i?p-tmx|=NK(‹7ߊ9&3 @w7 #讌N:# (fC.zS;؍S"H8Al9{9O~(l׺Awm1^zjrH@f^K+O"y9|ӳ/9e~`tKr=1L{AyCt0-p0CXj# 8<n"`L4!E`.ocNvnZ. :CdcP# 8p<eXSa?g: JߣF1.H> IȽ]`1: ΏؿI}Ǜ/9BP'W@h9ʹ1 B;ВnVtV!PP (,9?_~Γu(8i[3% iEzY P>,L㝧1n~Fd Ej$X\ \ iQA3`T"|$?+>1@%J=%KoxcjSRZҼc ̠-Uqc%+͘&H8xдq<ڡr `vbՀ.2|bwu6#e?$9I12[]XEHd'tVO8c<*x d|[`ё[@ `q71Rc .$ .vzݔvϺ\EH)HA$`ZEo,Wu񹛍p{+rW΂p*Y|- D&Pq)!?vv>؅W4 '.D*Q;9rt!js;bɹzM(awNTQa/-IG48CKO`zLRn\^h99aТzb!9!3BOcN'' ejFcjC D ղ>ZwhNvorlkAg. ?яj 8A?m,oCcT,4K<13˕KyQ%^q28B3 aZfƶZƇq$Ԓ4PTN p3uCy4bzVcBC0!QsdXb@.`aN\G@AL@5j Ld @D2٢45!؊xG8\pcDFGq26'(P#AMxe-fY}X ZXs?5i7tK>q7~;p\41L5#8 bW0Wg"p3!_&H-Y F 4k^rJ֎tn ` b f0jj$UQU=uGyTvU68Er03>5 @ t{x>+7~9&HImBVU JXVǐ laa 3W#5FzY}e0 8 SPP 0^%H ȠF Z aĈm U0 iEF?ߦq` ! M !Ѓ~^X@_ K1O1 Q! PReEO! 0O ]PLb,@OET(O`i$CD`J P р8gAe8yLG-2'&)GH\B 9p ܳ]pO` hx gPɰa"%J`70M)fifP y"]MPOtjX7D! PP ee%7A  ]Ɔ o7_EI/4?K7_ԅtxVd 4iٍo9tIv $˸X/wgak7D#isiiC-W< uixۤPٛSniԖܨ I'Y3 ؙ ^s d8 I]^-P[ 0@rr ǀPqs 9 Sp ȠI?ępo |3 NOi7)H p' s$KYp Y9O"v7 &J)jpM` J9CiIMq j  b9ǀh@ 6p!pq _" `i !d&ORy P pƏ@/\` =8ph a*A Cp4;8h'.0fa-v e7th` @C7_ q0P`oagGt +y*;`?dCxDGK 3`ӣ8hI9ZI-j֘~c64g0@G75o2e_up0hd;[{۱08؂y+k!m,#  $hr4C?4³>۳@%'"ac. 'r*BN4Eó2Q+Q[dU[ZK\R V+[f;g[hj۶^o˶q?M_x_Ұua<w0Bf+԰(۴;[4<Y <} !ˣ Kӥ0kۻz!"Bk@g0<tܻ"<+L/63蛾껾۾I31U`hГOi9`upmB"PHFlb*Ez  0uC%Y.dPDp ހ .#=3ô!5[1/%.+ oL[1Bӷk5@fkq;& #.u(s(#5#/x'\5RO]cL_5M2M4zB]!E]%"./ $\'0k_X(c(22[-CW\R #ŔL6Wr1rܲ<+}K%)62`SB/'.Ns1rK)D 6stiYBY$B@| )%0h] Y; ˰{S-ya"|@}bC70",Y1)l2,C3RsЯ\Ã-2/(08Jo5@c9C9RRC ox c8]P%"PS>5OA\\ GC@er@]s46L@pYQK#XO^]6@Q@d0zBrjbr7[ѪdL 7fc8dѱ1n ӏD cD)odPuC &~ t ~V"T(9V4u7Wc5^H{ELD-Z9jL X|4YM= 8MvY~?旇Jr=HMA[ Xt vP P 6*zu> tOO_ P X HMHws>>vA8>;TyS BZ9OaQ@ M 1,O4<<.V怇0 :pN) d2Y-D)0/M6"6j.*0f@ps;MH7"t  ?j7I$&~ h;,9`@j̢FwhV# ٍS4F-7N{ v}0a  =A.;HoM > X0A@`*6头GHWVu=6;[}>`뎠0 lynU]M9RYXE7jDMa]p905;Nvޥ=uu}X@cX?TiXdǓ琢o|Sb:_X@7@P,2ŀfu{wf&9np0  2` Đ8mؿgPB >QbBDKƍV ŸD-] q`1Kj',KOB)ĄMB3N 2l C L`5a0eT1jԘQqdIQ˸h*]XpӴRU@BYB*ȈP!L~YvVPB, k(lB$*0H,h^䘌\49An\"\%Mf.f.3 E^8/AYGE^9蔾!hyB̐ Lv9%:"Ȑ1NPbBzbA$>qf!b3PdYrhHH3;E.;q\ʺS0't76E8@ RZJ1$L  XȆh@L n\<SN?σ2#qX&,`bР -APU=H@>ShCÌ.C  L44sEnr\6׹υnt;]V׺=А CvVX9Suջ^׽o|+`PW*G\vp#Ɓ Xh@@€-L W81Z>uC 0Ѓ1Az0c  "h {#1 nLa!g0:H"rE( Te,OPֲuQjUԘ qMTQP /6\́W+޹~vv iK֦mmw;۞}l`5;R<:Q3t! fD, q. p]b(np '#p7x!nq\qԣш{&c^B Zi;Dz3A| 28mq"? `z3V8peq8]$.U$qO:!.v}f׻v %dWɂ .X5b)$ Zv〉*m`G~+g^|Cуa iLCy4  i,cU0p4$8q=q]| <3$0]P?z>?zc迿gF1 KTD}4DLhBvub4t (k*N.>+=\@iy@@@@ <=oÆS&0)` Ph 0E0wp`| 0oċj8ws9:s D@ГCB!#cs#B]YpH"e;23d2KCzc7\7$C3DC8l:C84C7=l;3WIC/zC30G5 249+5 s5$E܅b6\C3Y EBbWWHeE7CNC>DH3T]C: D@84ESEWTTŹ<W o8AB$0A, @BFqFp4nBts ]@ uȃB7$IxIxtɗ OCo PSvQqҗe[y `8jY;jqqdip-.H9*H*QaXPE.)!n(ЄYP"?ad[`=1D^'F/0Ci#l-VY9~FT7(Jû4am^PW( =eaC0#k bNd.ȘAL\?QTqFTHIWP((`K0Uc 6,e8KBɗ*dDR+Tx=}!3Y3MFMs^`ƄГrJ`ڄd(&ڱepNq 1a?h2ÍdpA(kk1ѨIE !؏nU p0Kq ~']H۶<\kѨq` =fYp4>5ܜ`FS8a„[`Q#1jv L(aP^(n]{7??$q܏eh˲PYI8 ݏ JA%f%fJN[H`?9XhH1ZvߩZM<0G`h"`A.I M*^@l.8V|urF\` (p|0XYzT[Fۂ #y MoP2Ih=k<؆qX8q q1Q/ &OѸ>`'_:wN}X:X6NX6m؆knVh0'+ '$ Fpy s!쀢P]d^pzl+ 7ev@)`!¡['8ۅqf\^b %3P ps8`vpBy[99rՇ'' fQ)ٔ\91 G\WP]?m\ޗ h4twW%O ؅^0zKW D6YW AY,h „ 8&ʕEdt`bD+E?f,i$ʁԕk=G̜oϠ>uwetR2=&OB: 7M@r-!Ĉ&Q̸&+ܸqC;{w:-ݓp='n1ׯ%Rhw ƐG.;unZzJN3Tpر/fqyݍ::iڵlŌ*Efw%C .qOH,8ORC4D\deMȰ Aؔ 0Вu6Ş(cITV|uVH#xI8E[D4@1/HcDsb?i!"$Ѵv n%dPU)oEDQY `GaTgVy1gc!SI #4Ȑ`I#0aMm7N/` ~ȡ |Y2BEku3i㩱 I D. 4L #M ."Kʟ/pAyt<lDg޲'ڢD#AZrCg8RwJ6H#Ҡ ,b 2R%IeTٰ >ȠP3d'dpIiJbojjC@hb +ؠ ,O|%BpiP(ZU}t[*K2#nb@4»riYy8Q?7A]OJ7mH 8l?Q.G2 A|C"A sAMPi0x8"B4㼠[մmj|껰=P\HOݙ,8LD?5t;5@b7"MYSXc5PrLbcb9^M#bM39۾)!k|b|qEY솣 ~ `rp? ,#[_[4܁6$M80ÒJ (#H Gs\$0#cn()'O#o"]F4ҒS.TQn< %0&@>t!8rW(AN;N!SB{~hvrb#!3ެn#, )rFt'd 5)0 8  =a&*73p#g72LOP :j &C(8ѠC"!ۉ|:x3Px $]D?}?/8Y0g t xG,~chNz-tC@q ˰ Dž֙t)OK$XND&i,$[h (uW|$=i E5(R\b,]74XF1:f# M`n%.ziI,(?Y.,XxuSc삠fP{qbQJt1*ٻn,VHe('x',a#^@uNXF f}p)qgu iPH0Cds88b5VrB(.4\]*YDME!ЅBa/,r&r>p @MImg74𪺹n2d8Blɻ$nE1*xU8Ccq BH4{ sqCFѸ/(T)edx@2;\#a$k]C\f2BRTJ b&qF⠕+ ?&,y9f=MCH(h=\20 4 <Ё"@:O`C(b*!pL0BG$Jd8XB+$d,ə( +sYM@LCa\.0B 3a5Oݠ7MwC4]Fnd=F1PBȁ'Ya%ɕ\2"QM%A å_u pAhBE NCTA(dW1"@AA8|&n 7'r]dM߄}vQ)@H% lV6 4A8$ f30.,C1']N[,&C1N3,FN,V^zSzbMz^+aݐ,3)͇T`4L", *Ȭm")B[4@AB ց"[Vrmmצb͢٪aڲٺmܾ-ݶm- n-&.ݦ[ۦ-.n2ڬͺ^-f嚁F+x&\.SӼɓ˹.Ʈ.֮ޮӻ<.h$$/s.1SKo*o1n &o/N/.$,40}5`7`봖'0/70?G0OM7P7H!½1D40ʥ7ѵr%Byё\~ 001p3 ,. 3ʂ)5,B&N62,B{q1qӱq۱q5ZE7H&*"^B(B#4<)d%qʊl80 01X+ݜˆB1,q,ϱr-2.Ӳ-2q1dE>B=1CB 0Y/pFql22)*1CA[U*Mw3Cwj939D:8;3޷}׿>~쿾Mڜܜyj hy '~[쯪LU>F_%~Mk{=f-X,Fr>>~W?@N`q TX`Å:0ܷqѼ=UtF [4EBlhe`ƛ7 " x.DB E J-.T0aUO. eՈJe%*P޲Ʃq0,"zې?gP?5Ll' Ǣz@!zo~hq`"FrL!&µ !Yz0/򂅴1(\Q`9ɂD$5 ABb38>D7ㅹ`Hr42Em, jΓ#:y8LA  XPzZMKOJL/DxKQ#pe dD H z X8_a)S0m_ ה0K(ӧ10F|0=K)ϦJx&Pi KKPPB40?[n K`-W>CpBDKϞlf2R7Jsx`k(! x Ni < d!>ax,HYԠKjhK.0bedv8P^B#lko*>&) !W|5,3kbdgcJ]+Ϝ<>ZTa"9]yudz{:B&K*hdx86_0 Z^mGAI8hA Z8jO<^8!H7Ofm#_ߘ6: a af<} 7!$)8b)v>o,C(i\( qoIwIj<`$OǙmN"F0-M`.sP('H&t= ~sAfSG'41Ԅ(b&xR&I$c:SwyiE$u^mgdP9S(Bx ,MFeued? u􏓰.P 4YL DA#aDnjnic,n58ÇX''ؽlOAu1wr;W,4d]PVhGJOvmʷME3Uwݮ>ݟhwiߡ4w7Oc x>!_K80ج*=6^1+anLO{%Sb>~7-$P(죯k_跋&JU u F8ġ`-<6!QTF=`@HnU}08 <"LCϧaRP !HЏĒ/N|xA` \00 @ pa! .<ʠ Fĭ Շ`@H_l8dn! 0 5 M HDNm@Gq 6n<ި ..13qP pfirK0z){ # OAᾆ A1s qlJ91 w։`.͐P ! 2HbRM3gg6J8CE v4cN8I\PON=HFI9w,Ta VQNӷՈ a{ABFMOAN\ qix4mTAKEޭTӐ4#,Ե4 fN`Z؄;l bԒ@nd )4/t?F,J,MMpԩ^,g |RА}m^ LZ"NXd~n,\(qK^B( -sal!rbo @nHa`02OW1dCdT(1ځaag!|B0e1 j!qagjj}jkܡd*V9Ъ:,V̡kaA6kȖS<-IAp%jv8jnkalmIgkAt/6V m IFvm6HLiA1yrynK(@nU7i7kV&ɵAʈkAuZ \np_7:(I PhKY aJFiT}tQ$dlbIl&q5 hM* LKYj|ZT.`.Rga܀\L|j!~2 \ $0+0-"8|~ꣲ&9~iLzG|Ϟk2a5,}Nϧ (|kp1>? |Rj_SnN:X}ms.濎!WL]"hx]nn~{g!*~!>G i|22~$=G!s5R%KYtE&޽{qƬ(ӉO@d:-E= 7&*โ  8$׬8JF#hcH# @YLȈg'3@YN9d㕥*9(.\<|AJy[6 k)>rǏ"݊enL`d\#"B4RD3%%C6 I4bNUAQZgl@Z$NAYh~$#ES̀ap=.w&r TJ!(gd1`Bqa2TA7hE d Ë1 G&ddIQS$/Gb ," ӄ44q .b%E%OR@gh&0c2ƹAz(tgA(s¶FcLĆKh( 2"+dѨÊ:P.>przlWWbi M7(p:Kb%2 , .P oi3bp&̠ jP35g'Hl` 5$x)* #1e K .X2&6nj \-aԂ$`'<ÁYͫ+IB on/I \(4.F#1ǜq1 {t7M GVܡy1SywbV;og| QD4Q c b(DiHc4Pb&Cs0-ń`Z~iOW~nVKP1-SkեȦ!5]CҰR*_ԻiKռD 1Kj+~O*Vֽ*vM%^nzHlK@0eg?$C xJ&0A .D 8(^Ts8a׬LIgq+vqNJ@XLuޣny vDA, MT;5rY 5Iz'?B  λ ? t&1qOx* oܱPĄrG0" .(<7rej5yք &cE@>b""/tE<3`^;xEbx@,pV>.X)4anh"j޺enxڭXw9尥X :znHfujWT[8Fl ?o87d!1fW#e F>۔Nmk؆ZBUѣ?x]q0Pps o /Q-l[nڏ B÷Ww]PtW?4v1D{BrW,x˖8Pp~b*kL"EUgl' ~75g6 1r/qhP ip2-Mw TbgPE`SPUF@u%*zK`@G@f@7${2Ӑ UP QpS eh` \ ZX `EecPPC.x"ezC!e@#xI8  ]r [p Qr0 ^@ EB: 2QmuxЁS p:P0S1gMdb рѐiJP}9$0d=p՘ ( ]@s&@ՈM@0 PP <x p @i% 2tpH苲 8 ``5lČV]HGҀ8~ؘٸrC0 "ɠ c 83 sPM rt 9|ii=MpN9‘^V )٨ɀ 'yg3,@( 2Ir TrEBKh'YL8O Qy ("Ѱ |$im%9 Q& hS@ Hu EI*P`f9 PuIF9prփq)gPYC5Ź  e}pLz1 z绎cV[i+⑹uH &( ʋ7%bi; ||y !yH}N:r+2+\ Pz9fj|jrəTMy*\KeNuV{rX\YnFŷY |FVkCL Y j\">i̿Tz7VN;:hPpwa=ӧ@e,Hڋ&;P e 00 }3hh '5 <ѾXߵ5 u)Ep 2؅Urٷ R } P ZP YP)$r 0 pM,<üDJ Y:Ӏ  ^0SpͿv1 ȐX ]0]0P] I0 s0P1S_ЈEُp^#MTGƼ=s`s0PSFX (XF؎U0 Gk+9 PM0}u(0Շ Px MP~ȔO\UӠʁ٣r ] V1 c J|U8 T#MQ? `sTP 8 ~'pא _ E!QM̻=1 @3YK 7`Շ Ǡ:`F)eYsz{d F]eLL 7\ J(97!&9>Z,$h훂q)p[<f)Z7~hE6,&6ɝ ZO zJ*#(>@$p!p4>тKh8:y " 0=.`" "lسĀ*~$# Oq n~{Qټ K xO, N #JJ΢!eQ;5iJ 1 U|#p# \/G 9#!$\j@Xn` M fp ꩸UЏ-nr-fw|fcMM`5B@ 7Qe:.* e!13 >a [ R~NFLcg: p$0qa 0jbf~Ұe gP Kh-׾i$ЪX?LAUKB*?O $p;@FqOR N:UlJMPՃ P g}<; 0U9v W1x `{qEe=fwHHT!W LT[|J45/0. /`C :aO@? &"n^ DL`7(4 ՀmY=y_4.1/ܠS pa6ɸ!A$YI)I艢q1A )J9u$牷 #Js̕yw>j1~&fpO⍇|?y}}巿~?_X .| L1zc{AU` * P#$ax'T pjPE+` EXC4diC<WXDQG$bhD(&ObxE'NQA"*QZ8qo^jdhF7pc(l ՘,?BBF8T8`GG;ұWS]a{_=IPR$e)MYNz2㍀;reportlab-3.3.0/docs/images/lj8100.jpg0000644000175000017500000003025712120332300016647 0ustar rptlabrptlabJFIFHH Photoshop 3.08BIMHH8BIM x8BIM8BIM 8BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM@@8BIM8BIM @pHP^ $JFIFHH&File written by Adobe Photoshop 5.0Adobed            Hp"?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?U{~cU6i"@, ˀ A*W5{^{uR_mݿlTpr\,pzvыUvS]uF-c]{N9__M[Ձx7MUsqok\s\[@ Wܟs|uzWRdG.Y߁?87cm&gYVۼ2sHuOځ4\}v‹7+8̆2p#E7 Wְcݻvxi *7x<"We*k#Ȭ <|OJ9Owb86]sYF Klۿ?Eӓ̎c1`7WVlu)ظg,{L9TL@ `w"e㲊WXV?nOJ3״|dd}ЦRֿkt贏C]UޙffrǗ9 .s|-ed!qr\?σŖf'-??Ѓu[)mn}IL O#A'ܭ5cEmKlkeگ^@ǾN^tog\w=os=rp,1jn\C뗫"\9>&S\ >zŝ7j~m,. GMo[?XXnk[nFe>skqX[Ѫ}xκbM sDsms=M=5!׬tv?ZQ;V{>-=A Oc]ymzgN{bF,H@(SuGe5mӷkmr,;^̌չ+{} S&(}2hs"|Nlbcq#ᢥ'dZ׹H}۾V%:bw.'kkKcsrcq%'CrD{i&0|1Mȯ&_d[<c;P,ZXs%8zN}*/6V*Y]Q\w7os< f%(iaഇ>JBecXegUk"&lN#t\xv_UWGkgKcl-\I~qYCH %;|Gˌ@~qg-i4h"9C.4;v$S<~95_Z]cZ w-OXa Kg,ĵC/imvkc uy?f7a4[\vϧ5kw}{L|>{g⣑5a5lb* va5ձʹdSvѠmTnکf9nx9P9϶ =k-tm I$K"r#7wh+C~X< [hn ~NHk$֎I#+C Nk H:uޑomh};G9Nek;XV>CS0܏_O vw(r5{6OuZڝg)+anq\U[n/.IMEgBe^R0̋~/=I:7|ioNO ZXuU &c\V$çA{F^:>eiTyIfnyH I\W8BIM XICC_PROFILE HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km&File written by Adobe Photoshop 5.0Adobed              "   s!1AQa"q2B#R3b$r%C4Scs5D'6Tdt& EFVU(eufv7GWgw8HXhx)9IYiy*:JZjzm!1AQa"q2#BRbr3$4CS%cs5DT &6E'dtU7()󄔤euFVfvGWgw8HXhx9IYiy*:JZjz ?X6H"oLz 4I%>}$zL6rw]BGkAb$q)$oGs'T42Jp#^Azo9#>1I6Aq-9vhU^;juEèizpjiK!-?VV{4e¨3ᓏPDJ w@żn+iȞ̙?"9x;Oqd2VCt[\p[][v$ ͺa^^oYccm6MGOhT1ˇgˬS1۩AY[נV`WrY1d; . mN. tGX~#HIJ1V Iph#=*թ9ϵL^}^ x8wE?ucl &~#&8Lj#>%64X$Ь Ο:bzwc"5ܾOsTzN}د,1ʼnN҆5Hy>φ>CqIxj>'ijd&q<''S6l|g|17JK 2|rAxf#FѶkJaݷl#nsjզ^4 i {oV"":bMfbSrA bʖf4%]Gx_ݿkb@nINq/=Z[ʲ>UZG22X,/JW" A0d^U:(mdp @a|K+9"ѣjδFYxZrn>)Ny l.$B^I X;{A4)5jV֐tvz~c=톜EPBE6E9!Tk ĔiBc5>Col?͔?uTk}Q⪫r8cm)$OeV9~l6Wh JῑY?ݲB)k|'bdLDՔv'E}@B5ސ͂f1Ӎ7=l"J )r|{a+ @vh:SvY>/0,b@1:8wm7|~JOKgP8;JK1T e?Fn-D-p ~TˡP~.TZ\Kk1 ,,Q} \i/>O3KƽUK37PBs;A#1ҁpp'jwrB"!@FZt8^"Svm"@uȐzݹpV#$>a؆>2"7IpA#emjҾdL`'?KAjo78Txፖ/[bK֟ͅ?*3)A^' ʧt{oS~i4P=ŏM+>lQot)@ nT'7դ{Cib}M~JAoWͳ/[8V(rRi:y&#tQt ?\LFIVk>zmOMj#?&/n~Beɴ3J]"乺ǫ{xD/=Jsz$R( sΡu<2k^$Zf//y"{)=* 3}D<7PAߎ\M`ޗ`1 3gn]6Ëh`n9EA*6 [c]m€ =2*}K%qNWb̸3Fl(8.75޸$%dTjR+|[̠Մ7~3 h=D58oo- '6Q0@I'`C]>F?XKYIzE{T xL\z-UkJu%q^!ؙT=>E?|35 @ B7 ^q"mq7n[C$E[[[Y,Ik֜(JׯO[h^ǫLERqG41a䱙s^]cM IbNΑI _/'-B;N@nİ"m`=##[ /K[aB]6rߐs~bԘF đv(7Z&#Fp M#`)\ce>b@ԻWMxrskf/\CM!C3t#/wq_L}1|1ypV.eCCKW6\PB`#A@bwJ [*T؊e.ZB?`dy2+-Ub*I")插'6Q$f6&Euhڤn`+N&6߈,:} C2S^2*NZR:R?)wW@`Hu4s"qf<沮-٫bPM|}Wi2*dHAgkwor.-h~@k`I$J0֪4F `4Vy]E+ (|g6#{x`.%j)ǕPOVe!5 n4N ܁ ?-X<9,~y$789BjƖ~+@?}icw930:8go@0e+_PFFle0)KL6"pldٲ _'#=՞/p`*7d#.E{%!{dDZS}#-Y c AW]ΪWE(e-?aivo' !Rȍau7Uԃ\8qIHl@ja|m# dM"̛u%4U--'$'3Wlqp͕2͞m͛syɁPv5fɫa-g3bM<y6J,e8\'\AfCC#pB̀l^3dK Flٰ gͅ^׍=sfʼn{98lPreportlab-3.3.0/docs/images/Python_21.gif0000644000175000017500000006671212120332300017506 0ustar rptlabrptlabGIF87aGv!)!!))J!B1!c!k!{!!!)!!!!B!))!)J)Z))R))c)kB11111k111!11)B11)19J19k1{R999!9)!9)19)Z9)k91{99199J999ZJ9sRBB!{B)sB1B9BB9cBB{BBBJcBJ{BZRBcRB{ZJJ!ZJ1JB1JB{JBJJBJJRJJJJJJJRR)R19R9R9RJ1RJcRRRZRRZkRccRskZRkZRZRZRZRZZZZcc1c9cBcRcRcRccccccccccckBkBkJkZkZRkZkkckkkkk{kskssJksJsZ1sZJsZsccskZsksksksscssss{s{B{R!{R{R{Zc{cJ{ck{kJ{kc{sk{s{{s{s{s{{k{{{{{{{s{{ք焄ƥksJsksƌsss{R{sƔ֔ޜΜޜΥޥ1sΥ޵J֭cε޵ֵֵ絵޽RƜƭƭƵƽΥsνֵ֭sֵֽcs!,Gv@ZWlڮuĦEK0ffFŋ3jȱǏNSm$:h6X(MSˑ͎Ț"y,LbҶIs)m״rNGJU#IڼP z-٬u drߺPQڥ޽RKo~Ífx2€)KW+]pJ?wC"`$5 Grv9 @W~˶./wc 0-_9i#ͧG=lr^P%WDCH$Q`9Pp4(8 gsSUrj"p"rX"'V4x=%b0C9*x"^>G΋$M%:48"D^^)ָA:i<>)eJڈ%\~h!DNcW4$6f#jÏ]g>rS>oCnxgY9(?}*碍*꧛wɧHӏ#cg3!]l:E%4xJi&J]f$*l -#g^L#^D&\?㏂bgD?عWg衁"g}yyΫn"-Hp9 Zkaع'ɰYhx~iFr;P𛛺Nʒ:n{/pl罄ț[ÏVLi!-:]L(S>t + r#tmx|1lH&Ґ`m7?R?H" R78>Ls?ηHX?fSDn<[Ɩlxo:|S駫Z% fק=;#./Nw{_#1es?;]QgTkЙ9 !f (v6FqEHИu@]Z688 RKhG&ve%6LH1?AaacXĸ3HE (ļAp{ 9>wlCC_IB[8 ^,ha'DZqD.%X ‚d1$@re!wAX rY8tJ,[bd, Is0C7<|;WVr 4HPGwRH 3tXjAOԜ枧yF/v~HSpt|gIJSZ7=(BONWX}S`C#Wo@pF81pҏ0+v>r2mѢ/ d&T-HRTEULiU7JkquP qf?@UnMG @<[jdW uOqe Cg?*vs&Kud!((]8nD, ei%;F6pmC{7u(E%ĎhP@feyiNG2QE`PE&ju-c;uxuiJ:glbXoW Sȸ]b_PTO&w4.nI8jR;uS]]Wo +ZRnܦ2~* EQc6?Ab;}f^n9U" !;B>"x!D9B/cDvg(lVlKt|cظVБo#zu+xyX6qFV[6 bCcrnb4PщTq(@*Ra ks@BT߷N2Up:=qs@06 ]7*%x Kx(-Ag#]✠EvpD퀁(,0LOlc$=;`ߠ7nQ qF,=[p#pS:H-C$UL20;mR34e\b %o4cstƹuHjK<n- 9Lrya{ 9rpGL9T Z懃Zk$9W Fq@>xUzwFC^xj [[@ @ i Pd i@@TXOaO Qd`BrD  İppCLru&Mg-R' hF)>H=` ޣ h0V `d0_` uR M%@WvC&q@ àxRE iWhv(  8y 8DT@!(w7d"&E#C#rNM:Pmy?H(`)yq+ze;@PxؕzMdٸ6x~Ń+8_}2Pv Te9@ T9)e iP~ ax88 hP iπ2qT|ُ } vibIs [p p `T9DǍ[ :` ȸ >ri^97 iR>`t8P `Ǩ x^c9 ΐȝ <@ПzI f ""Ж%9p t0 3 :#0 ]4'ZR u &eT +^RtӤ ݠ1@f 8` t@ ^3JA HW`ÕrFr  s 0:@dې pC\'  ]p!sG@++j;d?99aH_Ys`Ur&2]6~p,1#68L/K15/1d|b/Q' j'K-(E. p ~j^+~0 jES-@n;עr,us(f` 0}L-9uH ,$Eý(26/;1,[(]$BƎ0NSG' `y}ь^ăH`=`Z`Z@%jV#`W$Sn^fPHgz_AY \ ᣍEw?:0S@0c`YPKsit ^0]:Ypvrmfmpqwz3'<'ay~F`m5Ubw; ~6j`ΔtwF\8a'D9 A$pp98I *$OvA9|,[$cx&`a g ^DBrcF}j8 ؐ7MF_ӏyKә3PZJmA` t 9*{Au& ĩ p ` ځ}4t,ِ%??w;thc iʈOIR3ٜJ Z>@Cq5C #ghh > uPgWS%>POOH /}7N6p  @q6'&/.7EvD"@1M@d`p0d=D$ D澔#0@6,nI4ӏs4PDf-R8ԍv ":F0 AzqGm~M6e@ y爞H4ݘx}CZ[XՑ.Cf*nt=iGشY9H980!` @%  Xuw趌[vm ٔ@\ H sP 7p~<άa\Li \}G8Hq|,$9Y=(HxOE] qf;@x<7TbDu8RxgP2(Fc~X": 8e.# c+_[Q#p` x &,IPcrL0գe7( ՏNAT䰋~X)^E/ԁF]㏨A7,[b2B$ OQK[Ჹc P?Rb0sPcTt']LL& 0?*4TC 1@bAS!EGTBց3HLnʄ>9\)N92 (1HY6~ @(G)AMCX*(-6ԡhD%:QVԢhF5QRtzrh4v7R}=1 e(cbd,0iLe:SԦ7h? Z&HS2<N;F>y̔BZVիeYWb'@Np B^5`J%JVVWT!$%.[jg=ZEmj={Zі6uhe תֵm?[ע-m[ղM-fI e`90F|N=TO⇞J-񖗼ozͫ^׽/{^v=t~hWI ۧB?I}2)J𒷼p kE{ؼepl㵯Iíg9 W'X9Ѱf4EF h(Lv2&OYK2e*oVB,h4 ʝnޔ<'kI&5,Zɮ>Kj4aC>*W9L \G4P`5uH).(`2GH %/STng{o[w-uٞ2;Q vc`+lаvC;x>: "=TI5 [DH@^PSA$dt>vuf7Սr%x9*T<$I:8Tb]:zCX^*nw<{zֳnV׽wC.NA&la : fb6?^;~c;/F4m+VyyO6]E:}q5 m5rx}9\]~=i/ֿ>>Gs/|~ĐeMofu}^Z5A ɻ1OY?ېiziLп6nO'C>l>+?;@i?5eci ɇ9H< úúlA\D{1}A SA9l$R!yA " ۻ0@:*D%4k”J1A+[63TA6LA9lAB<%|C2;T' zikB.$/3B.DId:4- 0>Ot/qʒ~=)ȇ^(h@h iHDih|bu}ѹ]E xX@xr //Їh` z1H|r@L‘/G0kc:$DyKIE{DHDI$ ۰}<1| $0DB{D< 3͘!׬۱К5R )c>Yv9Qۭŋw])U@K -\'`[*0SP Yw f [ }}@xw@dA@w5ÚxZlZXia 4@^-^/vHZPmW\y\WUH0 (dXL GmCYƠ7D]M4KvEZ8^' }͇#]=at`\zvzFJ WZ ߯&Gt^L:Q[Zqiq% JH&EޛU^vظljƉ~d 14? $$:^7-N$09>S@50~@ M$2MJ(MՈp*   =%[흈p)ػr k੅hEwuh0" U9$ .x ؃0*Fm a*^bhT\S"MδiVfE7l x䶕E(VDf: BwPFeYÎ %Ygh@ йX$@UF G \d옆@l@1@% iCFfp)c<uP6&uVzp$0O`㢖jM\$%чTBΌN^ ([k궶3mp䨆k?b# 6f" {(xG5o&k ^deq޻kNxpI(F.\&yi3NDPE]E e&LfP$Hlx(zr^Th ʒ0';ZS} /@m^hcE)ȃlz|9]Z%'Yܻԡ[t22rk] Oy0uQ^Tr菊`MP &5эk≈]؅FRQ#rVVö"[ۈ5 m ]hb3r!U T>fr@c =1 XkaVh Vp] @m_ȪNxc^#~qq}9=m@Г<ۉXVPF}أ*t 9sfVvU_U%A{ d rK?\U<XPʁcP2VimĈؙ vZhcapv>䅘ו> kW }UnV ZiY"=wZMQ_9VoՆr&되Z^U ;T菂xwXYWʽVG4 i{g m(Zph x`Dtxuh+plP'ѦihXƘllz؀ƘkeToeu+<pov/pui5=VP %6bO%mm(؈n^Sov((rWj@zzpƿf8u"pZ/j@Hug)vvg HJzꨆXxp``CqV@(x"PTZj|Fm@639"ƌ7r#Ȑ";Xrg:رbnYN7v=J=*ȠBz, !5>lpA 66E.DY(ذ!u|x AN N@Dw1~kغu/JTfRA[Ŭj#8$5lh{mȟA~D4r^Mg.q"Kb?Cߺ@(.H!)$wBOi ?$:ĠWk}YB Q \&!ep3#S?S?9@?oxK*RI0dRFiDPJrJ"9$odUX ZTBHVnrYde$FHpgr%ڒOif\tFlXeׇ~*(֚?#}2 Y\P`"::*T)4Їj]ϞZh㥧Z*?| 8DA |5:eeb:ja-ډ,`I3:讳Cb im*/F^4XO>9꬙V˥;vHѮJmĺ'htc*$@Ir_϶>GɌ|c!9iH* J?|8\ 8Ep J `Ԛ7#@:hr,F${7Edx! bwIuBQ":cxFpxFƠQr,&Y?p! 'I=G8NhJ3 h5`Q:&s.S"g̐* _|B#CINaJ94/GZLv Ŵh/@e) HE4A ncc4 UO݀zFg +ᐣ "8P"şE 4 WBưCAKġᴨ1zLtpǸBٛ;Ab8: B <3!44nڰ/TBiUgr`Ld@ 89BH$@G;$] KLQщ ь)z B_hB P9A棠I Ka0/Z02[ !y'5ԕaiPYaƖ#A1|Z&@+c%{L*i\>qF)b/Cq)LeJSn7"ةXOc X " = ".E?:CG9$ar?Ьn] ʄQhl)bh4 ;dpgSkd̴QN&]Y %8B_B>P1sP{wISpLghlC9 w= [WϞ4A:u첣-1w|1fk}I:uCF)z0[=Ze(5'%f1%LA3~)?䢂0&aW4XA%P q/.! N;z'ArDj LRⳊڋ3zIGp h")AItqG:N; 9-'uo|si$rJ" T]^yv—qIy@lo)7@|tSÙ/15х.0R '^ _M0!M;NOfsSS /{j0p>s}%L`H&18.LbFȀk^=1q ΕtQAF#]I'ߨfa60b@dBϼIA l4d` su0pL K04ѰE\D;AʁAr=& xA3H@pth?Å!eZI|)AĂA }-I ́qW8^@HkmWC)$܂l tE||-l&@tqVi F$?* L>٣E#d}uF{Pv8>TH,Õ,?C)@ VѮmT!͘HC9C46XxCP|cԈC\{E?B44G?_FH@i$AxMA{}?@?x>HCzA>H^)H.F>\IH@S  H-J).?C6  ?@| |Qi[f NAڬ%>̝|-!gW4GLjG6*GCC G1@z$NI:X?I]|hF4D49a9$1A?C&PCQ%R&R.%S6S>%TFTN%UBppU?\9tL)m4H4lG4P@KU%\ƥ\%]֥]V"N$|xg4Wa>Ѝ^ t̺H@WLC `cVޥdN&eVe^&Tj[&:&Ce>tp(.L9z:8%&b&nn&oo&pp'qq'r'\"*s\i *&ݜr^=3Nv:X#<#4#dBy>Bdg&8x#|g|'|:%dL&c $$8} dBd># `is*++&&;;X$DP~:VFN~+^^kwa>ꮺ+k++kҫj*az+ 4ZҸ$Aj 4p4L4lj,vzlƂȊlnlɢɆʒʦ,lV4Q :;3B)$t%jNNtkts|2@Ǯ4LIdžm~,َlٚl˞mښ-ۢھb6U`zTxetHClNe)ln. x4fUBj[db.iOp?x.^fnnvBned>%4Q9YCGP|D@ NH K@&~ <C<9墮~. ~fe/oo,NtV+!%&C4;nL'ź$_> B9,xXf+.gm+n^m"fgn0k0oK9`k:13 C@óKbU` @ꐇ4aC"+^L#ho0 /qsqcScÉ}`:H?́#S[D\Iuiۺ B$2zP"+#7"?2z8r$C2%&G$o%[&_">~BAmPa"k3!@N1o9:C^@4ҍC&k2"/2"[2(G4g'+532'W's572%4'@T6oT< HxxA?C@֒+34A3C!A/4C7C?4DGA+B#2.) (-?T ?óHGӞ>ܪ`MDwBCS4PtBOQC4fBt{%Pc)*4D=ަ +4YjXԎ5Z5[\[+Z:C/$@ @B>kpovv+bggZ渮4+\ 66zJ5[_ekZugW6n5oÉ1D:$l˶m6mӶl#nn6o7pvpqn7q'lWorvrrw s׶l@$At7v6tO<7Tmvp߶sw{wx߷p7u6rpu3w8|pw|?v:bannqFFpl pdFp8SfFMVJ4? c/fHHdOkx;d.{`>`>Pxf8gs˹ǸMGpxK=ǝgeMCNJ+a6Gyy3zk-s9c:59_si^3 4B Έ?:yygeUyCzoesxC9 i:pH.z-̿LCt. :HV;o?J(:xIa8K͗p$A|`\:zy9.Ǹyaj!1 6fE<|r-9{;a<<|p;>8ѯ&Ā^o9TDs?[ z$? |ԼL% mu1HtF*,?DH,v|;<3}-{<@󗏟>"[K s("| 14hp`Gv\Q@/^Iɖ(,9ӤK%WdiպPerwQ|E7QFwR~2KR&Arʙqo$*TSMFs@`u_&29_TwBoے~5vbI'1g,c攞5slGf^Y%uF@48) k_b~s̖/87snD":Y'SFڈ1w21r)u< DG EDJEADJDSD]<?FmqEuDr"\g]DRԑQ,sLEGڅ9r1wQ,Wf]VZT̊f*IP;Sig"M1iס(޸ w*Aŋx_.\ؕCC}Z}֑ƭ+`Td~oք|$m9 ci4ق9P T!TZGjT \7Ո#8g69@!r& p"GKv1ÌPW|)SA)C* Qd}H5r#Ճo -ba.}ov`ȶ bBjH[v1|aExC#Qz0XG)Tּ$TU:-8 oMToD@;FgEU;oD 4r EϊvgTߖVƞ!Y.vq /LU"c5Glf^F?";̛f !9)*RS26*uvi"05ʘ,LFOz{$"\ƒRy)5+t<%QSrT(SZBe$^◓ fYLX slf1 g %fɴj.ot4vٕ:q(`GR tMd*GT,T%SG9HPrHr8P(QFR 4,LF7`)_jپ)ZZGK 9*֑y#m^ja Txr` 2W "6F~+G@ãXS@qG󱾋d *WH7GШg/Zu$cu!Ieb% G&B8$"1$QĽ0#fY&c+ XmT!e:X~)@E4$(*u8֏Uwɱ!60:4c68f 9sFCMhSBޣcTD|Y_~U1ӿt+<Jw?հxvxFЀZbDpsVvlk68݈; w@F?U67 ޏ<x%dMPpSNpw@h: ƠYD{3G;jv[ ~V|h]ƅn/*- ICP\é8/ׂ:A2oiډۇmf]Q A?|$ H .W&H<:0;ĉg+ʺ)yGJ$bK)?&Ӽ98ga& |Hy\FhBy80nq /rAovx1 :Pַͳ4a2 i_OM-Q$A}(bq `m)T jn d @!(2$_v!f*J(!vR& Ot @va2:)ܚl)(,v0% z,k!AArDHZF |@.&nį܁0NpGE*4GGh:xϽ )TA.Eb1e}H nsǿpjSh "B̪SHT,S8 쇌 lNx/䠷v!zjV/밹b(`֢.g*/6(4e*k z J `*" aX+ |"X  WXK/WJ `@0!A NtM EL%C%KAΑ!Wn-פ*24jb`!pf2 p$G%()-i&KUf"G' b;*S mdti.@҂/l12"((0/xJ> ,E=6b敶c⟤t%PXӠB3:%v!.b3wJ'*'"¯ [,҈!F *3X,O{"A2p<ӟ,}.*Ai+>syR&> A4~ʳBG1Eэr&CAШa(DQ4Ϥ-O4EFC>H$Dˎ3/2tǾ r lnEoi),9@$P!,6 -z 75lDCܬL1>*/`*ʢ( .24rI`ܳB?0  bb0FtX',ēxab6Dz#;xŲ ܢ 6&2$ F+`q< 5C% V  A(DκI,``r(#-.vt @"dK㐕*7 A A@`UU_^-0z-hOa T*aJP;5QlV!Th \\5U oR"cgt$F Ԡ Ta ?sZ5Z#An_{l^hQh66(am6d/.ʨTMeV!JHVlh,lUA> `C3(!*7P@K *p7Uwtab>zὐuvlGyAaj6l>/rn~՚i)"/F%2ΑJsQl|Y)H1q$a.B2,o Lo؈"uyɔF՘ ngs[8N^ɏPnwY jES *J x:j!B(N22r`!RnGF,~*H"_2P/TE!` a-2r7T//e"Q%(fPFP2"jIuVr5L{,x8hq`pxK5 ahãJ@]E1A` q!H.)0Q Ī\"5zĘr ()U2"@z|ozȞuДy0b&00d!2e S&62*|Q,/% @6vab1 (%MBva + jSܖ’XɌÝPLj~wW5a 5) <6^~/a< Z6ڻ@M5Ī 2G7St.: /<<ZA"a}uԣK E a mZ"H.":ar3[=,;w;L>,PY JJL*:Ǟ_H/ /`:ȷ6c 0EB#"McV\ f J!:߅/F(Zk6j UZAF<d_AM   !hH PιP7 0zu% 6s $n.A։5RA\!X^_W[UmiEyR x!})2+W,ƁTX!T @} ͧ zB\)nOT" "xRAs{a!8 F`\!8` ,}\㝁T׿c@d0  ~ A9#(Bu|[ءJa&}˹tAz'kUl-`p5}Q  `` c z hߛ!CJ0eEɣaW/uB_)W`[f ` jClȢ@#$'_Ha% _?v :|1j FsMHĕ,[| 3$S[x\ 'BI IW|*u^*ڿu*IsOjiD爬9ECE_r Ye0GB3/?w7 X/ncȑ%?L9fam6l]>ú3if䔻ߺ* #qWمaU0Zx/z+tw0|'}(|EBL"ǐ=IrwpusfS?Sc ďa5]v_K@5aB!pRX!T5^dLn1J%ΨNO P9?O:tc4afP#MNI--^Fqrd@IjtPVjh)y%4>ey2P^fb'{,W2h>zޏBhқ249S{^Jjj>Hq⎞rbbR(}kȓ=Q<Bg i)'kNk4z*ݙ:czΡeZԮ!8B V?v)TkҺޗa笢A OaH;QR@C$.+.я y1xc:p9 mtfoFғMS6:$P\,.GMvHMvfD]% OC.,c(-Zv~?uMܔn21`WON<%q84Ukc媯ΐ?38hx+2Izji'!Q$ED7K?bo};ٺJe}\\=s=IUG?t<6\:om d.WTI-N;Oy-oa UNr -d1OōDnMgRCI0[C%??GQ L:Eav 9dB0C&#$/ҳ[2DQ5'&K"A:5TC>>! ȍR)4@xAz o. vq8xa.l!W '  2)e 1D썍e` H#qqS].=ŀC=00}L(K93~C_Lɍ̴T,<`(ˡ/8v_49P˹DHczQ bL.I&PgN>6Q~K{pLRK($j*PdR8v s$x('^tb8'sj-UBvj:" YvCkǛO$QG C&ы:ZQF0t0%, [X&0P`NwY#Ԭ䐄 tx>a 8$NKG̡j,pT@~R ؚrhxR7( 2 I & $MDnP8%UAC%>ێ#@ MBi(7H:>cH4ы%P0!>uI,D?W#"[E/xJ#NKBBo#A#p:!~LC>auOj֛e} ?#q-?1Ӹi$(q Jb_;NK7)@ShDd5!!Yp=|DËx*VM֬lF\NH)k\cM:ؕw۱φ1:1$=_F3zH(A HP!b&s4v!Ss邮ʹ2 tnbgEzk @LAvՏI1 .ԡS쏓W! о&Qh-9+S;1@x68nϰh aKAM G;>6 ::~ʸ1n [#hXMX#sR 1m1B3ja,a ;#L!GMo*h6JE7x/( 6s9( ?d>&g.Ox/8cKӁދ&p0CA~@"sρ @@,N 58x`>ID*,fP-z,87qSrY! MA p88b8G?A2*&^98?3̂@EN āsѫQ߹ߤSZI7 9$^ڟ{wrwtPQ>s02` u0 7px}>PTpr jUyG^F pSqS_p^`7zAve 9 b"VU@9Ucw9@(wMwXT0% FW _ >0RJЇ@S T%gz5j :K#V2>!W#9`U`Ad^<h(Ug SiC"'AM>`1 ]nR% LVZ`:PE+~Ѱa5zbOGI$ s0@CYR! 7 R_kbBhvWy0F}:PWш3zirg otQ5ԁ0FIJ 1CgZbD?Q BW %G @OQzzѰ&Y$QX + TJ-o>p\#4 ܰbp =1f |pS5F MpdZd_׈lmE Kpd\6 WFdȝ j^@ _`d@_`Ԫyl Jʜ;ǵu 2du (75h6;  l˷O ]^G*Ng&`]ʸq[c&Z_Yߠt5wΠ %7C@"p  nz'a f4`;Pa p[ u s9 C>ZZ F^P JNuZhsxx8Smf ki?_PP %fvy gWsg~k=`f %!0 Xf mϠ Tp`r аk"%)e,|7 2 1 ?9o3v "O V `/c0yIi0 @BlV` g e kL0S`o Ee h@ `O `%ev[HPgB%'%NYq`F0`@s4I窲P>`V(-R_W <+KK_`/ [\#yetzkGgZׅNee\|#p _01@ T` [;1 RZʛP2Je8o+#)w Ra9RN) oE0VnձPx P2j1NDI`VAJ! x#J`"pTI-g됈D6&i-IH$ W aɢ-`S<}"זNeVFXD>BHA6߲a%e>9-Ց~՘>韞>.nꥎ꧞Ճ.0P~3$I0HNo# ɮ.Nn׎nnBbtnnUS:E 5.$$  M~aqG +Hs/Ow"$W>aUbGo>d-/1oXQ{8D+bdxqG!5XH&9dŖ0eF S_~<ShѠF*S^+%*(_ yʴٲHo۷{.\y_&\ذ]ˆ҅xݴ|;reportlab-3.3.0/docs/userguide/0000755000175000017500000000000012661063723015773 5ustar rptlabrptlabreportlab-3.3.0/docs/userguide/ch1_intro.py0000664000175000017500000004037712661063723020250 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details __version__ = '$Id$' from tools.docco.rl_doc_utils import * from reportlab.platypus.tableofcontents import TableOfContents from datetime import datetime import reportlab from reportlab.rl_config import invariant title("ReportLab PDF Library") title("User Guide") centred('ReportLab Version ' + reportlab.Version) centred((datetime(2000,1,1,0,0,0) if invariant else datetime.now()).strftime('Document generated on %Y/%m/%d %H:%M:%S %Z')) nextTemplate("TOC") headingTOC() toc = TableOfContents() PS = ParagraphStyle toc.levelStyles = [ PS(fontName='Times-Bold', fontSize=14, name='TOCHeading1', leftIndent=20, firstLineIndent=-20, spaceBefore=5, leading=16), PS(fontSize=12, name='TOCHeading2', leftIndent=40, firstLineIndent=-20, spaceBefore=0, leading=12), PS(fontSize=10, name='TOCHeading3', leftIndent=60, firstLineIndent=-20, spaceBefore=0, leading=12), PS(fontSize=10, name='TOCHeading4', leftIndent=100, firstLineIndent=-20, spaceBefore=0, leading=12), ] getStory().append(toc) nextTemplate("Normal") ######################################################################## # # Chapter 1 # ######################################################################## heading1("Introduction") heading2("About this document") disc("""This document is an introduction to the ReportLab PDF library. Some previous programming experience is presumed and familiarity with the Python Programming language is recommended. If you are new to Python, we tell you in the next section where to go for orientation. """) disc(""" This manual does not cover 100% of the features, but should explain all the main concepts and help you get started, and point you at other learning resources. After working your way through this, you should be ready to begin writing programs to produce sophisticated reports. """) disc("""In this chapter, we will cover the groundwork:""") bullet("What is ReportLab all about, and why should I use it?") bullet("What is Python?") bullet("How do I get everything set up and running?") todo(""" We need your help to make sure this manual is complete and helpful. Please send any feedback to our user mailing list, which is signposted from www.reportlab.com. """) heading2("What is the ReportLab PDF Library?") disc("""This is a software library that lets you directly create documents in Adobe's Portable Document Format (PDF) using the Python programming language. It also creates charts and data graphics in various bitmap and vector formats as well as PDF.""") disc("""PDF is the global standard for electronic documents. It supports high-quality printing yet is totally portable across platforms, thanks to the freely available Acrobat Reader. Any application which previously generated hard copy reports or driving a printer can benefit from making PDF documents instead; these can be archived, emailed, placed on the web, or printed out the old-fashioned way. However, the PDF file format is a complex indexed binary format which is impossible to type directly. The PDF format specification is more than 600 pages long and PDF files must provide precise byte offsets -- a single extra character placed anywhere in a valid PDF document can render it invalid. This makes it harder to generate than HTML.""") disc("""Most of the world's PDF documents have been produced by Adobe's Acrobat tools, or rivals such as JAWS PDF Creator, which act as 'print drivers'. Anyone wanting to automate PDF production would typically use a product like Quark, Word or Framemaker running in a loop with macros or plugins, connected to Acrobat. Pipelines of several languages and products can be slow and somewhat unwieldy. """) disc("""The ReportLab library directly creates PDF based on your graphics commands. There are no intervening steps. Your applications can generate reports extremely fast - sometimes orders of magnitude faster than traditional report-writing tools. This approach is shared by several other libraries - PDFlib for C, iText for Java, iTextSharp for .NET and others. However, The ReportLab library differs in that it can work at much higher levels, with a full featured engine for laying out documents complete with tables and charts. """) disc("""In addition, because you are writing a program in a powerful general purpose language, there are no restrictions at all on where you get your data from, how you transform it, and the kind of output you can create. And you can reuse code across whole families of reports.""") disc("""The ReportLab library is expected to be useful in at least the following contexts:""") bullet("Dynamic PDF generation on the web") bullet("High-volume corporate reporting and database publishing") bullet("""An embeddable print engine for other applications, including a 'report language' so that users can customize their own reports. This is particularly relevant to cross-platform apps which cannot rely on a consistent printing or previewing API on each operating system.""") bullet("""A 'build system' for complex documents with charts, tables and text such as management accounts, statistical reports and scientific papers """) bullet("""Going from XML to PDF in one step""") heading2("ReportLab's commercial software") disc(""" The ReportLab library forms the foundation of our commercial solution for PDF generation, Report Markup Language (RML). This is available for evaluation on our web site with full documentation. We believe that RML is the fastest and easiest way to develop rich PDF workflows. You work in a markup language at a similar level to HTML, using your favorite templating system to populate an RML document; then call our rml2pdf API function to generate a PDF. It's what ReportLab staff use to build all of the solutions you can see on reportlab.com. Key differences: """) bullet("""Fully documented with two manuals, a formal specification (the DTD) and extensive self-documenting tests. (By contrast, we try to make sure the open source documentation isn't wrong, but we don't always keep up with the code)""") bullet("""Work in high-level markup rather than constructing graphs of Python objects """) bullet("""Requires no Python expertise - your colleagues may thank you after you've left!'""") bullet("""Support for vector graphics and inclusion of other PDF documents""") bullet("""Many more useful features expressed with a single tag, which would need a lot of coding in the open source package""") bullet("""Commercial support is included""") disc(""" We ask open source developers to consider trying out RML where it is appropriate. You can register on our site and try out a copy before buying. The costs are reasonable and linked to the volume of the project, and the revenue helps us spend more time developing this software.""") heading2("What is Python?") disc(""" Python is an interpreted, interactive, object-oriented programming language. It is often compared to Tcl, Perl, Scheme or Java. """) disc(""" Python combines remarkable power with very clear syntax. It has modules, classes, exceptions, very high level dynamic data types, and dynamic typing. There are interfaces to many system calls and libraries, as well as to various windowing systems (X11, Motif, Tk, Mac, MFC). New built-in modules are easily written in C or C++. Python is also usable as an extension language for applications that need a programmable interface. """) disc(""" Python is as old as Java and has been growing steadily in popularity for years; since our library first came out it has entered the mainstream. Many ReportLab library users are already Python devotees, but if you are not, we feel that the language is an excellent choice for document-generation apps because of its expressiveness and ability to get data from anywhere. """) disc(""" Python is copyrighted but freely usable and distributable, even for commercial use. """) heading2("Acknowledgements") disc("""Many people have contributed to ReportLab. We would like to thank in particular (in alphabetical order): Albertas Agejevas, Alex Buck, Andre Reitz, Andrew Cutler, Andrew Mercer, Ben Echols, Benjamin Dumke, Benn B, Chad Miller, Chris Buergi, Chris Lee, Christian Jacobs, Dinu Gherman, Edward Greve, Eric Johnson, Felix Labrecque, Fubu @ bitbucket, Gary Poster, Germán M. Bravo, Guillaume Francois, Hans Brand, Henning Vonbargen, Hosam Aly, Ian Stevens, James Martin-Collar, Jeff Bauer, Jerome Alet, Jerry Casiano, Jorge Godoy, Keven D Smith, Kyle MacFarlane, Magnus Lie Hetland, Marcel Tromp, Marius Gedminas, Mark de Wit, Matthew Duggan, Matthias Kirst, Matthias Klose, Max M, Michael Egorov, Michael Spector, Mike Folwell, Mirko Dziadzka, Moshe Wagner, Nate Silva, Paul McNett, Peter Johnson, PJACock, Publio da Costa Melo, Randolph Bentson, Robert Alsina, Robert Hölzl, Robert Kern, Ron Peleg, Ruby Yocum, Simon King, Stephan Richter, Steve Halasz, Stoneleaf @ bitbucket, T Blatter, Tim Roberts, Tomasz Swiderski, Ty Sarna, Volker Haas, Yoann Roman, and many more.""") disc("""Special thanks go to Just van Rossum for his valuable assistance with font technicalities.""") disc("""Moshe Wagner and Hosam Aly deserve a huge thanks for contributing to the RTL patch, which is not yet on the trunk.""") disc("""Marius Gedminas deserves a big hand for contributing the work on TrueType fonts and we are glad to include these in the toolkit. Finally we thank Michal Kosmulski for the DarkGarden font for and Bitstream Inc. for the Vera fonts.""") heading2("Installation and Setup") disc("""To avoid duplication, the installation instructions are kept in the README file in our distribution, which can be viewed online at ^http://bitbucket.org/rptlab/reportlab/^""") disc("""This release (3.0) of ReportLab requires Python versions 2.7, 3.3 or higher. If you need to use Python 2.5 or 2.6, please use the latest ReportLab 2.x package. """) heading2("Getting Involved") disc("""ReportLab is an Open Source project. Although we are a commercial company we provide the core PDF generation sources freely, even for commercial purposes, and we make no income directly from these modules. We also welcome help from the community as much as any other Open Source project. There are many ways in which you can help:""") bullet("""General feedback on the core API. Does it work for you? Are there any rough edges? Does anything feel clunky and awkward?""") bullet("""New objects to put in reports, or useful utilities for the library. We have an open standard for report objects, so if you have written a nice chart or table class, why not contribute it?""") bullet("""Snippets and Case Studies: If you have produced some nice output, register online on ^http://www.reportlab.com^ and submit a snippet of your output (with or without scripts). If ReportLab solved a problem for you at work, write a little 'case study' and submit it. And if your web site uses our tools to make reports, let us link to it. We will be happy to display your work (and credit it with your name and company) on our site!""") bullet("""Working on the core code: we have a long list of things to refine or to implement. If you are missing some features or just want to help out, let us know!""") disc("""The first step for anyone wanting to learn more or get involved is to join the mailing list. To Subscribe visit $http://two.pairlist.net/mailman/listinfo/reportlab-users$. From there you can also browse through the group's archives and contributions. The mailing list is the place to report bugs and get support. """) disc("""The code now lives on BitBucket ($http://bitbucket.org/rptlab/reportlab/$) in a Mercurial repository, along with an issue tracker and wiki. Everyone should feel free to contribute, but if you are working actively on some improvements or want to draw attention to an issue, please use the mailing list to let us know.""") heading2("Site Configuration") disc("""There are a number of options which most likely need to be configured globally for a site. The python script module $reportlab/rl_config.py$ may be edited to change the values of several important sitewide properties.""") bullet("""verbose: set to integer values to control diagnostic output.""") bullet("""shapeChecking: set this to zero to turn off a lot of error checking in the graphics modules""") bullet("""defaultEncoding: set this to WinAnsiEncoding or MacRomanEncoding.""") bullet("""defaultPageSize: set this to one of the values defined in reportlab/lib/pagesizes.py; as delivered it is set to pagesizes.A4; other values are pagesizes.letter etc.""") bullet("""defaultImageCaching: set to zero to inhibit the creation of .a85 files on your hard-drive. The default is to create these preprocessed PDF compatible image files for faster loading""") bullet("""T1SearchPath: this is a python list of strings representing directories that may be queried for information on Type 1 fonts""") bullet("""TTFSearchPath: this is a python list of strings representing directories that may be queried for information on TrueType fonts""") bullet("""CMapSearchPath: this is a python list of strings representing directories that may be queried for information on font code maps.""") bullet("""showBoundary: set to non-zero to get boundary lines drawn.""") bullet("""ZLIB_WARNINGS: set to non-zero to get warnings if the Python compression extension is not found.""") bullet("""pageComression: set to non-zero to try and get compressed PDF.""") bullet("""allowtableBoundsErrors: set to 0 to force an error on very large Platypus table elements""") bullet("""emptyTableAction: Controls behaviour for empty tables, can be 'error' (default), 'indicate' or 'ignore'.""") heading2("Learning More About Python") disc(""" If you are a total beginner to Python, you should check out one or more from the growing number of resources on Python programming. The following are freely available on the web: """) bullet("""Python Documentation. A list of documentation on the Python.org web site. $http://www.python.org/doc/$ """) bullet("""Python Tutorial. The official Python Tutorial , originally written by Guido van Rossum himself. $http://docs.python.org/tutorial/$ """) bullet("""Learning to Program. A tutorial on programming by Alan Gauld. Has a heavy emphasis on Python, but also uses other languages. $http://www.freenetpages.co.uk/hp/alan.gauld/$ """) bullet("""Instant Python. A 6-page minimal crash course by Magnus Lie Hetland. $http://www.hetland.org/python/instant-python.php$ """) bullet("""Dive Into Python. A free Python tutorial for experienced programmers. $http://www.diveintopython.net/$ """) from reportlab.lib.codecharts import SingleByteEncodingChart from tools.docco.stylesheet import getStyleSheet styles = getStyleSheet() indent0_style = styles['Indent0'] indent1_style = styles['Indent1'] heading2("Goals for the 3.x release series") disc("""ReportLab 3.0 has been produced to help in the migration to Python 3.x. Python 3.x will be standard in future Ubuntu releases and is gaining popularity, and a good proportion of major Python packages now run on Python 3. """) bullet("""Python 3.x compatibility. A single line of code should run on 2.7 and 3.3""") bullet(""" __init__.py restricts to 2.7 or >=3.3""") bullet("""__init__.py allow the import of on optional reportlab.local_rl_mods to allow monkey patching etc.""") bullet("""rl_config now imports rl_settings & optionally local_rl_settings""") bullet("""ReportLab C extensions now live inside reportlab; _rl_accel is no longer required. All _rl_accel imports now pass through reportlab.lib.rl_accel""") bullet("""xmllib is gone, alongside the paraparser stuff that caused issues in favour of HTMLParser.""") bullet("""some obsolete C extensions (sgmlop and pyHnj) are gone""") bullet("""Improved support for multi-threaded systems to the _rl_accel C extension module.""") bullet("""Removed reportlab/lib/ para.py & pycanvas.py. These would better belong in third party packages, which can make use of the monkeypatching feature above.""") bullet("""Add ability to output greyscale and 1-bit PIL images without conversion to RGB. (contributed by Matthew Duggan)""") bullet("""highlight annotation (contributed by Ben Echols)""") bullet("""full compliance with pip, easy_install, wheels etc""") disc("""Detailed release notes are available at $http://www.reportlab.com/software/documentation/relnotes/30/$""") reportlab-3.3.0/docs/userguide/app_demos.py0000664000175000017500000001065112661063723020321 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/app_demos.py from tools.docco.rl_doc_utils import * Appendix1("ReportLab Demos") disc("""In the subdirectories of $reportlab/demos$ there are a number of working examples showing almost all aspects of reportlab in use.""") heading2("""Odyssey""") disc(""" The three scripts odyssey.py, dodyssey.py and fodyssey.py all take the file odyssey.txt and produce PDF documents. The included odyssey.txt is short; a longer and more testing version can be found at ftp://ftp.reportlab.com/odyssey.full.zip. """) eg(""" Windows cd reportlab\\demos\\odyssey python odyssey.py start odyssey.pdf Linux cd reportlab/demos/odyssey python odyssey.py acrord odyssey.pdf """) disc("""Simple formatting is shown by the odyssey.py script. It runs quite fast, but all it does is gather the text and force it onto the canvas pages. It does no paragraph manipulation at all so you get to see the XML < & > tags. """) disc("""The scripts fodyssey.py and dodyssey.py handle paragraph formatting so you get to see colour changes etc. Both scripts use the document template class and the dodyssey.py script shows the ability to do dual column layout and uses multiple page templates. """) heading2("""Standard Fonts and Colors""") disc("""In $reportlab/demos/stdfonts$ the script stdfonts.py can be used to illustrate ReportLab's standard fonts. Run the script using""") eg(""" cd reportlab\\demos\\stdfonts python stdfonts.py """) disc(""" to produce two PDF documents, StandardFonts_MacRoman.pdf & StandardFonts_WinAnsi.pdf which show the two most common built in font encodings. """) disc("""The colortest.py script in $reportlab/demos/colors$ demonstrates the different ways in which reportlab can set up and use colors.""") disc("""Try running the script and viewing the output document, colortest.pdf. This shows different color spaces and a large selection of the colors which are named in the $reportlab.lib.colors$ module. """) heading2("""Py2pdf""") disc("""Dinu Gherman contributed this useful script which uses reportlab to produce nicely colorized PDF documents from Python scripts including bookmarks for classes, methods and functions. To get a nice version of the main script try""") eg(""" cd reportlab/demos/py2pdf python py2pdf.py py2pdf.py acrord py2pdf.pdf """) disc("""i.e. we used py2pdf to produce a nice version of py2pdf.py in the document with the same rootname and a .pdf extension. """) disc(""" The py2pdf.py script has many options which are beyond the scope of this simple introduction; consult the comments at the start of the script. """) heading2("Gadflypaper") disc(""" The Python script, gfe.py, in $reportlab/demos/gadflypaper$ uses an inline style of document preparation. The script almost entirely produced by Aaron Watters produces a document describing Aaron's $gadfly$ in memory database for Python. To generate the document use """) eg(""" cd reportlab\\gadflypaper python gfe.py start gfe.pdf """) disc(""" everything in the PDF document was produced by the script which is why this is an inline style of document production. So, to produce a header followed by some text the script uses functions $header$ and $p$ which take some text and append to a global story list. """) eg(''' header("Conclusion") p("""The revamped query engine design in Gadfly 2 supports .......... and integration.""") ''') heading2("""Pythonpoint""") disc("""Andy Robinson has refined the pythonpoint.py script (in $reportlab\\demos\\pythonpoint$) until it is a really useful script. It takes an input file containing an XML markup and uses an xmllib style parser to map the tags into PDF slides. When run in its own directory pythonpoint.py takes as a default input the file pythonpoint.xml and produces pythonpoint.pdf which is documentation for Pythonpoint! You can also see it in action with an older paper """) eg(""" cd reportlab\\demos\\pythonpoint python pythonpoint.py monterey.xml start monterey.pdf """) disc(""" Not only is pythonpoint self documenting, but it also demonstrates reportlab and PDF. It uses many features of reportlab (document templates, tables etc). Exotic features of PDF such as fadeins and bookmarks are also shown to good effect. The use of an XML document can be contrasted with the inline style of the gadflypaper demo; the content is completely separate from the formatting """) reportlab-3.3.0/docs/userguide/ch2_graphics.py0000664000175000017500000013021012661063723020700 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch2_graphics.py from tools.docco.rl_doc_utils import * from reportlab.lib.codecharts import SingleByteEncodingChart heading1("Graphics and Text with $pdfgen$") heading2("Basic Concepts") disc(""" The $pdfgen$ package is the lowest level interface for generating PDF documents. A $pdfgen$ program is essentially a sequence of instructions for "painting" a document onto a sequence of pages. The interface object which provides the painting operations is the $pdfgen canvas$. """) disc(""" The canvas should be thought of as a sheet of white paper with points on the sheet identified using Cartesian ^(X,Y)^ coordinates which by default have the ^(0,0)^ origin point at the lower left corner of the page. Furthermore the first coordinate ^x^ goes to the right and the second coordinate ^y^ goes up, by default.""") disc(""" A simple example program that uses a canvas follows. """) eg(""" from reportlab.pdfgen import canvas def hello(c): c.drawString(100,100,"Hello World") c = canvas.Canvas("hello.pdf") hello(c) c.showPage() c.save() """) disc(""" The above code creates a $canvas$ object which will generate a PDF file named $hello.pdf$ in the current working directory. It then calls the $hello$ function passing the $canvas$ as an argument. Finally the $showPage$ method saves the current page of the canvas and the $save$ method stores the file and closes the canvas.""") disc(""" The $showPage$ method causes the $canvas$ to stop drawing on the current page and any further operations will draw on a subsequent page (if there are any further operations -- if not no new page is created). The $save$ method must be called after the construction of the document is complete -- it generates the PDF document, which is the whole purpose of the $canvas$ object. """) heading2("More about the Canvas") disc(""" Before describing the drawing operations, we will digress to cover some of the things which can be done to configure a canvas. There are many different settings available. If you are new to Python or can't wait to produce some output, you can skip ahead, but come back later and read this!""") disc("""First of all, we will look at the constructor arguments for the canvas:""") eg(""" def __init__(self,filename, pagesize=(595.27,841.89), bottomup = 1, pageCompression=0, encoding=rl_config.defaultEncoding, verbosity=0 encrypt=None): """) disc("""The $filename$ argument controls the name of the final PDF file. You may also pass in any open file object (such as $sys.stdout$, the python process standard output) and the PDF document will be written to that. Since PDF is a binary format, you should take care when writing other stuff before or after it; you can't deliver PDF documents inline in the middle of an HTML page!""") disc("""The $pagesize$ argument is a tuple of two numbers in points (1/72 of an inch). The canvas defaults to $A4$ (an international standard page size which differs from the American standard page size of $letter$), but it is better to explicitly specify it. Most common page sizes are found in the library module $reportlab.lib.pagesizes$, so you can use expressions like""") eg("""from reportlab.lib.pagesizes import letter, A4 myCanvas = Canvas('myfile.pdf', pagesize=letter) width, height = letter #keep for later """) pencilnote() disc("""If you have problems printing your document make sure you are using the right page size (usually either $A4$ or $letter$). Some printers do not work well with pages that are too large or too small.""") disc("""Very often, you will want to calculate things based on the page size. In the example above we extracted the width and height. Later in the program we may use the $width$ variable to define a right margin as $width - inch$ rather than using a constant. By using variables the margin will still make sense even if the page size changes.""") disc("""The $bottomup$ argument switches coordinate systems. Some graphics systems (like PDF and PostScript) place (0,0) at the bottom left of the page others (like many graphical user interfaces [GUI's]) place the origin at the top left. The $bottomup$ argument is deprecated and may be dropped in future""") todo("""Need to see if it really works for all tasks, and if not then get rid of it""") disc("""The $pageCompression$ option determines whether the stream of PDF operations for each page is compressed. By default page streams are not compressed, because the compression slows the file generation process. If output size is important set $pageCompression=1$, but remember that, compressed documents will be smaller, but slower to generate. Note that images are always compressed, and this option will only save space if you have a very large amount of text and vector graphics on each page.""") disc("""The $encoding$ argument is largely obsolete in version 2.0 and can probably be omitted by 99% of users. Its default value is fine unless you very specifically need to use one of the 25 or so characters which are present in MacRoman and not in Winansi. A useful reference to these is here: http://www.alanwood.net/demos/charsetdiffs.html. The parameter determines which font encoding is used for the standard Type 1 fonts; this should correspond to the encoding on your system. Note that this is the encoding used internally by the font; text you pass to the ReportLab toolkit for rendering should always either be a Python unicode string object or a UTF-8 encoded byte string (see the next chapter)! The font encoding has two values at present: $'WinAnsiEncoding'$ or $'MacRomanEncoding'$. The variable $rl_config.defaultEncoding$ above points to the former, which is standard on Windows, Mac OS X and many Unices (including Linux). If you are Mac user and don't have OS X, you may want to make a global change: modify the line at the top of reportlab/pdfbase/pdfdoc.py to switch it over. Otherwise, you can probably just ignore this argument completely and never pass it. For all TTF and the commonly-used CID fonts, the encoding you pass in here is ignored, since the reportlab library itself knows the right encodings in those cases.""") disc("""The demo script $reportlab/demos/stdfonts.py$ will print out two test documents showing all code points in all fonts, so you can look up characters. Special characters can be inserted into string commands with the usual Python escape sequences; for example \\101 = 'A'.""") disc("""The $verbosity$ argument determines how much log information is printed. By default, it is zero to assist applications which want to capture PDF from standard output. With a value of 1, you will get a confirmation message each time a document is generated. Higher numbers may give more output in future.""") disc("""The $encrypt$ argument determines if and how the document is encrypted. By default, the document is not encrypted. If $encrypt$ is a string object, it is used as the user password for the pdf. If $encrypt$ is an instance of $reportlab.lib.pdfencrypt.StandardEncryption$, this object is used to encrypt the pdf. This allows more finegrained control over the encryption settings. Encryption is covered in more detail in Chapter 4.""") todo("to do - all the info functions and other non-drawing stuff") todo("""Cover all constructor arguments, and setAuthor etc.""") heading2("Drawing Operations") disc(""" Suppose the $hello$ function referenced above is implemented as follows (we will not explain each of the operations in detail yet). """) eg(examples.testhello) disc(""" Examining this code notice that there are essentially two types of operations performed using a canvas. The first type draws something on the page such as a text string or a rectangle or a line. The second type changes the state of the canvas such as changing the current fill or stroke color or changing the current font type and size. """) disc(""" If we imagine the program as a painter working on the canvas the "draw" operations apply paint to the canvas using the current set of tools (colors, line styles, fonts, etcetera) and the "state change" operations change one of the current tools (changing the fill color from whatever it was to blue, or changing the current font to $Times-Roman$ in 15 points, for example). """) disc(""" The document generated by the "hello world" program listed above would contain the following graphics. """) illust(examples.hello, '"Hello World" in pdfgen') heading3("About the demos in this document") disc(""" This document contains demonstrations of the code discussed like the one shown in the rectangle above. These demos are drawn on a "tiny page" embedded within the real pages of the guide. The tiny pages are %s inches wide and %s inches tall. The demo displays show the actual output of the demo code. For convenience the size of the output has been reduced slightly. """ % (examplefunctionxinches, examplefunctionyinches)) heading2('The tools: the "draw" operations') disc(""" This section briefly lists the tools available to the program for painting information onto a page using the canvas interface. These will be discussed in detail in later sections. They are listed here for easy reference and for summary purposes. """) heading3("Line methods") eg("""canvas.line(x1,y1,x2,y2)""") eg("""canvas.lines(linelist)""") disc(""" The line methods draw straight line segments on the canvas. """) heading3("Shape methods") eg("""canvas.grid(xlist, ylist) """) eg("""canvas.bezier(x1, y1, x2, y2, x3, y3, x4, y4)""") eg("""canvas.arc(x1,y1,x2,y2) """) eg("""canvas.rect(x, y, width, height, stroke=1, fill=0) """) eg("""canvas.ellipse(x1,y1, x2,y2, stroke=1, fill=0)""") eg("""canvas.wedge(x1,y1, x2,y2, startAng, extent, stroke=1, fill=0) """) eg("""canvas.circle(x_cen, y_cen, r, stroke=1, fill=0)""") eg("""canvas.roundRect(x, y, width, height, radius, stroke=1, fill=0) """) disc(""" The shape methods draw common complex shapes on the canvas. """) heading3("String drawing methods") eg("""canvas.drawString(x, y, text):""") eg("""canvas.drawRightString(x, y, text) """) eg("""canvas.drawCentredString(x, y, text)""") disc(""" The draw string methods draw single lines of text on the canvas. """) heading3("The text object methods") eg("""textobject = canvas.beginText(x, y) """) eg("""canvas.drawText(textobject) """) disc(""" Text objects are used to format text in ways that are not supported directly by the $canvas$ interface. A program creates a text object from the $canvas$ using $beginText$ and then formats text by invoking $textobject$ methods. Finally the $textobject$ is drawn onto the canvas using $drawText$. """) heading3("The path object methods") eg("""path = canvas.beginPath() """) eg("""canvas.drawPath(path, stroke=1, fill=0) """) eg("""canvas.clipPath(path, stroke=1, fill=0) """) disc(""" Path objects are similar to text objects: they provide dedicated control for performing complex graphical drawing not directly provided by the canvas interface. A program creates a path object using $beginPath$ populates the path with graphics using the methods of the path object and then draws the path on the canvas using $drawPath$.""") disc("""It is also possible to use a path as a "clipping region" using the $clipPath$ method -- for example a circular path can be used to clip away the outer parts of a rectangular image leaving only a circular part of the image visible on the page. """) heading3("Image methods") pencilnote() disc(""" You need the Python Imaging Library (PIL) to use images with the ReportLab package. Examples of the techniques below can be found by running the script $test_pdfgen_general.py$ in our $tests$ subdirectory and looking at page 7 of the output. """) disc(""" There are two similar-sounding ways to draw images. The preferred one is the $drawImage$ method. This implements a caching system so you can define an image once and draw it many times; it will only be stored once in the PDF file. $drawImage$ also exposes one advanced parameter, a transparency mask, and will expose more in future. The older technique, $drawInlineImage$, stores bitmaps within the page stream and is thus very inefficient if you use the same image more than once in a document; but can result in PDFs which render faster if the images are very small and not repeated. We'll discuss the oldest one first: """) eg("""canvas.drawInlineImage(self, image, x,y, width=None,height=None) """) disc(""" The $drawInlineImage$ method places an image on the canvas. The $image$ parameter may be either a PIL Image object or an image filename. Many common file formats are accepted including GIF and JPEG. It returns the size of the actual image in pixels as a (width, height) tuple. """) eg("""canvas.drawImage(self, image, x,y, width=None,height=None,mask=None) """) disc(""" The arguments and return value work as for $drawInlineImage$. However, we use a caching system; a given image will only be stored the first time it is used, and just referenced on subsequent use. If you supply a filename, it assumes that the same filename means the same image. If you supply a PIL image, it tests if the content has actually changed before re-embedding.""") disc(""" The $mask$ parameter lets you create transparent images. It takes 6 numbers and defines the range of RGB values which will be masked out or treated as transparent. For example with [0,2,40,42,136,139], it will mask out any pixels with a Red value from 0 or 1, Green from 40 or 41 and Blue of 136, 137 or 138 (on a scale of 0-255). It's currently your job to know which color is the 'transparent' or background one.""") disc("""PDF allows for many image features and we will expose more of the over time, probably with extra keyword arguments to $drawImage$.""") heading3("Ending a page") eg("""canvas.showPage()""") disc("""The $showPage$ method finishes the current page. All additional drawing will be done on another page.""") pencilnote() disc("""Warning! All state changes (font changes, color settings, geometry transforms, etcetera) are FORGOTTEN when you advance to a new page in $pdfgen$. Any state settings you wish to preserve must be set up again before the program proceeds with drawing!""") heading2('The toolbox: the "state change" operations') disc(""" This section briefly lists the ways to switch the tools used by the program for painting information onto a page using the $canvas$ interface. These too will be discussed in detail in later sections. """) heading3("Changing Colors") eg("""canvas.setFillColorCMYK(c, m, y, k) """) eg("""canvas.setStrikeColorCMYK(c, m, y, k) """) eg("""canvas.setFillColorRGB(r, g, b) """) eg("""canvas.setStrokeColorRGB(r, g, b) """) eg("""canvas.setFillColor(acolor) """) eg("""canvas.setStrokeColor(acolor) """) eg("""canvas.setFillGray(gray) """) eg("""canvas.setStrokeGray(gray) """) disc(""" PDF supports three different color models: gray level, additive (red/green/blue or RGB), and subtractive with darkness parameter (cyan/magenta/yellow/darkness or CMYK). The ReportLab packages also provide named colors such as $lawngreen$. There are two basic color parameters in the graphics state: the $Fill$ color for the interior of graphic figures and the $Stroke$ color for the boundary of graphic figures. The above methods support setting the fill or stroke color using any of the four color specifications. """) heading3("Changing Fonts") eg("""canvas.setFont(psfontname, size, leading = None) """) disc(""" The $setFont$ method changes the current text font to a given type and size. The $leading$ parameter specifies the distance down to move when advancing from one text line to the next. """) heading3("Changing Graphical Line Styles") eg("""canvas.setLineWidth(width) """) eg("""canvas.setLineCap(mode) """) eg("""canvas.setLineJoin(mode) """) eg("""canvas.setMiterLimit(limit) """) eg("""canvas.setDash(self, array=[], phase=0) """) disc(""" Lines drawn in PDF can be presented in a number of graphical styles. Lines can have different widths, they can end in differing cap styles, they can meet in different join styles, and they can be continuous or they can be dotted or dashed. The above methods adjust these various parameters.""") heading3("Changing Geometry") eg("""canvas.setPageSize(pair) """) eg("""canvas.transform(a,b,c,d,e,f): """) eg("""canvas.translate(dx, dy) """) eg("""canvas.scale(x, y) """) eg("""canvas.rotate(theta) """) eg("""canvas.skew(alpha, beta) """) disc(""" All PDF drawings fit into a specified page size. Elements drawn outside of the specified page size are not visible. Furthermore all drawn elements are passed through an affine transformation which may adjust their location and/or distort their appearence. The $setPageSize$ method adjusts the current page size. The $transform$, $translate$, $scale$, $rotate$, and $skew$ methods add additional transformations to the current transformation. It is important to remember that these transformations are incremental -- a new transform modifies the current transform (but does not replace it). """) heading3("State control") eg("""canvas.saveState() """) eg("""canvas.restoreState() """) disc(""" Very often it is important to save the current font, graphics transform, line styles and other graphics state in order to restore them later. The $saveState$ method marks the current graphics state for later restoration by a matching $restoreState$. Note that the save and restore method invokation must match -- a restore call restores the state to the most recently saved state which hasn't been restored yet. You cannot save the state on one page and restore it on the next, however -- no state is preserved between pages.""") heading2("Other $canvas$ methods.") disc(""" Not all methods of the $canvas$ object fit into the "tool" or "toolbox" categories. Below are some of the misfits, included here for completeness. """) eg(""" canvas.setAuthor() canvas.addOutlineEntry(title, key, level=0, closed=None) canvas.setTitle(title) canvas.setSubject(subj) canvas.pageHasData() canvas.showOutline() canvas.bookmarkPage(name) canvas.bookmarkHorizontalAbsolute(name, yhorizontal) canvas.doForm() canvas.beginForm(name, lowerx=0, lowery=0, upperx=None, uppery=None) canvas.endForm() canvas.linkAbsolute(contents, destinationname, Rect=None, addtopage=1, name=None, **kw) canvas.linkRect(contents, destinationname, Rect=None, addtopage=1, relative=1, name=None, **kw) canvas.getPageNumber() canvas.addLiteral() canvas.getAvailableFonts() canvas.stringWidth(self, text, fontName, fontSize, encoding=None) canvas.setPageCompression(onoff=1) canvas.setPageTransition(self, effectname=None, duration=1, direction=0,dimension='H',motion='I') """) heading2('Coordinates (default user space)') disc(""" By default locations on a page are identified by a pair of numbers. For example the pair $(4.5*inch, 1*inch)$ identifies the location found on the page by starting at the lower left corner and moving to the right 4.5 inches and up one inch. """) disc("""For example, the following function draws a number of elements on a $canvas$.""") eg(examples.testcoords) disc("""In the default user space the "origin" ^(0,0)^ point is at the lower left corner. Executing the $coords$ function in the default user space (for the "demo minipage") we obtain the following.""") illust(examples.coords, 'The Coordinate System') heading3("Moving the origin: the $translate$ method") disc("""Often it is useful to "move the origin" to a new point off the lower left corner. The $canvas.translate(^x,y^)$ method moves the origin for the current page to the point currently identified by ^(x,y)^.""") disc("""For example the following translate function first moves the origin before drawing the same objects as shown above.""") eg(examples.testtranslate) disc("""This produces the following.""") illust(examples.translate, "Moving the origin: the $translate$ method") #illust(NOP) # execute some code pencilnote() disc(""" Note: As illustrated in the example it is perfectly possible to draw objects or parts of objects "off the page". In particular a common confusing bug is a translation operation that translates the entire drawing off the visible area of the page. If a program produces a blank page it is possible that all the drawn objects are off the page. """) heading3("Shrinking and growing: the scale operation") disc("""Another important operation is scaling. The scaling operation $canvas.scale(^dx,dy^)$ stretches or shrinks the ^x^ and ^y^ dimensions by the ^dx^, ^dy^ factors respectively. Often ^dx^ and ^dy^ are the same -- for example to reduce a drawing by half in all dimensions use $dx = dy = 0.5$. However for the purposes of illustration we show an example where $dx$ and $dy$ are different. """) eg(examples.testscale) disc("""This produces a "short and fat" reduced version of the previously displayed operations.""") illust(examples.scale, "Scaling the coordinate system") #illust(NOP) # execute some code pencilnote() disc("""Note: scaling may also move objects or parts of objects off the page, or may cause objects to "shrink to nothing." """) disc("""Scaling and translation can be combined, but the order of the operations are important.""") eg(examples.testscaletranslate) disc("""This example function first saves the current $canvas$ state and then does a $scale$ followed by a $translate$. Afterward the function restores the state (effectively removing the effects of the scaling and translation) and then does the same operations in a different order. Observe the effect below.""") illust(examples.scaletranslate, "Scaling and Translating") #illust(NOP) # execute some code pencilnote() disc("""Note: scaling shrinks or grows everything including line widths so using the canvas.scale method to render a microscopic drawing in scaled microscopic units may produce a blob (because all line widths will get expanded a huge amount). Also rendering an aircraft wing in meters scaled to centimeters may cause the lines to shrink to the point where they disappear. For engineering or scientific purposes such as these scale and translate the units externally before rendering them using the canvas.""") heading3("Saving and restoring the $canvas$ state: $saveState$ and $restoreState$") disc(""" The $scaletranslate$ function used an important feature of the $canvas$ object: the ability to save and restore the current parameters of the $canvas$. By enclosing a sequence of operations in a matching pair of $canvas.saveState()$ an $canvas.restoreState()$ operations all changes of font, color, line style, scaling, translation, or other aspects of the $canvas$ graphics state can be restored to the state at the point of the $saveState()$. Remember that the save/restore calls must match: a stray save or restore operation may cause unexpected and undesirable behavior. Also, remember that no $canvas$ state is preserved across page breaks, and the save/restore mechanism does not work across page breaks. """) heading3("Mirror image") disc(""" It is interesting although perhaps not terribly useful to note that scale factors can be negative. For example the following function """) eg(examples.testmirror) disc(""" creates a mirror image of the elements drawn by the $coord$ function. """) illust(examples.mirror, "Mirror Images") disc(""" Notice that the text strings are painted backwards. """) heading2("Colors") disc(""" There are generally two types of colors used in PDF depending on the media where the PDF will be used. The most commonly known screen colors model RGB can be used in PDF, however in professional printing another color model CMYK is mainly used which gives more control over how inks are applied to paper. More on these color models below. """) heading3("RGB Colors") disc(""" The $RGB$ or additive color representation follows the way a computer screen adds different levels of the red, green, and blue light to make any color in between, where white is formed by turning all three lights on full ($1,1,1$). """) disc(""" There are three ways to specify RGB colors in $pdfgen$: by name (using the $color$ module, by red/green/blue (additive, $RGB$) value, or by gray level. The $colors$ function below exercises each of the four methods. """) eg(examples.testRGBcolors) illust(examples.colorsRGB, "RGB Color Models") heading4("RGB Color Transparency") disc(""" Objects may be painted over other objects to good effect in $pdfgen$. Generally There are two modes of handling objects that overlap in space, the default objects in the top layer will hide any part of other objects that falls underneath it. If you need transparency you got two choices: """) disc(""" 1. If your document is intended to be printed in a professional way and you are working in CMYK color space then you can use overPrint. In overPrinting the colors physically mix in the printer and thus a new color is obtained. By default a knockout will be applied and only top object appears. Read the CMYK section if this is what you intend to use. """) disc(""" 2. If your document is intended for screen output and you are using RGB colors then you can set an alpha value, where alpha is the opacity value of the color. The default alpha value is $1$ (fully opaque) and you can use any real number value in the range 0-1. """) disc(""" Alpha transparency ($alpha$) is similar to overprint but works in RGB color space this example below demonstrates the alpha funtionality. Refer to our website http://www.reportlab.com/snippets/ and look for snippets of overPrint and alpha to see the code that generates the graph below. """) eg(examples.testalpha) illust(examples.alpha, "Alpha example") heading3("CMYK Colors") disc(""" The $CMYK$ or subtractive method follows the way a printer mixes three pigments (cyan, magenta, and yellow) to form colors. Because mixing chemicals is more difficult than combining light there is a fourth parameter for darkness. For example a chemical combination of the $CMY$ pigments generally never makes a perfect black -- instead producing a muddy color -- so, to get black printers don not use the $CMY$ pigments but use a direct black ink. Because $CMYK$ maps more directly to the way printer hardware works it may be the case that colors specified in $CMYK$ will provide better fidelity and better control when printed. """) disc(""" There are two ways of representing CMYK Color: each color can be represented either by a real value between 0 and 1, or integer value between 0 and 100. Depending on your preference you can either use CMYKColor (for real values) or PCMYKColor ( for integer values). 0 means 'no ink', so printing on white papers gives you white. 1 (or 100 if you use PCMYKColor) means 'the maximum amount of ink'. e.g. CMYKColor(0,0,0,1) is black, CMYKColor(0,0,0,0) means 'no ink', and CMYKColor(0.5,0,0,0) means 50 percent cyan color. """) eg(examples.testCMYKcolors) illust(examples.colorsCMYK, "CMYK Color Models") heading2("Color space checking") disc("""The $enforceColorSpace$ argument of the canvas is used to enforce the consistency of the colour model used in a document. It accepts these values: CMYK, RGB, SEP, SEP_BLACK, SEP_CMYK. 'SEP' refers to named color separations such as Pantone spot colors - these can be mixed with CMYK or RGB according to the parameter used. The default is 'MIXED' which allows you to use colors from any color space. An exception is raised if any colors used are not convertible to the specified model, e.g. rgb and cmyk (more information in test_pdfgen_general). This approach doesn't check external images included in document. """) heading2("Color Overprinting") disc(""" When two CMYK colored objects overlap in printing, then either the object 'on top' will knock out the color of the the one underneath it, or the colors of the two objects will mix in the overlapped area. This behaviour can be set using the property $overPrint$. """) disc(""" The $overPrint$ function will cause ovelapping areas of color to mix. In the example below, the colors of the rectangles on the left should appear mixed where they overlap - If you can't see this effect then you may need to enable the 'overprint preview' option in your PDF viewing software. Some PDF viewers such as $evince$ do not support overPrint; however Adobe Acrobat Reader does support it. """) illust(examples.overPrint, "overPrint example") heading3("Other Object Order of Printing Examples") disc(""" The word "SPUMONI" is painted in white over the colored rectangles, with the apparent effect of "removing" the color inside the body of the word. """) eg(examples.testspumoni) illust(examples.spumoni, "Painting over colors") disc(""" The last letters of the word are not visible because the default $canvas$ background is white and painting white letters over a white background leaves no visible effect. """) disc(""" This method of building up complex paintings in layers can be done in very many layers in $pdfgen$ -- there are fewer physical limitations than there are when dealing with physical paints. """) eg(examples.testspumoni2) disc(""" The $spumoni2$ function layers an ice cream cone over the $spumoni$ drawing. Note that different parts of the cone and scoops layer over eachother as well. """) illust(examples.spumoni2, "building up a drawing in layers") heading2('Standard fonts and text objects') disc(""" Text may be drawn in many different colors, fonts, and sizes in $pdfgen$. The $textsize$ function demonstrates how to change the color and font and size of text and how to place text on the page. """) eg(examples.testtextsize) disc(""" The $textsize$ function generates the following page. """) illust(examples.textsize, "text in different fonts and sizes") disc(""" A number of different fonts are always available in $pdfgen$. """) eg(examples.testfonts) disc(""" The $fonts$ function lists the fonts that are always available. These don't need to be stored in a PDF document, since they are guaranteed to be present in Acrobat Reader. """) illust(examples.fonts, "the 14 standard fonts") disc("""The Symbol and ZapfDingbats fonts cannot display properly because the required glyphs are not present in those fonts.""") disc(""" For information on how to use arbitrary fonts, see the next chapter. """) heading2("Text object methods") disc(""" For the dedicated presentation of text in a PDF document, use a text object. The text object interface provides detailed control of text layout parameters not available directly at the $canvas$ level. In addition, it results in smaller PDF that will render faster than many separate calls to the $drawString$ methods. """) eg("""textobject.setTextOrigin(x,y)""") eg("""textobject.setTextTransform(a,b,c,d,e,f)""") eg("""textobject.moveCursor(dx, dy) # from start of current LINE""") eg("""(x,y) = textobject.getCursor()""") eg("""x = textobject.getX(); y = textobject.getY()""") eg("""textobject.setFont(psfontname, size, leading = None)""") eg("""textobject.textOut(text)""") eg("""textobject.textLine(text='')""") eg("""textobject.textLines(stuff, trim=1)""") disc(""" The text object methods shown above relate to basic text geometry. """) disc(""" A text object maintains a text cursor which moves about the page when text is drawn. For example the $setTextOrigin$ places the cursor in a known position and the $textLine$ and $textLines$ methods move the text cursor down past the lines that have been missing. """) eg(examples.testcursormoves1) disc(""" The $cursormoves$ function relies on the automatic movement of the text cursor for placing text after the origin has been set. """) illust(examples.cursormoves1, "How the text cursor moves") disc(""" It is also possible to control the movement of the cursor more explicitly by using the $moveCursor$ method (which moves the cursor as an offset from the start of the current line NOT the current cursor, and which also has positive ^y^ offsets move down (in contrast to the normal geometry where positive ^y^ usually moves up. """) eg(examples.testcursormoves2) disc(""" Here the $textOut$ does not move the down a line in contrast to the $textLine$ function which does move down. """) illust(examples.cursormoves2, "How the text cursor moves again") heading3("Character Spacing") eg("""textobject.setCharSpace(charSpace)""") disc("""The $setCharSpace$ method adjusts one of the parameters of text -- the inter-character spacing.""") eg(examples.testcharspace) disc("""The $charspace$ function exercises various spacing settings. It produces the following page.""") illust(examples.charspace, "Adjusting inter-character spacing") heading3("Word Spacing") eg("""textobject.setWordSpace(wordSpace)""") disc("The $setWordSpace$ method adjusts the space between words.") eg(examples.testwordspace) disc("""The $wordspace$ function shows what various word space settings look like below.""") illust(examples.wordspace, "Adjusting word spacing") heading3("Horizontal Scaling") eg("""textobject.setHorizScale(horizScale)""") disc("""Lines of text can be stretched or shrunken horizontally by the $setHorizScale$ method.""") eg(examples.testhorizontalscale) disc("""The horizontal scaling parameter ^horizScale^ is given in percentages (with 100 as the default), so the 80 setting shown below looks skinny. """) illust(examples.horizontalscale, "adjusting horizontal text scaling") heading3("Interline spacing (Leading)") eg("""textobject.setLeading(leading)""") disc("""The vertical offset between the point at which one line starts and where the next starts is called the leading offset. The $setLeading$ method adjusts the leading offset. """) eg(examples.testleading) disc("""As shown below if the leading offset is set too small characters of one line my write over the bottom parts of characters in the previous line.""") illust(examples.leading, "adjusting the leading") heading3("Other text object methods") eg("""textobject.setTextRenderMode(mode)""") disc("""The $setTextRenderMode$ method allows text to be used as a forground for clipping background drawings, for example.""") eg("""textobject.setRise(rise)""") disc(""" The $setRise$ method raises or lowers text on the line (for creating superscripts or subscripts, for example). """) eg("""textobject.setFillColor(aColor); textobject.setStrokeColor(self, aColor) # and similar""") disc(""" These color change operations change the color of the text and are otherwise similar to the color methods for the $canvas$ object.""") heading2('Paths and Lines') disc("""Just as textobjects are designed for the dedicated presentation of text, path objects are designed for the dedicated construction of graphical figures. When path objects are drawn onto a $canvas$ they are drawn as one figure (like a rectangle) and the mode of drawing for the entire figure can be adjusted: the lines of the figure can be drawn (stroked) or not; the interior of the figure can be filled or not; and so forth.""") disc(""" For example the $star$ function uses a path object to draw a star """) eg(examples.teststar) disc(""" The $star$ function has been designed to be useful in illustrating various line style parameters supported by $pdfgen$. """) illust(examples.star, "line style parameters") heading3("Line join settings") disc(""" The $setLineJoin$ method can adjust whether line segments meet in a point a square or a rounded vertex. """) eg(examples.testjoins) disc(""" The line join setting is only really of interest for thick lines because it cannot be seen clearly for thin lines. """) illust(examples.joins, "different line join styles") heading3("Line cap settings") disc("""The line cap setting, adjusted using the $setLineCap$ method, determines whether a terminating line ends in a square exactly at the vertex, a square over the vertex or a half circle over the vertex. """) eg(examples.testcaps) disc("""The line cap setting, like the line join setting, is only clearly visible when the lines are thick.""") illust(examples.caps, "line cap settings") heading3("Dashes and broken lines") disc(""" The $setDash$ method allows lines to be broken into dots or dashes. """) eg(examples.testdashes) disc(""" The patterns for the dashes or dots can be in a simple on/off repeating pattern or they can be specified in a complex repeating pattern. """) illust(examples.dashes, "some dash patterns") heading3("Creating complex figures with path objects") disc(""" Combinations of lines, curves, arcs and other figures can be combined into a single figure using path objects. For example the function shown below constructs two path objects using lines and curves. This function will be used later on as part of a pencil icon construction. """) eg(examples.testpenciltip) disc(""" Note that the interior of the pencil tip is filled as one object even though it is constructed from several lines and curves. The pencil lead is then drawn over it using a new path object. """) illust(examples.penciltip, "a pencil tip") heading2('Rectangles, circles, ellipses') disc(""" The $pdfgen$ module supports a number of generally useful shapes such as rectangles, rounded rectangles, ellipses, and circles. Each of these figures can be used in path objects or can be drawn directly on a $canvas$. For example the $pencil$ function below draws a pencil icon using rectangles and rounded rectangles with various fill colors and a few other annotations. """) eg(examples.testpencil) pencilnote() disc(""" Note that this function is used to create the "margin pencil" to the left. Also note that the order in which the elements are drawn are important because, for example, the white rectangles "erase" parts of a black rectangle and the "tip" paints over part of the yellow rectangle. """) illust(examples.pencil, "a whole pencil") heading2('Bezier curves') disc(""" Programs that wish to construct figures with curving borders generally use Bezier curves to form the borders. """) eg(examples.testbezier) disc(""" A Bezier curve is specified by four control points $(x1,y1)$, $(x2,y2)$, $(x3,y3)$, $(x4,y4)$. The curve starts at $(x1,y1)$ and ends at $(x4,y4)$ and the line segment from $(x1,y1)$ to $(x2,y2)$ and the line segment from $(x3,y3)$ to $(x4,y4)$ both form tangents to the curve. Furthermore the curve is entirely contained in the convex figure with vertices at the control points. """) illust(examples.bezier, "basic bezier curves") disc(""" The drawing above (the output of $testbezier$) shows a bezier curves, the tangent lines defined by the control points and the convex figure with vertices at the control points. """) heading3("Smoothly joining bezier curve sequences") disc(""" It is often useful to join several bezier curves to form a single smooth curve. To construct a larger smooth curve from several bezier curves make sure that the tangent lines to adjacent bezier curves that join at a control point lie on the same line. """) eg(examples.testbezier2) disc(""" The figure created by $testbezier2$ describes a smooth complex curve because adjacent tangent lines "line up" as illustrated below. """) illust(examples.bezier2, "bezier curves") heading2("Path object methods") disc(""" Path objects build complex graphical figures by setting the "pen" or "brush" at a start point on the canvas and drawing lines or curves to additional points on the canvas. Most operations apply paint on the canvas starting at the end point of the last operation and leave the brush at a new end point. """) eg("""pathobject.moveTo(x,y)""") disc(""" The $moveTo$ method lifts the brush (ending any current sequence of lines or curves if there is one) and replaces the brush at the new ^(x,y)^ location on the canvas to start a new path sequence. """) eg("""pathobject.lineTo(x,y)""") disc(""" The $lineTo$ method paints straight line segment from the current brush location to the new ^(x,y)^ location. """) eg("""pathobject.curveTo(x1, y1, x2, y2, x3, y3) """) disc(""" The $curveTo$ method starts painting a Bezier curve beginning at the current brush location, using ^(x1,y1)^, ^(x2,y2)^, and ^(x3,y3)^ as the other three control points, leaving the brush on ^(x3,y3)^. """) eg("""pathobject.arc(x1,y1, x2,y2, startAng=0, extent=90) """) eg("""pathobject.arcTo(x1,y1, x2,y2, startAng=0, extent=90) """) disc(""" The $arc$ and $arcTo$ methods paint partial ellipses. The $arc$ method first "lifts the brush" and starts a new shape sequence. The $arcTo$ method joins the start of the partial ellipse to the current shape sequence by line segment before drawing the partial ellipse. The points ^(x1,y1)^ and ^(x2,y2)^ define opposite corner points of a rectangle enclosing the ellipse. The $startAng$ is an angle (in degrees) specifying where to begin the partial ellipse where the 0 angle is the midpoint of the right border of the enclosing rectangle (when ^(x1,y1)^ is the lower left corner and ^(x2,y2)^ is the upper right corner). The $extent$ is the angle in degrees to traverse on the ellipse. """) eg(examples.testarcs) disc("""The $arcs$ function above exercises the two partial ellipse methods. It produces the following drawing.""") illust(examples.arcs, "arcs in path objects") eg("""pathobject.rect(x, y, width, height) """) disc("""The $rect$ method draws a rectangle with lower left corner at ^(x,y)^ of the specified ^width^ and ^height^.""") eg("""pathobject.ellipse(x, y, width, height)""") disc("""The $ellipse$ method draws an ellipse enclosed in the rectange with lower left corner at ^(x,y)^ of the specified ^width^ and ^height^. """) eg("""pathobject.circle(x_cen, y_cen, r) """) disc("""The $circle$ method draws a circle centered at ^(x_cen, y_cen)^ with radius ^r^. """) eg(examples.testvariousshapes) disc(""" The $variousshapes$ function above shows a rectangle, circle and ellipse placed in a frame of reference grid. """) illust(examples.variousshapes, "rectangles, circles, ellipses in path objects") eg("""pathobject.close() """) disc(""" The $close$ method closes the current graphical figure by painting a line segment from the last point of the figure to the starting point of the figure (the the most recent point where the brush was placed on the paper by $moveTo$ or $arc$ or other placement operations). """) eg(examples.testclosingfigures) disc(""" The $closingfigures$ function illustrates the effect of closing or not closing figures including a line segment and a partial ellipse. """) illust(examples.closingfigures, "closing and not closing pathobject figures") disc(""" Closing or not closing graphical figures effects only the stroked outline of a figure, not the filling of the figure as illustrated above. """) disc(""" For a more extensive example of drawing using a path object examine the $hand$ function. """) eg(examples.testhand) disc(""" In debug mode (the default) the $hand$ function shows the tangent line segments to the bezier curves used to compose the figure. Note that where the segments line up the curves join smoothly, but where they do not line up the curves show a "sharp edge". """) illust(examples.hand, "an outline of a hand using bezier curves") disc(""" Used in non-debug mode the $hand$ function only shows the Bezier curves. With the $fill$ parameter set the figure is filled using the current fill color. """) eg(examples.testhand2) disc(""" Note that the "stroking" of the border draws over the interior fill where they overlap. """) illust(examples.hand2, "the finished hand, filled") heading2("Further Reading: The ReportLab Graphics Library") disc(""" So far the graphics we have seen were created on a fairly low level. It should be noted, though, that there is another way of creating much more sophisticated graphics using the dedicated high-level ReportLab Graphics Library. """) disc(""" It can be used to produce high-quality, platform-independant, reusable graphics for different output formats (vector and bitmap) like PDF, EPS, SVG, JPG and PNG. """) disc(""" A more thorough description of its philsophy and features is now covered in Chapter 11 of this document, Graphics, which contains information about the existing components and how to create customized ones. """) disc(""" Chapter 11 also contains details of the ReportLab charting package and its components (labels, axes, legends and different types of charts like bar, line and pie charts) that builds directly on the graphics library. """) ##### FILL THEM IN reportlab-3.3.0/docs/userguide/graph_intro.py0000664000175000017500000000075212661063723020667 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details __version__='3.3.0' from tools.docco.rl_doc_utils import * heading1("Graphics") heading2("Introduction") disc(""" ReportLab Graphics is one of the sub-packages to the ReportLab library. It started off as a stand-alone set of programs, but is now a fully integrated part of the ReportLab toolkit that allows you to use its powerful charting and graphics features to improve your PDF forms and reports. """) reportlab-3.3.0/docs/userguide/testfile.txt0000644000175000017500000000016012120332300020324 0ustar rptlabrptlabTest of ability to create new files in sourceforge CVS, which seems not to be working. Can be removed any time.reportlab-3.3.0/docs/userguide/ch5_paragraphs.py0000664000175000017500000004455312661063723021251 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch5_paragraphs.py from tools.docco.rl_doc_utils import * #begin chapter oon paragraphs heading1("Paragraphs") disc(""" The $reportlab.platypus.Paragraph$ class is one of the most useful of the Platypus $Flowables$; it can format fairly arbitrary text and provides for inline font style and colour changes using an XML style markup. The overall shape of the formatted text can be justified, right or left ragged or centered. The XML markup can even be used to insert greek characters or to do subscripts. """) disc("""The following text creates an instance of the $Paragraph$ class:""") eg("""Paragraph(text, style, bulletText=None)""") disc("""The $text$ argument contains the text of the paragraph; excess white space is removed from the text at the ends and internally after linefeeds. This allows easy use of indented triple quoted text in Python scripts. The $bulletText$ argument provides the text of a default bullet for the paragraph. The font and other properties for the paragraph text and bullet are set using the style argument. """) disc(""" The $style$ argument should be an instance of class $ParagraphStyle$ obtained typically using""") eg(""" from reportlab.lib.styles import ParagraphStyle """) disc(""" this container class provides for the setting of multiple default paragraph attributes in a structured way. The styles are arranged in a dictionary style object called a $stylesheet$ which allows for the styles to be accessed as $stylesheet['BodyText']$. A sample style sheet is provided. """) eg(""" from reportlab.lib.styles import getSampleStyleSheet stylesheet=getSampleStyleSheet() normalStyle = stylesheet['Normal'] """) disc(""" The options which can be set for a $Paragraph$ can be seen from the $ParagraphStyle$ defaults. """) heading4("$class ParagraphStyle$") eg(""" class ParagraphStyle(PropertySet): defaults = { 'fontName':'Times-Roman', 'fontSize':10, 'leading':12, 'leftIndent':0, 'rightIndent':0, 'firstLineIndent':0, 'alignment':TA_LEFT, 'spaceBefore':0, 'spaceAfter':0, 'bulletFontName':'Times-Roman', 'bulletFontSize':10, 'bulletIndent':0, 'textColor': black, 'backColor':None, 'wordWrap':None, 'borderWidth': 0, 'borderPadding': 0, 'borderColor': None, 'borderRadius': None, 'allowWidows': 1, 'allowOrphans': 0, 'textTransform':None, 'endDots':None, 'splitLongWords':1, 'underlineProportion': _baseUnderlineProportion, 'bulletAnchor': 'start', } """) heading2("Using Paragraph Styles") #this will be used in the ParaBox demos. sample = """You are hereby charged that on the 28th day of May, 1970, you did willfully, unlawfully, and with malice of forethought, publish an alleged English-Hungarian phrase book with intent to cause a breach of the peace. How do you plead?""" disc("""The $Paragraph$ and $ParagraphStyle$ classes together handle most common formatting needs. The following examples draw paragraphs in various styles, and add a bounding box so that you can see exactly what space is taken up.""") s1 = ParagraphStyle('Normal') parabox(sample, s1, 'The default $ParagraphStyle$') disc("""The two attributes $spaceBefore$ and $spaceAfter$ do what they say, except at the top or bottom of a frame. At the top of a frame, $spaceBefore$ is ignored, and at the bottom, $spaceAfter$ is ignored. This means that you could specify that a 'Heading2' style had two inches of space before when it occurs in mid-page, but will not get acres of whitespace at the top of a page. These two attributes should be thought of as 'requests' to the Frame and are not part of the space occupied by the Paragraph itself.""") disc("""The $fontSize$ and $fontName$ tags are obvious, but it is important to set the $leading$. This is the spacing between adjacent lines of text; a good rule of thumb is to make this 20% larger than the point size. To get double-spaced text, use a high $leading$. If you set $autoLeading$(default $"off"$) to $"min"$(use observed leading even if smaller than specified) or $"max"$(use the larger of observed and specified) then an attempt is made to determine the leading on a line by line basis. This may be useful if the lines contain different font sizes etc.""") disc("""The figure below shows space before and after and an increased leading:""") parabox(sample, ParagraphStyle('Spaced', spaceBefore=6, spaceAfter=6, leading=16), 'Space before and after and increased leading' ) disc("""The attribute $borderPadding$ adjusts the padding between the paragraph and the border of its background. This can either be a single value or a tuple containing 2 to 4 values. These values are applied the same way as in Cascading Style Sheets (CSS). If a single value is given, that value is applied to all four sides. If more than one value is given, they are applied in clockwise order to the sides starting at the top. If two or three values are given, the missing values are taken from the opposite side(s). Note that in the following example the yellow box is drawn by the paragraph itself.""") parabox(sample, ParagraphStyle('padded', borderPadding=(7, 2, 20), borderColor='#000000', borderWidth=1, backColor='#FFFF00'), 'Variable padding' ) disc("""The $leftIndent$ and $rightIndent$ attributes do exactly what you would expect; $firstLineIndent$ is added to the $leftIndent$ of the first line. If you want a straight left edge, remember to set $firstLineIndent$ equal to 0.""") parabox(sample, ParagraphStyle('indented', firstLineIndent=+24, leftIndent=24, rightIndent=24), 'one third inch indents at left and right, two thirds on first line' ) disc("""Setting $firstLineIndent$ equal to a negative number, $leftIndent$ much higher, and using a different font (we'll show you how later!) can give you a definition list:.""") parabox('Judge Pickles: ' + sample, ParagraphStyle('dl', leftIndent=36), 'Definition Lists' ) disc("""There are four possible values of $alignment$, defined as constants in the module reportlab.lib.enums. These are TA_LEFT, TA_CENTER or TA_CENTRE, TA_RIGHT and TA_JUSTIFY, with values of 0, 1, 2 and 4 respectively. These do exactly what you would expect.""") disc("""Set $wordWrap$ to $'CJK'$ to get Asian language linewrapping. For normal western text you can change the way the line breaking algorithm handles widows and orphans with the $allowWidows$ and $allowOrphans$ values. Both should normally be set to $0$, but for historical reasons we have allowed widows. The default color of the text can be set with $textColor$ and the paragraph background colour can be set with $backColor$. The paragraph's border properties may be changed using $borderWidth$, $borderPadding$, $borderColor$ and $borderRadius$.""") disc("""The $textTransform$ attribute can be None, 'upper' or 'lower' to get the obvious result.""") disc("""Attribute $endDots$ can be None, a string, or an object with attributes text and optional fontName, fontSize, textColor, backColor and dy(y offset) to specify trailing matter on the last line of left/right justified paragraphs.""") disc("""The $splitLongWords$ attribute can be set to a false value to avoid splitting very long words.""") disc("""The $underLineProportion$ attribute can be set to a true or false value to control whether underlines are proportional to the font size.""") disc("""Attribute $bulletAnchor$ can be 'start', 'middle', 'end' or 'numeric' to control where the bullet is anchored.""") heading2("Paragraph XML Markup Tags") disc("""XML markup can be used to modify or specify the overall paragraph style, and also to specify intra- paragraph markup.""") heading3("The outermost < para > tag") disc(""" The paragraph text may optionally be surrounded by <para attributes....> </para> tags. The attributes if any of the opening <para> tag affect the style that is used with the $Paragraph$ $text$ and/or $bulletText$. """) disc(" ") from reportlab.platypus.paraparser import _addAttributeNames, _paraAttrMap, _bulletAttrMap def getAttrs(A): _addAttributeNames(A) S={} for k, v in A.items(): a = v[0] if a not in S: S[a] = k else: S[a] = "%s, %s" %(S[a],k) K = list(S.keys()) K.sort() D=[('Attribute','Synonyms')] for k in K: D.append((k,S[k])) cols=2*[None] rows=len(D)*[None] return D,cols,rows t=Table(*getAttrs(_paraAttrMap)) t.setStyle(TableStyle([ ('FONT',(0,0),(-1,1),'Times-Bold',10,12), ('FONT',(0,1),(-1,-1),'Courier',8,8), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) getStory().append(t) caption("""Table - Synonyms for style attributes""") disc("""Some useful synonyms have been provided for our Python attribute names, including lowercase versions, and the equivalent properties from the HTML standard where they exist. These additions make it much easier to build XML-printing applications, since much intra-paragraph markup may not need translating. The table below shows the allowed attributes and synonyms in the outermost paragraph tag.""") heading2("Intra-paragraph markup") disc("""...), italic (...) and underline (...). Other tags which are allowed are strong (...), and strike through (...). The and tags may be used to refer to URIs, documents or bookmarks in the current document. The a variant of the tag can be used to mark a position in a document. A break (
) tag is also allowed.]]> """) parabox2("""You are hereby charged that on the 28th day of May, 1970, you did willfully, unlawfully, and with malice of forethought, publish an alleged English-Hungarian phrase book with intent to cause a breach of the peace. How do you plead?""", "Simple bold and italic tags") parabox2("""This
is a link to an anchor tag ie here. This is another link to the same anchor tag.""", "anchors and links") disc("""The link tag can be used as a reference, but not as an anchor. The a and link hyperlink tags have additional attributes fontName, fontSize, color & backColor attributes. The hyperlink reference can have a scheme of http:(external webpage), pdf:(different pdf document) or document:(same pdf document); a missing scheme is treated as document as is the case when the reference starts with # (in which case the anchor should omit it). Any other scheme is treated as some kind of URI. """) parabox2("""You are hereby charged that on the 28th day of May, 1970, you did willfully, unlawfully, and with malice of forethought,
publish an alleged English-Hungarian phrase book with intent to cause a breach of the peace. How do you plead?""", "Strong, strike, and break tags") heading3("The $<font>$ tag") disc("""The $<font>$ tag can be used to change the font name, size and text color for any substring within the paragraph. Legal attributes are $size$, $face$, $name$ (which is the same as $face$), $color$, and $fg$ (which is the same as $color$). The $name$ is the font family name, without any 'bold' or 'italic' suffixes. Colors may be HTML color names or a hex string encoded in a variety of ways; see ^reportlab.lib.colors^ for the formats allowed.""") parabox2("""You are hereby charged that on the 28th day of May, 1970, you did willfully, unlawfully, and with malice of forethought, publish an alleged English-Hungarian phrase book with intent to cause a breach of the peace. How do you plead?""", "The $font$ tag") heading3("Superscripts and Subscripts") disc("""Superscripts and subscripts are supported with the / and tags, which work exactly as you might expect. Additionally these three tags have attributes rise and size to optionally set the rise/descent and font size for the superscript/subscript text. In addition, most greek letters can be accessed by using the tag, or with mathML entity names.]]>""") ##parabox2("""epsiloniota ##pi = -1""", "Greek letters and subscripts") parabox2("""Equation (α): e ip = -1""", "Greek letters and superscripts") heading3("Inline Images") disc("""We can embed images in a paragraph with the <img/> tag which has attributes $src$, $width$, $height$ whose meanings are obvious. The $valign$ attribute may be set to a css like value from "baseline", "sub", "super", "top", "text-top", "middle", "bottom", "text-bottom"; the value may also be a numeric percentage or an absolute value. """) parabox2("""This <img/> is aligned top.

This <img/> is aligned bottom.

This <img/> is aligned middle.

This <img/> is aligned -4.

This <img/> is aligned +4.

This <img/> has width 10.

""","Inline images") heading3("Numbering Paragraphs and Lists") disc("""The $<seq>$ tag provides comprehensive support for numbering lists, chapter headings and so on. It acts as an interface to the $Sequencer$ class in ^reportlab.lib.sequencer^. These are used to number headings and figures throughout this document. You may create as many separate 'counters' as you wish, accessed with the $id$ attribute; these will be incremented by one each time they are accessed. The $seqreset$ tag resets a counter. If you want it to resume from a number other than 1, use the syntax <seqreset id="mycounter" base="42">. Let's have a go:""") parabox2(""", , . Reset. , , .""", "Basic sequences") disc("""You can save specifying an ID by designating a counter ID as the default using the <seqdefault id="Counter"> tag; it will then be used whenever a counter ID is not specified. This saves some typing, especially when doing multi-level lists; you just change counter ID when stepping in or out a level.""") parabox2("""Continued... , , , , , , .""", "The default sequence") disc("""Finally, one can access multi-level sequences using a variation of Python string formatting and the $template$ attribute in a <seq> tags. This is used to do the captions in all of the figures, as well as the level two headings. The substring $%(counter)s$ extracts the current value of a counter without incrementing it; appending a plus sign as in $%(counter)s$ increments the counter. The figure captions use a pattern like the one below:""") parabox2("""Figure - Multi-level templates""", "Multi-level templates") disc("""We cheated a little - the real document used 'Figure', but the text above uses 'FigureNo' - otherwise we would have messed up our numbering!""") heading2("Bullets and Paragraph Numbering") disc("""In addition to the three indent properties, some other parameters are needed to correctly handle bulleted and numbered lists. We discuss this here because you have now seen how to handle numbering. A paragraph may have an optional ^bulletText^ argument passed to its constructor; alternatively, bullet text may be placed in a $..]]>$ tag at its head. This text will be drawn on the first line of the paragraph, with its x origin determined by the $bulletIndent$ attribute of the style, and in the font given in the $bulletFontName$ attribute. The "bullet" may be a single character such as (doh!) a bullet, or a fragment of text such as a number in some numbering sequence, or even a short title as used in a definition list. Fonts may offer various bullet characters but we suggest first trying the Unicode bullet ($•$), which may be written as $&bull;$, $&#x2022;$ or (in utf8) $\\xe2\\x80\\xa2$):""") t=Table(*getAttrs(_bulletAttrMap)) t.setStyle([ ('FONT',(0,0),(-1,1),'Times-Bold',10,12), ('FONT',(0,1),(-1,-1),'Courier',8,8), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ]) getStory().append(t) caption("""Table - <bullet> attributes & synonyms""") disc("""The <bullet> tag is only allowed once in a given paragraph and its use overrides the implied bullet style and ^bulletText^ specified in the ^Paragraph^ creation. """) parabox("""\u2022this is a bullet point. Spam spam spam spam spam spam spam spam spam spam spam spam spam spam spam spam spam spam spam spam spam spam """, styleSheet['Bullet'], 'Basic use of bullet points') disc("""Exactly the same technique is used for numbers, except that a sequence tag is used. It is also possible to put a multi-character string in the bullet; with a deep indent and bold bullet font, you can make a compact definition list.""") reportlab-3.3.0/docs/userguide/ch3_pdffeatures.py0000664000175000017500000003660712661063723021430 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch3_pdffeatures.py from tools.docco.rl_doc_utils import * heading1("Exposing PDF Special Capabilities") disc("""PDF provides a number of features to make electronic document viewing more efficient and comfortable, and our library exposes a number of these.""") heading2("Forms") disc("""The Form feature lets you create a block of graphics and text once near the start of a PDF file, and then simply refer to it on subsequent pages. If you are dealing with a run of 5000 repetitive business forms - for example, one-page invoices or payslips - you only need to store the backdrop once and simply draw the changing text on each page. Used correctly, forms can dramatically cut file size and production time, and apparently even speed things up on the printer. """) disc("""Forms do not need to refer to a whole page; anything which might be repeated often should be placed in a form.""") disc("""The example below shows the basic sequence used. A real program would probably define the forms up front and refer to them from another location.""") eg(examples.testforms) heading2("Links and Destinations") disc("""PDF supports internal hyperlinks. There is a very wide range of link types, destination types and events which can be triggered by a click. At the moment we just support the basic ability to jump from one part of a document to another, and to control the zoom level of the window after the jump. The bookmarkPage method defines a destination that is the endpoint of a jump.""") #todo("code example here...") eg(""" canvas.bookmarkPage(name, fit="Fit", left=None, top=None, bottom=None, right=None, zoom=None ) """) disc(""" By default the $bookmarkPage$ method defines the page itself as the destination. After jumping to an endpoint defined by bookmarkPage, the PDF browser will display the whole page, scaling it to fit the screen:""") eg("""canvas.bookmarkPage(name)""") disc("""The $bookmarkPage$ method can be instructed to display the page in a number of different ways by providing a $fit$ parameter.""") eg("") t = Table([ ['fit','Parameters Required','Meaning'], ['Fit',None,'Entire page fits in window (the default)'], ['FitH','top','Top coord at top of window, width scaled to fit'], ['FitV','left','Left coord at left of window, height scaled to fit'], ['FitR','left bottom right top','Scale window to fit the specified rectangle'], ['XYZ','left top zoom','Fine grained control. If you omit a parameter\nthe PDF browser interprets it as "leave as is"'] ]) t.setStyle(TableStyle([ ('FONT',(0,0),(-1,1),'Times-Bold',10,12), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) getStory().append(t) caption("""Table - Required attributes for different fit types""") disc(""" Note : $fit$ settings are case-sensitive so $fit="FIT"$ is invalid """) disc(""" Sometimes you want the destination of a jump to be some part of a page. The $FitR$ fit allows you to identify a particular rectangle, scaling the area to fit the entire page. """) disc(""" To set the display to a particular x and y coordinate of the page and to control the zoom directly use fit="XYZ". """) eg(""" canvas.bookmarkPage('my_bookmark',fit="XYZ",left=0,top=200) """) disc(""" This destination is at the leftmost of the page with the top of the screen at position 200. Because $zoom$ was not set the zoom remains at whatever the user had it set to. """) eg(""" canvas.bookmarkPage('my_bookmark',fit="XYZ",left=0,top=200,zoom=2) """) disc("""This time zoom is set to expand the page 2X its normal size.""") disc(""" Note : Both $XYZ$ and $FitR$ fit types require that their positional parameters ($top, bottom, left, right$) be specified in terms of the default user space. They ignore any geometric transform in effect in the canvas graphic state. """) pencilnote() disc(""" Note: Two previous bookmark methods are supported but deprecated now that bookmarkPage is so general. These are $bookmarkHorizontalAbsolute$ and $bookmarkHorizontal$. """) heading3("Defining internal links") eg(""" canvas.linkAbsolute(contents, destinationname, Rect=None, addtopage=1, name=None, thickness=0, color=None, dashArray=None, **kw) """) disc(""" The $linkAbsolute$ method defines a starting point for a jump. When the user is browsing the generated document using a dynamic viewer (such as Acrobat Reader) when the mouse is clicked when the pointer is within the rectangle specified by $Rect$ the viewer will jump to the endpoint associated with $destinationname$. As in the case with $bookmarkHorizontalAbsolute$ the rectangle $Rect$ must be specified in terms of the default user space. The $contents$ parameter specifies a chunk of text which displays in the viewer if the user left-clicks on the region. """) disc(""" The rectangle $Rect$ must be specified in terms of a tuple ^(x1,y1,x2,y2)^ identifying the lower left and upper right points of the rectangle in default user space. """) disc(""" For example the code """) eg(""" canvas.bookmarkPage("Meaning_of_life") """) disc(""" defines a location as the whole of the current page with the identifier $Meaning_of_life$. To create a rectangular link to it while drawing a possibly different page, we would use this code: """) eg(""" canvas.linkAbsolute("Find the Meaning of Life", "Meaning_of_life", (inch, inch, 6*inch, 2*inch)) """) disc(""" By default during interactive viewing a rectangle appears around the link. Use the keyword argument $Border='[0 0 0]'$ to suppress the visible rectangle around the during viewing link. For example """) eg(""" canvas.linkAbsolute("Meaning of Life", "Meaning_of_life", (inch, inch, 6*inch, 2*inch), Border='[0 0 0]') """) disc("""The $thickness$, $color$ and $dashArray$ arguments may be used alternately to specify a border if no Border argument is specified. If Border is specified it must be either a string representation of a PDF array or a $PDFArray$ (see the pdfdoc module). The $color$ argument (which should be a $Color$ instance) is equivalent to a keyword argument $C$ which should resolve to a PDF color definition (Normally a three entry PDF array). """) disc("""The $canvas.linkRect$ method is similar in intent to the $linkAbsolute$ method, but has an extra argument $relative=1$ so is intended to obey the local userspace transformation.""") heading2("Outline Trees") disc("""Acrobat Reader has a navigation page which can hold a document outline; it should normally be visible when you open this guide. We provide some simple methods to add outline entries. Typically, a program to make a document (such as this user guide) will call the method $canvas.addOutlineEntry(^self, title, key, level=0, closed=None^)$ as it reaches each heading in the document. """) disc("""^title^ is the caption which will be displayed in the left pane. The ^key^ must be a string which is unique within the document and which names a bookmark, as with the hyperlinks. The ^level^ is zero - the uppermost level - unless otherwise specified, and it is an error to go down more than one level at a time (for example to follow a level 0 heading by a level 2 heading). Finally, the ^closed^ argument specifies whether the node in the outline pane is closed or opened by default.""") disc("""The snippet below is taken from the document template that formats this user guide. A central processor looks at each paragraph in turn, and makes a new outline entry when a new chapter occurs, taking the chapter heading text as the caption text. The key is obtained from the chapter number (not shown here), so Chapter 2 has the key 'ch2'. The bookmark to which the outline entry points aims at the whole page, but it could as easily have been an individual paragraph. """) eg(""" #abridged code from our document template if paragraph.style == 'Heading1': self.chapter = paragraph.getPlainText() key = 'ch%d' % self.chapterNo self.canv.bookmarkPage(key) self.canv.addOutlineEntry(paragraph.getPlainText(), key, 0, 0) """) heading2("Page Transition Effects") eg(""" canvas.setPageTransition(self, effectname=None, duration=1, direction=0,dimension='H',motion='I') """) disc(""" The $setPageTransition$ method specifies how one page will be replaced with the next. By setting the page transition effect to "dissolve" for example the current page will appear to melt away when it is replaced by the next page during interactive viewing. These effects are useful in spicing up slide presentations, among other places. Please see the reference manual for more detail on how to use this method. """) heading2("Internal File Annotations") eg(""" canvas.setAuthor(name) canvas.setTitle(title) canvas.setSubject(subj) """) disc(""" These methods have no automatically seen visible effect on the document. They add internal annotations to the document. These annotations can be viewed using the "Document Info" menu item of the browser and they also can be used as a simple standard way of providing basic information about the document to archiving software which need not parse the entire file. To find the annotations view the $*.pdf$ output file using a standard text editor (such as $notepad$ on MS/Windows or $vi$ or $emacs$ on unix) and look for the string $/Author$ in the file contents. """) eg(examples.testannotations) disc(""" If you want the subject, title, and author to automatically display in the document when viewed and printed you must paint them onto the document like any other text. """) illust(examples.annotations, "Setting document internal annotations") heading2("Encryption") heading3("About encrypting PDF files") disc(""" Adobe's PDF standard allows you to do three related things to a PDF file when you encrypt it: """) bullet("""Apply password protection to it, so a user must supply a valid password before being able to read it, """) bullet("""Encrypt the contents of the file to make it useless until it is decrypted, and """) bullet("""Control whether the user can print, copy and paste or modify the document while viewing it. """) disc(""" The PDF security handler allows two different passwords to be specified for a document: """) bullet("""The 'owner' password (aka the 'security password' or 'master password') """) bullet("""The 'user' password (aka the 'open password') """) disc(""" When a user supplies either one of these passwords, the PDF file will be opened, decrypted and displayed on screen. """) disc(""" If the owner password is supplied, then the file is opened with full control - you can do anything to it, including changing the security settings and passwords, or re-encrypting it with a new password. """) disc(""" If the user password was the one that was supplied, you open it up in a more restricted mode. The restrictions were put in place when the file was encrypted, and will either allow or deny the user permission to do the following: """) bullet(""" Modifying the document's contents """) bullet(""" Copying text and graphics from the document """) bullet(""" Adding or modifying text annotations and interactive form fields """) bullet(""" Printing the document """) disc(""" Note that all password protected PDF files are encrypted, but not all encrypted PDFs are password protected. If a document's user password is an empty string, there will be no prompt for the password when the file is opened. If you only secure a document with the owner password, there will also not be a prompt for the password when you open the file. If the owner and user passwords are set to the same string when encrypting the PDF file, the document will always open with the user access privileges. This means that it is possible to create a file which, for example, is impossible for anyone to print out, even the person who created it. """) t = Table([ ['Owner Password \nset?','User Password \nset?','Result'], ['Y','-','No password required when opening file. \nRestrictions apply to everyone.'], ['-','Y','User password required when opening file. \nRestrictions apply to everyone.'], ['Y','Y','A password required when opening file. \nRestrictions apply only if user password supplied.'], ],[90, 90, 260]) t.setStyle(TableStyle([ ('FONT',(0,0),(-1,0),'Times-Bold',10,12), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) getStory().append(t) disc(""" When a PDF file is encrypted, encryption is applied to all the strings and streams in the file. This prevents people who don't have the password from simply removing the password from the PDF file to gain access to it - it renders the file useless unless you actually have the password. """) disc(""" PDF's standard encryption methods use the MD5 message digest algorithm (as described in RFC 1321, The MD5 Message-Digest Algorithm) and an encryption algorithm known as RC4. RC4 is a symmetric stream cipher - the same algorithm is used both for encryption and decryption, and the algorithm does not change the length of the data. """) heading3("How To Use Encryption") disc(""" Documents can be encrypted by passing an argument to the canvas object. """) disc(""" If the argument is a string object, it is used as the User password to the PDF. """) disc(""" The argument can also be an instance of the class $reportlab.lib.pdfencrypt.StandardEncryption$, which allows more finegrained control over encryption settings. """) disc(""" The $StandardEncryption$ constructor takes the following arguments: """) eg(""" def __init__(self, userPassword, ownerPassword=None, canPrint=1, canModify=1, canCopy=1, canAnnotate=1, strength=40): """) disc(""" The $userPassword$ and $ownerPassword$ parameters set the relevant password on the encrypted PDF. """) disc(""" The boolean flags $canPrint$, $canModify$, $canCopy$, $canAnnotate$ determine wether a user can perform the corresponding actions on the PDF when only a user password has been supplied. """) disc(""" If the user supplies the owner password while opening the PDF, all actions can be performed regardless of the flags. """) heading3("Example") disc(""" To create a document named hello.pdf with a user password of 'rptlab' on which printing is not allowed, use the following code: """) eg(""" from reportlab.pdfgen import canvas from reportlab.lib import pdfencrypt enc=pdfencrypt.StandardEncryption("rptlab",canPrint=0) def hello(c): c.drawString(100,100,"Hello World") c = canvas.Canvas("hello.pdf",encrypt=enc) hello(c) c.showPage() c.save() """)reportlab-3.3.0/docs/userguide/graph_shapes.py0000664000175000017500000003016312661063723021016 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details __version__='3.3.0' from tools.docco.rl_doc_utils import * from reportlab.graphics.shapes import * heading2("Shapes") disc(""" This section describes the concept of shapes and their importance as building blocks for all output generated by the graphics library. Some properties of existing shapes and their relationship to diagrams are presented and the notion of having different renderers for different output formats is briefly introduced. """) heading3("Available Shapes") disc(""" Drawings are made up of Shapes. Absolutely anything can be built up by combining the same set of primitive shapes. The module $shapes.py$ supplies a number of primitive shapes and constructs which can be added to a drawing. They are: """) bullet("Rect") bullet("Circle") bullet("Ellipse") bullet("Wedge (a pie slice)") bullet("Polygon") bullet("Line") bullet("PolyLine") bullet("String") bullet("Group") bullet("Path (not implemented yet, but will be added in the future)") disc(""" The following drawing, taken from our test suite, shows most of the basic shapes (except for groups). Those with a filled green surface are also called solid shapes (these are $Rect$, $Circle$, $Ellipse$, $Wedge$ and $Polygon$). """) from reportlab.graphics import testshapes t = testshapes.getDrawing06() draw(t, "Basic shapes") heading3("Shape Properties") disc(""" Shapes have two kinds of properties - some to define their geometry and some to define their style. Let's create a red rectangle with 3-point thick green borders: """) eg(""" >>> from reportlab.graphics.shapes import Rect >>> from reportlab.lib.colors import red, green >>> r = Rect(5, 5, 200, 100) >>> r.fillColor = red >>> r.strokeColor = green >>> r.strokeWidth = 3 >>> """) from reportlab.graphics.shapes import Rect from reportlab.lib.colors import red, green d = Drawing(220, 120) r = Rect(5, 5, 200, 100) r.fillColor = red r.strokeColor = green r.strokeWidth = 3 d.add(r) draw(d, "red rectangle with green border") disc(""" Note: In future examples we will omit the import statements. """) disc(""" All shapes have a number of properties which can be set. At an interactive prompt, we can use their dumpProperties() method to list these. Here's what you can use to configure a Rect: """) eg(""" >>> r.dumpProperties() fillColor = Color(1.00,0.00,0.00) height = 100 rx = 0 ry = 0 strokeColor = Color(0.00,0.50,0.00) strokeDashArray = None strokeLineCap = 0 strokeLineJoin = 0 strokeMiterLimit = 0 strokeWidth = 3 width = 200 x = 5 y = 5 >>> """) disc(""" Shapes generally have style properties and geometry properties. $x$, $y$, $width$ and $height$ are part of the geometry and must be provided when creating the rectangle, since it does not make much sense without those properties. The others are optional and come with sensible defaults. """) disc(""" You may set other properties on subsequent lines, or by passing them as optional arguments to the constructor. We could also have created our rectangle this way: """) eg(""" >>> r = Rect(5, 5, 200, 100, fillColor=red, strokeColor=green, strokeWidth=3) """) disc(""" Let's run through the style properties. $fillColor$ is obvious. $stroke$ is publishing terminology for the edge of a shape; the stroke has a color, width, possibly a dash pattern, and some (rarely used) features for what happens when a line turns a corner. $rx$ and $ry$ are optional geometric properties and are used to define the corner radius for a rounded rectangle. """) disc("All the other solid shapes share the same style properties.") heading3("Lines") disc(""" We provide single straight lines, PolyLines and curves. Lines have all the $stroke*$ properties, but no $fillColor$. Here are a few Line and PolyLine examples and the corresponding graphics output: """) eg(""" Line(50,50, 300,100, strokeColor=colors.blue, strokeWidth=5) Line(50,100, 300,50, strokeColor=colors.red, strokeWidth=10, strokeDashArray=[10, 20]) PolyLine([120,110, 130,150, 140,110, 150,150, 160,110, 170,150, 180,110, 190,150, 200,110], strokeWidth=2, strokeColor=colors.purple) """) d = Drawing(400, 200) d.add(Line(50,50, 300,100,strokeColor=colors.blue, strokeWidth=5)) d.add(Line(50,100, 300,50, strokeColor=colors.red, strokeWidth=10, strokeDashArray=[10, 20])) d.add(PolyLine([120,110, 130,150, 140,110, 150,150, 160,110, 170,150, 180,110, 190,150, 200,110], strokeWidth=2, strokeColor=colors.purple)) draw(d, "Line and PolyLine examples") heading3("Strings") disc(""" The ReportLab Graphics package is not designed for fancy text layout, but it can place strings at desired locations and with left/right/center alignment. Let's specify a $String$ object and look at its properties: """) eg(""" >>> s = String(10, 50, 'Hello World') >>> s.dumpProperties() fillColor = Color(0.00,0.00,0.00) fontName = Times-Roman fontSize = 10 text = Hello World textAnchor = start x = 10 y = 50 >>> """) disc(""" Strings have a textAnchor property, which may have one of the values 'start', 'middle', 'end'. If this is set to 'start', x and y relate to the start of the string, and so on. This provides an easy way to align text. """) disc(""" Strings use a common font standard: the Type 1 Postscript fonts present in Acrobat Reader. We can thus use the basic 14 fonts in ReportLab and get accurate metrics for them. We have recently also added support for extra Type 1 fonts and the renderers all know how to render Type 1 fonts. """) ##Until now we have worked with bitmap renderers which have to use ##TrueType fonts and which make some substitutions; this could lead ##to differences in text wrapping or even the number of labels on ##a chart between renderers. disc(""" Here is a more fancy example using the code snippet below. Please consult the ReportLab User Guide to see how non-standard like 'DarkGardenMK' fonts are being registered! """) eg(""" d = Drawing(400, 200) for size in range(12, 36, 4): d.add(String(10+size*2, 10+size*2, 'Hello World', fontName='Times-Roman', fontSize=size)) d.add(String(130, 120, 'Hello World', fontName='Courier', fontSize=36)) d.add(String(150, 160, 'Hello World', fontName='DarkGardenMK', fontSize=36)) """) from reportlab.pdfbase import pdfmetrics from reportlab import rl_config rl_config.warnOnMissingFontGlyphs = 0 afmFile, pfbFile = getJustFontPaths() T1face = pdfmetrics.EmbeddedType1Face(afmFile, pfbFile) T1faceName = 'DarkGardenMK' pdfmetrics.registerTypeFace(T1face) T1font = pdfmetrics.Font(T1faceName, T1faceName, 'WinAnsiEncoding') pdfmetrics.registerFont(T1font) d = Drawing(400, 200) for size in range(12, 36, 4): d.add(String(10+size*2, 10+size*2, 'Hello World', fontName='Times-Roman', fontSize=size)) d.add(String(130, 120, 'Hello World', fontName='Courier', fontSize=36)) d.add(String(150, 160, 'Hello World', fontName='DarkGardenMK', fontSize=36)) draw(d, 'fancy font example') heading3("""Paths""") disc(""" Postscript paths are a widely understood concept in graphics. They are not implemented in $reportlab/graphics$ as yet, but they will be, soon. """) # NB This commented out section is for 'future compatibility' - paths haven't # been implemented yet, but when they are we can uncomment this back in. ##disc("""Postscript paths are a widely understood concept in graphics. A Path ## is a way of defining a region in space. You put an imaginary pen down, ## draw straight and curved segments, and even pick the pen up and move ## it. At the end of this you have described a region, which may consist ## of several distinct close shapes or unclosed lines. At the end, this ## 'path' is 'stroked and filled' according to its properties. A Path has ## the same style properties as a solid shape. It can be used to create ## any irregular shape.""") ## ##disc("""In Postscript-based imaging models such as PDF, Postscript and SVG, ## everything is done with paths. All the specific shapes covered above ## are instances of paths; even text strings (which are shapes in which ## each character is an outline to be filled). Here we begin creating a ## path with a straight line and a bezier curve:""") ## ##eg(""" ##>>> P = Path(0,0, strokeWidth=3, strokeColor=red) ##>>> P.lineTo(0, 50) ##>>> P.curveTo(10,50,80,80,100,30) ##>>> ##""") ##disc("""As well as being the only way to draw complex shapes, paths offer some ## performance advantages in renderers which support them. If you want to ## create a scatter plot with 5000 blue circles of different sizes, you ## can create 5000 circles, or one path object. With the latter, you only ## need to set the color and line width once. PINGO just remembers the ## drawing sequence, and writes it out into the file. In renderers which ## do not support paths, the renderer will still have to decompose it ## into 5000 circles so you won't save anything.""") ## ##disc("""Note that our current path implementation is an approximation; it ## should be finished off accurately for PDF and PS.""") heading3("Groups") disc(""" Finally, we have Group objects. A group has a list of contents, which are other nodes. It can also apply a transformation - its contents can be rotated, scaled or shifted. If you know the math, you can set the transform directly. Otherwise it provides methods to rotate, scale and so on. Here we make a group which is rotated and translated: """) eg(""" >>> g =Group(shape1, shape2, shape3) >>> g.rotate(30) >>> g.translate(50, 200) """) disc(""" Groups provide a tool for reuse. You can make a bunch of shapes to represent some component - say, a coordinate system - and put them in one group called "Axis". You can then put that group into other groups, each with a different translation and rotation, and you get a bunch of axis. It is still the same group, being drawn in different places. """) disc("""Let's do this with some only slightly more code:""") eg(""" d = Drawing(400, 200) Axis = Group( Line(0,0,100,0), # x axis Line(0,0,0,50), # y axis Line(0,10,10,10), # ticks on y axis Line(0,20,10,20), Line(0,30,10,30), Line(0,40,10,40), Line(10,0,10,10), # ticks on x axis Line(20,0,20,10), Line(30,0,30,10), Line(40,0,40,10), Line(50,0,50,10), Line(60,0,60,10), Line(70,0,70,10), Line(80,0,80,10), Line(90,0,90,10), String(20, 35, 'Axes', fill=colors.black) ) firstAxisGroup = Group(Axis) firstAxisGroup.translate(10,10) d.add(firstAxisGroup) secondAxisGroup = Group(Axis) secondAxisGroup.translate(150,10) secondAxisGroup.rotate(15) d.add(secondAxisGroup) thirdAxisGroup = Group(Axis, transform=mmult(translate(300,10), rotate(30))) d.add(thirdAxisGroup) """) d = Drawing(400, 200) Axis = Group( Line(0,0,100,0), # x axis Line(0,0,0,50), # y axis Line(0,10,10,10), # ticks on y axis Line(0,20,10,20), Line(0,30,10,30), Line(0,40,10,40), Line(10,0,10,10), # ticks on x axis Line(20,0,20,10), Line(30,0,30,10), Line(40,0,40,10), Line(50,0,50,10), Line(60,0,60,10), Line(70,0,70,10), Line(80,0,80,10), Line(90,0,90,10), String(20, 35, 'Axes', fill=colors.black) ) firstAxisGroup = Group(Axis) firstAxisGroup.translate(10,10) d.add(firstAxisGroup) secondAxisGroup = Group(Axis) secondAxisGroup.translate(150,10) secondAxisGroup.rotate(15) d.add(secondAxisGroup) thirdAxisGroup = Group(Axis, transform=mmult(translate(300,10), rotate(30))) d.add(thirdAxisGroup) draw(d, "Groups examples") reportlab-3.3.0/docs/userguide/graph_widgets.py0000664000175000017500000003232212661063723021200 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details __version__='3.3.0' from tools.docco.rl_doc_utils import * from reportlab.graphics.shapes import * from reportlab.graphics.widgets import signsandsymbols heading2("Widgets") disc(""" We now describe widgets and how they relate to shapes. Using many examples it is shown how widgets make reusable graphics components. """) heading3("Shapes vs. Widgets") disc("""Up until now, Drawings have been 'pure data'. There is no code in them to actually do anything, except assist the programmer in checking and inspecting the drawing. In fact, that's the cornerstone of the whole concept and is what lets us achieve portability - a renderer only needs to implement the primitive shapes.""") disc("""We want to build reusable graphic objects, including a powerful chart library. To do this we need to reuse more tangible things than rectangles and circles. We should be able to write objects for other to reuse - arrows, gears, text boxes, UML diagram nodes, even fully fledged charts.""") disc(""" The Widget standard is a standard built on top of the shapes module. Anyone can write new widgets, and we can build up libraries of them. Widgets support the $getProperties()$ and $setProperties()$ methods, so you can inspect and modify as well as document them in a uniform way. """) bullet("A widget is a reusable shape ") bullet("""it can be initialized with no arguments when its $draw()$ method is called it creates a primitive Shape or a Group to represent itself""") bullet("""It can have any parameters you want, and they can drive the way it is drawn""") bullet("""it has a $demo()$ method which should return an attractively drawn example of itself in a 200x100 rectangle. This is the cornerstone of the automatic documentation tools. The $demo()$ method should also have a well written docstring, since that is printed too!""") disc("""Widgets run contrary to the idea that a drawing is just a bundle of shapes; surely they have their own code? The way they work is that a widget can convert itself to a group of primitive shapes. If some of its components are themselves widgets, they will get converted too. This happens automatically during rendering; the renderer will not see your chart widget, but just a collection of rectangles, lines and strings. You can also explicitly 'flatten out' a drawing, causing all widgets to be converted to primitives.""") heading3("Using a Widget") disc(""" Let's imagine a simple new widget. We will use a widget to draw a face, then show how it was implemented.""") eg(""" >>> from reportlab.lib import colors >>> from reportlab.graphics import shapes >>> from reportlab.graphics import widgetbase >>> from reportlab.graphics import renderPDF >>> d = shapes.Drawing(200, 100) >>> f = widgetbase.Face() >>> f.skinColor = colors.yellow >>> f.mood = "sad" >>> d.add(f) >>> renderPDF.drawToFile(d, 'face.pdf', 'A Face') """) from reportlab.graphics import widgetbase d = Drawing(200, 120) f = widgetbase.Face() f.x = 50 f.y = 10 f.skinColor = colors.yellow f.mood = "sad" d.add(f) draw(d, 'A sample widget') disc(""" Let's see what properties it has available, using the $setProperties()$ method we have seen earlier: """) eg(""" >>> f.dumpProperties() eyeColor = Color(0.00,0.00,1.00) mood = sad size = 80 skinColor = Color(1.00,1.00,0.00) x = 10 y = 10 >>> """) disc(""" One thing which seems strange about the above code is that we did not set the size or position when we made the face. This is a necessary trade-off to allow a uniform interface for constructing widgets and documenting them - they cannot require arguments in their $__init__()$ method. Instead, they are generally designed to fit in a 200 x 100 window, and you move or resize them by setting properties such as x, y, width and so on after creation. """) disc(""" In addition, a widget always provides a $demo()$ method. Simple ones like this always do something sensible before setting properties, but more complex ones like a chart would not have any data to plot. The documentation tool calls $demo()$ so that your fancy new chart class can create a drawing showing what it can do. """) disc(""" Here are a handful of simple widgets available in the module signsandsymbols.py: """) from reportlab.graphics.shapes import Drawing from reportlab.graphics.widgets import signsandsymbols d = Drawing(230, 230) ne = signsandsymbols.NoEntry() ds = signsandsymbols.DangerSign() fd = signsandsymbols.FloppyDisk() ns = signsandsymbols.NoSmoking() ne.x, ne.y = 10, 10 ds.x, ds.y = 120, 10 fd.x, fd.y = 10, 120 ns.x, ns.y = 120, 120 d.add(ne) d.add(ds) d.add(fd) d.add(ns) draw(d, 'A few samples from signsandsymbols.py') disc(""" And this is the code needed to generate them as seen in the drawing above: """) eg(""" from reportlab.graphics.shapes import Drawing from reportlab.graphics.widgets import signsandsymbols d = Drawing(230, 230) ne = signsandsymbols.NoEntry() ds = signsandsymbols.DangerSign() fd = signsandsymbols.FloppyDisk() ns = signsandsymbols.NoSmoking() ne.x, ne.y = 10, 10 ds.x, ds.y = 120, 10 fd.x, fd.y = 10, 120 ns.x, ns.y = 120, 120 d.add(ne) d.add(ds) d.add(fd) d.add(ns) """) heading3("Compound Widgets") disc("""Let's imagine a compound widget which draws two faces side by side. This is easy to build when you have the Face widget.""") eg(""" >>> tf = widgetbase.TwoFaces() >>> tf.faceOne.mood 'happy' >>> tf.faceTwo.mood 'sad' >>> tf.dumpProperties() faceOne.eyeColor = Color(0.00,0.00,1.00) faceOne.mood = happy faceOne.size = 80 faceOne.skinColor = None faceOne.x = 10 faceOne.y = 10 faceTwo.eyeColor = Color(0.00,0.00,1.00) faceTwo.mood = sad faceTwo.size = 80 faceTwo.skinColor = None faceTwo.x = 100 faceTwo.y = 10 >>> """) disc("""The attributes 'faceOne' and 'faceTwo' are deliberately exposed so you can get at them directly. There could also be top-level attributes, but there aren't in this case.""") heading3("Verifying Widgets") disc("""The widget designer decides the policy on verification, but by default they work like shapes - checking every assignment - if the designer has provided the checking information.""") heading3("Implementing Widgets") disc("""We tried to make it as easy to implement widgets as possible. Here's the code for a Face widget which does not do any type checking:""") eg(""" class Face(Widget): \"\"\"This draws a face with two eyes, mouth and nose.\"\"\" def __init__(self): self.x = 10 self.y = 10 self.size = 80 self.skinColor = None self.eyeColor = colors.blue self.mood = 'happy' def draw(self): s = self.size # abbreviate as we will use this a lot g = shapes.Group() g.transform = [1,0,0,1,self.x, self.y] # background g.add(shapes.Circle(s * 0.5, s * 0.5, s * 0.5, fillColor=self.skinColor)) # CODE OMITTED TO MAKE MORE SHAPES return g """) disc("""We left out all the code to draw the shapes in this document, but you can find it in the distribution in $widgetbase.py$.""") disc("""By default, any attribute without a leading underscore is returned by setProperties. This is a deliberate policy to encourage consistent coding conventions.""") disc("""Once your widget works, you probably want to add support for verification. This involves adding a dictionary to the class called $_verifyMap$, which map from attribute names to 'checking functions'. The $widgetbase.py$ module defines a bunch of checking functions with names like $isNumber$, $isListOfShapes$ and so on. You can also simply use $None$, which means that the attribute must be present but can have any type. And you can and should write your own checking functions. We want to restrict the "mood" custom attribute to the values "happy", "sad" or "ok". So we do this:""") eg(""" class Face(Widget): \"\"\"This draws a face with two eyes. It exposes a couple of properties to configure itself and hides all other details\"\"\" def checkMood(moodName): return (moodName in ('happy','sad','ok')) _verifyMap = { 'x': shapes.isNumber, 'y': shapes.isNumber, 'size': shapes.isNumber, 'skinColor':shapes.isColorOrNone, 'eyeColor': shapes.isColorOrNone, 'mood': checkMood } """) disc("""This checking will be performed on every attribute assignment; or, if $config.shapeChecking$ is off, whenever you call $myFace.verify()$.""") heading3("Documenting Widgets") disc(""" We are working on a generic tool to document any Python package or module; this is already checked into ReportLab and will be used to generate a reference for the ReportLab package. When it encounters widgets, it adds extra sections to the manual including:""") bullet("the doc string for your widget class ") bullet("the code snippet from your demo() method, so people can see how to use it") bullet("the drawing produced by the demo() method ") bullet("the property dump for the widget in the drawing. ") disc(""" This tool will mean that we can have guaranteed up-to-date documentation on our widgets and charts, both on the web site and in print; and that you can do the same for your own widgets, too! """) heading3("Widget Design Strategies") disc("""We could not come up with a consistent architecture for designing widgets, so we are leaving that problem to the authors! If you do not like the default verification strategy, or the way $setProperties/getProperties$ works, you can override them yourself.""") disc("""For simple widgets it is recommended that you do what we did above: select non-overlapping properties, initialize every property on $__init__$ and construct everything when $draw()$ is called. You can instead have $__setattr__$ hooks and have things updated when certain attributes are set. Consider a pie chart. If you want to expose the individual wedges, you might write code like this:""") eg(""" from reportlab.graphics.charts import piecharts pc = piecharts.Pie() pc.defaultColors = [navy, blue, skyblue] #used in rotation pc.data = [10,30,50,25] pc.slices[7].strokeWidth = 5 """) #removed 'pc.backColor = yellow' from above code example disc("""The last line is problematic as we have only created four wedges - in fact we might not have created them yet. Does $pc.wedges[7]$ raise an error? Is it a prescription for what should happen if a seventh wedge is defined, used to override the default settings? We dump this problem squarely on the widget author for now, and recommend that you get a simple one working before exposing 'child objects' whose existence depends on other properties' values :-)""") disc("""We also discussed rules by which parent widgets could pass properties to their children. There seems to be a general desire for a global way to say that 'all wedges get their lineWidth from the lineWidth of their parent' without a lot of repetitive coding. We do not have a universal solution, so again leave that to widget authors. We hope people will experiment with push-down, pull-down and pattern-matching approaches and come up with something nice. In the meantime, we certainly can write monolithic chart widgets which work like the ones in, say, Visual Basic and Delphi.""") disc("""For now have a look at the following sample code using an early version of a pie chart widget and the output it generates:""") eg(""" from reportlab.lib.colors import * from reportlab.graphics import shapes,renderPDF from reportlab.graphics.charts.piecharts import Pie d = Drawing(400,200) d.add(String(100,175,"Without labels", textAnchor="middle")) d.add(String(300,175,"With labels", textAnchor="middle")) pc = Pie() pc.x = 25 pc.y = 50 pc.data = [10,20,30,40,50,60] pc.slices[0].popout = 5 d.add(pc, 'pie1') pc2 = Pie() pc2.x = 150 pc2.y = 50 pc2.data = [10,20,30,40,50,60] pc2.labels = ['a','b','c','d','e','f'] d.add(pc2, 'pie2') pc3 = Pie() pc3.x = 275 pc3.y = 50 pc3.data = [10,20,30,40,50,60] pc3.labels = ['a','b','c','d','e','f'] pc3.wedges.labelRadius = 0.65 pc3.wedges.fontName = "Helvetica-Bold" pc3.wedges.fontSize = 16 pc3.wedges.fontColor = colors.yellow d.add(pc3, 'pie3') """) # Hack to force a new paragraph before the todo() :-( disc("") from reportlab.lib.colors import * from reportlab.graphics import shapes,renderPDF from reportlab.graphics.charts.piecharts import Pie d = Drawing(400,200) d.add(String(100,175,"Without labels", textAnchor="middle")) d.add(String(300,175,"With labels", textAnchor="middle")) pc = Pie() pc.x = 25 pc.y = 50 pc.data = [10,20,30,40,50,60] pc.slices[0].popout = 5 d.add(pc, 'pie1') pc2 = Pie() pc2.x = 150 pc2.y = 50 pc2.data = [10,20,30,40,50,60] pc2.labels = ['a','b','c','d','e','f'] d.add(pc2, 'pie2') pc3 = Pie() pc3.x = 275 pc3.y = 50 pc3.data = [10,20,30,40,50,60] pc3.labels = ['a','b','c','d','e','f'] pc3.slices.labelRadius = 0.65 pc3.slices.fontName = "Helvetica-Bold" pc3.slices.fontSize = 16 pc3.slices.fontColor = colors.yellow d.add(pc3, 'pie3') draw(d, 'Some sample Pies') reportlab-3.3.0/docs/userguide/ch6_tables.py0000664000175000017500000007417412661063723020376 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch6_tables.py from tools.docco.rl_doc_utils import * from reportlab.platypus import Image,ListFlowable, ListItem import reportlab heading1("Tables and TableStyles") disc(""" The $Table$ and $LongTable$ classes derive from the $Flowable$ class and are intended as a simple textual gridding mechanisms. The $longTable$ class uses a greedy algorithm when calculating column widths and is intended for long tables where speed counts. $Table$ cells can hold anything which can be converted to a Python $string$ or $Flowables$ (or lists of $Flowables$). """) disc(""" Our present tables are a trade-off between efficient drawing and specification and functionality. We assume the reader has some familiarity with HTML tables. In brief, they have the following characteristics: """) bullet("""They can contain anything convertible to a string; flowable objects such as other tables; or entire sub-stories""") bullet("""They can work out the row heights to fit the data if you don't supply the row height. (They can also work out the widths, but generally it is better for a designer to set the width manually, and it draws faster).""") bullet("""They can split across pages if needed (see the canSplit attribute). You can specify that a number of rows at the top and bottom should be repeated after the split (e.g. show the headers again on page 2,3,4...)""") bullet("""They have a simple and powerful notation for specifying shading and gridlines which works well with financial or database tables, where you don't know the number of rows up front. You can easily say 'make the last row bold and put a line above it'""") bullet("""The style and data are separated, so you can declare a handful of table styles and use them for a family of reports. Styes can also 'inherit', as with paragraphs.""") disc("""There is however one main limitation compared to an HTML table. They define a simple rectangular grid. There is no simple row or column spanning; if you need to span cells, you must nest tables inside table cells instead or use a more complex scheme in which the lead cell of a span contains the actual contents.""") disc(""" $Tables$ are created by passing the constructor an optional sequence of column widths, an optional sequence of row heights, and the data in row order. Drawing of the table can be controlled by using a $TableStyle$ instance. This allows control of the color and weight of the lines (if any), and the font, alignment and padding of the text. A primitive automatic row height and or column width calculation mechanism is provided for. """) heading2('$Table$ User Methods') disc("""These are the main methods which are of interest to the client programmer.""") heading4("""$Table(data, colWidths=None, rowHeights=None, style=None, splitByRow=1, repeatRows=0, repeatCols=0)$""") disc("""The $data$ argument is a sequence of sequences of cell values each of which should be convertible to a string value using the $str$ function or should be a Flowable instance (such as a $Paragraph$) or a list (or tuple) of such instances. If a cell value is a $Flowable$ or list of $Flowables$ these must either have a determined width or the containing column must have a fixed width. The first row of cell values is in $data[0]$ i.e. the values are in row order. The $i$, $j$th. cell value is in $data[i][j]$. Newline characters $'\\n'$ in cell values are treated as line split characters and are used at draw time to format the cell into lines. """) disc("""The other arguments are fairly obvious, the $colWidths$ argument is a sequence of numbers or possibly $None$, representing the widths of the columns. The number of elements in $colWidths$ determines the number of columns in the table. A value of $None$ means that the corresponding column width should be calculated automatically.""") disc("""The $rowHeights$ argument is a sequence of numbers or possibly $None$, representing the heights of the rows. The number of elements in $rowHeights$ determines the number of rows in the table. A value of $None$ means that the corresponding row height should be calculated automatically.""") disc("""The $style$ argument can be an initial style for the table.""") disc("""The $splitByRow$ argument is only needed for tables both too tall and too wide to fit in the current context. In this case you must decide whether to 'tile' down and across, or across and then down. This parameter is a Boolean indicating that the $Table$ should split itself by row before attempting to split itself by column when too little space is available in the current drawing area and the caller wants the $Table$ to split. Splitting a $Table$ by column is currently not implemented, so setting $splitByRow$ to $False$ will result in a $NotImplementedError$.""") disc("""The $repeatRows$ argument specifies the number or a tuple of leading rows that should be repeated when the $Table$ is asked to split itself. If it is a tuple it should specify which of the leading rows should be repeated; this allows for cases where the first appearance of the table hsa more leading rows than later split parts. The $repeatCols$ argument is currently ignored as a $Table$ cannot be split by column.""") heading4('$Table.setStyle(tblStyle)$') disc(""" This method applies a particular instance of class $TableStyle$ (discussed below) to the $Table$ instance. This is the only way to get $tables$ to appear in a nicely formatted way. """) disc(""" Successive uses of the $setStyle$ method apply the styles in an additive fashion. That is, later applications override earlier ones where they overlap. """) heading2('$TableStyle$') disc(""" This class is created by passing it a sequence of commands, each command is a tuple identified by its first element which is a string; the remaining elements of the command tuple represent the start and stop cell coordinates of the command and possibly thickness and colors, etc. """) heading2("$TableStyle$ User Methods") heading3("$TableStyle(commandSequence)$") disc("""The creation method initializes the $TableStyle$ with the argument command sequence as an example:""") eg(""" LIST_STYLE = TableStyle( [('LINEABOVE', (0,0), (-1,0), 2, colors.green), ('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), ('LINEBELOW', (0,-1), (-1,-1), 2, colors.green), ('ALIGN', (1,1), (-1,-1), 'RIGHT')] ) """) heading3("$TableStyle.add(commandSequence)$") disc("""This method allows you to add commands to an existing $TableStyle$, i.e. you can build up $TableStyles$ in multiple statements. """) eg(""" LIST_STYLE.add('BACKGROUND', (0,0), (-1,0), colors.Color(0,0.7,0.7)) """) heading3("$TableStyle.getCommands()$") disc("""This method returns the sequence of commands of the instance.""") eg(""" cmds = LIST_STYLE.getCommands() """) heading2("$TableStyle$ Commands") disc("""The commands passed to $TableStyles$ come in three main groups which affect the table background, draw lines, or set cell styles. """) disc("""The first element of each command is its identifier, the second and third arguments determine the cell coordinates of the box of cells which are affected with negative coordinates counting backwards from the limit values as in Python indexing. The coordinates are given as (column, row) which follows the spreadsheet 'A1' model, but not the more natural (for mathematicians) 'RC' ordering. The top left cell is (0, 0) the bottom right is (-1, -1). Depending on the command various extra (???) occur at indices beginning at 3 on. """) heading3("""$TableStyle$ Cell Formatting Commands""") disc("""The cell formatting commands all begin with an identifier, followed by the start and stop cell definitions and the perhaps other arguments. the cell formatting commands are:""") eg(""" FONT - takes fontname, optional fontsize and optional leading. FONTNAME (or FACE) - takes fontname. FONTSIZE (or SIZE) - takes fontsize in points; leading may get out of sync. LEADING - takes leading in points. TEXTCOLOR - takes a color name or (R,G,B) tuple. ALIGNMENT (or ALIGN) - takes one of LEFT, RIGHT and CENTRE (or CENTER) or DECIMAL. LEFTPADDING - takes an integer, defaults to 6. RIGHTPADDING - takes an integer, defaults to 6. BOTTOMPADDING - takes an integer, defaults to 3. TOPPADDING - takes an integer, defaults to 3. BACKGROUND - takes a color. ROWBACKGROUNDS - takes a list of colors to be used cyclically. COLBACKGROUNDS - takes a list of colors to be used cyclically. VALIGN - takes one of TOP, MIDDLE or the default BOTTOM """) disc("""This sets the background cell color in the relevant cells. The following example shows the $BACKGROUND$, and $TEXTCOLOR$ commands in action:""") EmbeddedCode(""" data= [['00', '01', '02', '03', '04'], ['10', '11', '12', '13', '14'], ['20', '21', '22', '23', '24'], ['30', '31', '32', '33', '34']] t=Table(data) t.setStyle(TableStyle([('BACKGROUND',(1,1),(-2,-2),colors.green), ('TEXTCOLOR',(0,0),(1,-1),colors.red)])) """) disc("""To see the effects of the alignment styles we need some widths and a grid, but it should be easy to see where the styles come from.""") EmbeddedCode(""" data= [['00', '01', '02', '03', '04'], ['10', '11', '12', '13', '14'], ['20', '21', '22', '23', '24'], ['30', '31', '32', '33', '34']] t=Table(data,5*[0.4*inch], 4*[0.4*inch]) t.setStyle(TableStyle([('ALIGN',(1,1),(-2,-2),'RIGHT'), ('TEXTCOLOR',(1,1),(-2,-2),colors.red), ('VALIGN',(0,0),(0,-1),'TOP'), ('TEXTCOLOR',(0,0),(0,-1),colors.blue), ('ALIGN',(0,-1),(-1,-1),'CENTER'), ('VALIGN',(0,-1),(-1,-1),'MIDDLE'), ('TEXTCOLOR',(0,-1),(-1,-1),colors.green), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) """) heading3("""$TableStyle$ Line Commands""") disc(""" Line commands begin with the identifier, the start and stop cell coordinates and always follow this with the thickness (in points) and color of the desired lines. Colors can be names, or they can be specified as a (R, G, B) tuple, where R, G and B are floats and (0, 0, 0) is black. The line command names are: GRID, BOX, OUTLINE, INNERGRID, LINEBELOW, LINEABOVE, LINEBEFORE and LINEAFTER. BOX and OUTLINE are equivalent, and GRID is the equivalent of applying both BOX and INNERGRID. """) CPage(4.0) disc("""We can see some line commands in action with the following example. """) EmbeddedCode(""" data= [['00', '01', '02', '03', '04'], ['10', '11', '12', '13', '14'], ['20', '21', '22', '23', '24'], ['30', '31', '32', '33', '34']] t=Table(data,style=[('GRID',(1,1),(-2,-2),1,colors.green), ('BOX',(0,0),(1,-1),2,colors.red), ('LINEABOVE',(1,2),(-2,2),1,colors.blue), ('LINEBEFORE',(2,1),(2,-2),1,colors.pink), ]) """) disc("""Line commands cause problems for tables when they split; the following example shows a table being split in various positions""") EmbeddedCode(""" data= [['00', '01', '02', '03', '04'], ['10', '11', '12', '13', '14'], ['20', '21', '22', '23', '24'], ['30', '31', '32', '33', '34']] t=Table(data,style=[ ('GRID',(0,0),(-1,-1),0.5,colors.grey), ('GRID',(1,1),(-2,-2),1,colors.green), ('BOX',(0,0),(1,-1),2,colors.red), ('BOX',(0,0),(-1,-1),2,colors.black), ('LINEABOVE',(1,2),(-2,2),1,colors.blue), ('LINEBEFORE',(2,1),(2,-2),1,colors.pink), ('BACKGROUND', (0, 0), (0, 1), colors.pink), ('BACKGROUND', (1, 1), (1, 2), colors.lavender), ('BACKGROUND', (2, 2), (2, 3), colors.orange), ]) """) t=getStory()[-1] getStory().append(Spacer(0,6)) for s in t.split(4*inch,30): getStory().append(s) getStory().append(Spacer(0,6)) getStory().append(Spacer(0,6)) for s in t.split(4*inch,36): getStory().append(s) getStory().append(Spacer(0,6)) disc("""When unsplit and split at the first or second row.""") CPage(4.0) heading3("""Complex Cell Values""") disc(""" As mentioned above we can have complicated cell values including $Paragraphs$, $Images$ and other $Flowables$ or lists of the same. To see this in operation consider the following code and the table it produces. Note that the $Image$ has a white background which will obscure any background you choose for the cell. To get better results you should use a transparent background. """) import os, reportlab.platypus I = '../images/replogo.gif' EmbeddedCode(""" I = Image('%s') I.drawHeight = 1.25*inch*I.drawHeight / I.drawWidth I.drawWidth = 1.25*inch P0 = Paragraph(''' A paragraph 1''', styleSheet["BodyText"]) P = Paragraph(''' The ReportLab Left Logo Image''', styleSheet["BodyText"]) data= [['A', 'B', 'C', P0, 'D'], ['00', '01', '02', [I,P], '04'], ['10', '11', '12', [P,I], '14'], ['20', '21', '22', '23', '24'], ['30', '31', '32', '33', '34']] t=Table(data,style=[('GRID',(1,1),(-2,-2),1,colors.green), ('BOX',(0,0),(1,-1),2,colors.red), ('LINEABOVE',(1,2),(-2,2),1,colors.blue), ('LINEBEFORE',(2,1),(2,-2),1,colors.pink), ('BACKGROUND', (0, 0), (0, 1), colors.pink), ('BACKGROUND', (1, 1), (1, 2), colors.lavender), ('BACKGROUND', (2, 2), (2, 3), colors.orange), ('BOX',(0,0),(-1,-1),2,colors.black), ('GRID',(0,0),(-1,-1),0.5,colors.black), ('VALIGN',(3,0),(3,0),'BOTTOM'), ('BACKGROUND',(3,0),(3,0),colors.limegreen), ('BACKGROUND',(3,1),(3,1),colors.khaki), ('ALIGN',(3,1),(3,1),'CENTER'), ('BACKGROUND',(3,2),(3,2),colors.beige), ('ALIGN',(3,2),(3,2),'LEFT'), ]) t._argW[3]=1.5*inch """%I) heading3("""$TableStyle$ Span Commands""") disc("""Our $Table$ classes support the concept of spanning, but it isn't specified in the same way as html. The style specification """) eg(""" SPAN, (sc,sr), (ec,er) """) disc("""indicates that the cells in columns $sc$ - $ec$ and rows $sr$ - $er$ should be combined into a super cell with contents determined by the cell $(sc, sr)$. The other cells should be present, but should contain empty strings or you may get unexpected results. """) EmbeddedCode(""" data= [['Top\\nLeft', '', '02', '03', '04'], ['', '', '12', '13', '14'], ['20', '21', '22', 'Bottom\\nRight', ''], ['30', '31', '32', '', '']] t=Table(data,style=[ ('GRID',(0,0),(-1,-1),0.5,colors.grey), ('BACKGROUND',(0,0),(1,1),colors.palegreen), ('SPAN',(0,0),(1,1)), ('BACKGROUND',(-2,-2),(-1,-1), colors.pink), ('SPAN',(-2,-2),(-1,-1)), ]) """) disc("""notice that we don't need to be conservative with our $GRID$ command. The spanned cells are not drawn through. """) heading3("""Special $TableStyle$ Indeces""") disc("""In any style command the first row index may be set to one of the special strings $'splitlast'$ or $'splitfirst'$ to indicate that the style should be used only for the last row of a split table, or the first row of a continuation. This allows splitting tables with nicer effects around the split.""") heading1("""Programming $Flowables$""") disc("""The following flowables let you conditionally evaluate and execute expressions and statements at wrap time:""") heading2("""$DocAssign(self, var, expr, life='forever')$""") disc("""Assigns a variable of name $var$ to the expression $expr$. E.g.:""") eg(""" DocAssign('i',3) """) heading2("""$DocExec(self, stmt, lifetime='forever')$""") disc("""Executes the statement $stmt$. E.g.:""") eg(""" DocExec('i-=1') """) heading2("""$DocPara(self, expr, format=None, style=None, klass=None, escape=True)$""") disc("""Creates a paragraph with the value of expr as text. If format is specified it should use %(__expr__)s for string interpolation of the expression expr (if any). It may also use %(name)s interpolations for other variables in the namespace. E.g.:""") eg(""" DocPara('i',format='The value of i is %(__expr__)d',style=normal) """) heading2("""$DocAssert(self, cond, format=None)$""") disc("""Raises an $AssertionError$ containing the $format$ string if $cond$ evaluates as $False$.""") eg(""" DocAssert(val, 'val is False') """) heading2("""$DocIf(self, cond, thenBlock, elseBlock=[])$""") disc("""If $cond$ evaluates as $True$, this flowable is replaced by the $thenBlock$ elsethe $elseBlock$.""") eg(""" DocIf('i>3',Paragraph('The value of i is larger than 3',normal),\\ Paragraph('The value of i is not larger than 3',normal)) """) heading2("""$DocWhile(self, cond, whileBlock)$""") disc("""Runs the $whileBlock$ while $cond$ evaluates to $True$. E.g.:""") eg(""" DocAssign('i',5) DocWhile('i',[DocPara('i',format='The value of i is %(__expr__)d',style=normal),DocExec('i-=1')]) """) disc("""This example produces a set of paragraphs of the form:""") eg(""" The value of i is 5 The value of i is 4 The value of i is 3 The value of i is 2 The value of i is 1 """) heading1("""Other Useful $Flowables$""") heading2("""$Preformatted(text, style, bulletText=None, dedent=0, maxLineLength=None, splitChars=None, newLineChars=None)$""") disc(""" Creates a preformatted paragraph which does no wrapping, line splitting or other manipulations. No $XML$ style tags are taken account of in the text. If dedent is non zero $dedent$ common leading spaces will be removed from the front of each line. """) heading3("Defining a maximum line length") disc(""" You can use the property $maxLineLength$ to define a maximum line length. If a line length exceeds this maximum value, the line will be automatically splitted. """) disc(""" The line will be split on any single character defined in $splitChars$. If no value is provided for this property, the line will be split on any of the following standard characters: space, colon, full stop, semi-colon, coma, hyphen, forward slash, back slash, left parenthesis, left square bracket and left curly brace """) disc(""" Characters can be automatically inserted at the beginning of each line that has been created. You can set the property $newLineChars$ to the characters you want to use. """) EmbeddedCode(""" from reportlab.lib.styles import getSampleStyleSheet stylesheet=getSampleStyleSheet() normalStyle = stylesheet['Code'] text=''' class XPreformatted(Paragraph): def __init__(self, text, style, bulletText = None, frags=None, caseSensitive=1): self.caseSensitive = caseSensitive if maximumLineLength and text: text = self.stopLine(text, maximumLineLength, splitCharacters) cleaner = lambda text, dedent=dedent: ''.join(_dedenter(text or '',dedent)) self._setup(text, style, bulletText, frags, cleaner) ''' t=Preformatted(text,normalStyle,maxLineLength=60, newLineChars='> ') """) heading2("""$XPreformatted(text, style, bulletText=None, dedent=0, frags=None)$""") disc(""" This is a non rearranging form of the $Paragraph$ class; XML tags are allowed in $text$ and have the same meanings as for the $Paragraph$ class. As for $Preformatted$, if dedent is non zero $dedent$ common leading spaces will be removed from the front of each line. """) EmbeddedCode(""" from reportlab.lib.styles import getSampleStyleSheet stylesheet=getSampleStyleSheet() normalStyle = stylesheet['Code'] text=''' This is a non rearranging form of the Paragraph class; XML tags are allowed in text and have the same meanings as for the Paragraph class. As for Preformatted, if dedent is non zero dedent common leading spaces will be removed from the front of each line. You can have &amp; style entities as well for & < > and ". ''' t=XPreformatted(text,normalStyle,dedent=3) """) heading2("""$Image(filename, width=None, height=None)$""") disc("""Create a flowable which will contain the image defined by the data in file $filename$. The default PDF image type jpeg is supported and if the PIL extension to Python is installed the other image types can also be handled. If $width$ and or $height$ are specified then they determine the dimension of the displayed image in points. If either dimension is not specified (or specified as $None$) then the corresponding pixel dimension of the image is assumed to be in points and used. """) I="../images/lj8100.jpg" eg(""" Image("lj8100.jpg") """,after=0.1) disc("""will display as""") try: getStory().append(Image(I)) except: disc("""An image should have appeared here.""") disc("""whereas""") eg(""" im = Image("lj8100.jpg", width=2*inch, height=2*inch) im.hAlign = 'CENTER' """, after=0.1) disc('produces') try: im = Image(I, width=2*inch, height=2*inch) im.hAlign = 'CENTER' getStory().append(Image(I, width=2*inch, height=2*inch)) except: disc("""An image should have appeared here.""") heading2("""$Spacer(width, height)$""") disc("""This does exactly as would be expected; it adds a certain amount of space into the story. At present this only works for vertical space. """) CPage(1) heading2("""$PageBreak()$""") disc("""This $Flowable$ represents a page break. It works by effectively consuming all vertical space given to it. This is sufficient for a single $Frame$ document, but would only be a frame break for multiple frames so the $BaseDocTemplate$ mechanism detects $pageBreaks$ internally and handles them specially. """) CPage(1) heading2("""$CondPageBreak(height)$""") disc("""This $Flowable$ attempts to force a $Frame$ break if insufficient vertical space remains in the current $Frame$. It is thus probably wrongly named and should probably be renamed as $CondFrameBreak$. """) CPage(1) heading2("""$KeepTogether(flowables)$""") disc(""" This compound $Flowable$ takes a list of $Flowables$ and attempts to keep them in the same $Frame$. If the total height of the $Flowables$ in the list $flowables$ exceeds the current frame's available space then all the space is used and a frame break is forced. """) CPage(1) heading2("""$TableOfContents()$""") disc(""" A table of contents can be generated by using the $TableOfContents$ flowable. The following steps are needed to add a table of contents to your document: """) disc("""Create an instance of $TableOfContents$. Override the level styles (optional) and add the object to the story:""") eg(""" toc = TableOfContents() PS = ParagraphStyle toc.levelStyles = [ PS(fontName='Times-Bold', fontSize=14, name='TOCHeading1', leftIndent=20, firstLineIndent=-20, spaceBefore=5, leading=16), PS(fontSize=12, name='TOCHeading2', leftIndent=40, firstLineIndent=-20, spaceBefore=0, leading=12), PS(fontSize=10, name='TOCHeading3', leftIndent=60, firstLineIndent=-20, spaceBefore=0, leading=12), PS(fontSize=10, name='TOCHeading4', leftIndent=100, firstLineIndent=-20, spaceBefore=0, leading=12), ] story.append(toc) """) disc("""Entries to the table of contents can be done either manually by calling the $addEntry$ method on the $TableOfContents$ object or automatically by sending a $'TOCEntry'$ notification in the $afterFlowable$ method of the $DocTemplate$ you are using. The data to be passed to $notify$ is a list of three or four items countaining a level number, the entry text, the page number and an optional destination key which the entry should point to. This list will usually be created in a document template's method like afterFlowable(), making notification calls using the notify() method with appropriate data like this: """) eg(''' def afterFlowable(self, flowable): """Detect Level 1 and 2 headings, build outline, and track chapter title.""" if isinstance(flowable, Paragraph): txt = flowable.getPlainText() if style == 'Heading1': # ... self.notify('TOCEntry', (0, txt, self.page)) elif style == 'Heading2': # ... key = 'h2-%s' % self.seq.nextf('heading2') self.canv.bookmarkPage(key) self.notify('TOCEntry', (1, txt, self.page, key)) # ... ''') disc("""This way, whenever a paragraph of style $'Heading1'$ or $'Heading2'$ is added to the story, it will appear in the table of contents. $Heading2$ entries will be clickable because a bookmarked key has been supplied. """) disc("""Finally you need to use the $multiBuild$ method of the DocTemplate because tables of contents need several passes to be generated:""") eg(""" doc.multiBuild(story) """) disc("""Below is a simple but working example of a document with a table of contents:""") eg(''' from reportlab.lib.styles import ParagraphStyle as PS from reportlab.platypus import PageBreak from reportlab.platypus.paragraph import Paragraph from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate from reportlab.platypus.tableofcontents import TableOfContents from reportlab.platypus.frames import Frame from reportlab.lib.units import cm class MyDocTemplate(BaseDocTemplate): def __init__(self, filename, **kw): self.allowSplitting = 0 BaseDocTemplate.__init__(self, filename, **kw) template = PageTemplate('normal', [Frame(2.5*cm, 2.5*cm, 15*cm, 25*cm, id='F1')]) self.addPageTemplates(template) def afterFlowable(self, flowable): "Registers TOC entries." if flowable.__class__.__name__ == 'Paragraph': text = flowable.getPlainText() style = flowable.style.name if style == 'Heading1': self.notify('TOCEntry', (0, text, self.page)) if style == 'Heading2': self.notify('TOCEntry', (1, text, self.page)) h1 = PS(name = 'Heading1', fontSize = 14, leading = 16) h2 = PS(name = 'Heading2', fontSize = 12, leading = 14, leftIndent = delta) # Build story. story = [] toc = TableOfContents() # For conciseness we use the same styles for headings and TOC entries toc.levelStyles = [h1, h2] story.append(toc) story.append(PageBreak()) story.append(Paragraph('First heading', h1)) story.append(Paragraph('Text in first heading', PS('body'))) story.append(Paragraph('First sub heading', h2)) story.append(Paragraph('Text in first sub heading', PS('body'))) story.append(PageBreak()) story.append(Paragraph('Second sub heading', h2)) story.append(Paragraph('Text in second sub heading', PS('body'))) story.append(Paragraph('Last heading', h1)) doc = MyDocTemplate('mintoc.pdf') doc.multiBuild(story) ''') CPage(1) heading2("""$SimpleIndex()$""") disc(""" An index can be generated by using the $SimpleIndex$ flowable. The following steps are needed to add an index to your document: """) disc("""Use the index tag in paragraphs to index terms:""") eg(''' story = [] ... story.append('The third word of this paragraph is indexed.') ''') disc("""Create an instance of $SimpleIndex$ and add it to the story where you want it to appear:""") eg(''' index = SimpleIndex(dot=' . ', headers=headers) story.append(index) ''') disc("""The parameters which you can pass into the SimpleIndex constructor are explained in the reportlab reference. Now, build the document by using the canvas maker returned by SimpleIndex.getCanvasMaker():""") eg(""" doc.build(story, canvasmaker=index.getCanvasMaker()) """) disc("""To build an index with multiple levels, pass a comma-separated list of items to the item attribute of an index tag:""") eg(""" """) disc("""terma will respresent the top-most level and termc the most specific term. termd and termb will appear in the same level inside terma.""") disc("""If you need to index a term containing a comma, you will need to escape it by doubling it. To avoid the ambiguity of three consecutive commas (an escaped comma followed by a list separator or a list separator followed by an escaped comma?) introduce a space in the right position. Spaces at the beginning or end of terms will be removed.""") eg(""" """) disc(""" This indexes the terms "comma (,)", "," and "...". """) heading2("""$ListFlowable(),ListItem()$""") disc(""" Use these classes to make ordered and unordered lists. Lists can be nested. """) disc(""" $ListFlowable()$ will create an ordered list, which can contain any flowable. The class has a number of parameters to change font, colour, size, style and position of list numbers, or of bullets in unordered lists. The type of numbering can also be set to use lower or upper case letters ('A,B,C' etc.) or Roman numerals (capitals or lowercase) using the bulletType property. To change the list to an unordered type, set bulletType='bullet'. """) disc(""" Items within a $ListFlowable()$ list can be changed from their default appearance by wrapping them in a $ListItem()$ class and setting its properties. """) disc(""" The following will create an ordered list, and set the third item to an unordered sublist. """) EmbeddedCode(""" from reportlab.platypus import ListFlowable, ListItem from reportlab.lib.styles import getSampleStyleSheet styles = getSampleStyleSheet() style = styles["Normal"] t = ListFlowable( [ Paragraph("Item no.1", style), ListItem(Paragraph("Item no. 2", style),bulletColor="green",value=7), ListFlowable( [ Paragraph("sublist item 1", style), ListItem(Paragraph('sublist item 2', style),bulletColor='red',value='square') ], bulletType='bullet', start='square', ), Paragraph("Item no.4", style), ], bulletType='i' ) """) reportlab-3.3.0/docs/userguide/ch2a_fonts.py0000664000175000017500000004672312661063723020411 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch2a_fonts.py from tools.docco.rl_doc_utils import * from reportlab.lib.codecharts import SingleByteEncodingChart from reportlab.platypus import Image import reportlab heading1("Fonts and encodings") disc(""" This chapter covers fonts, encodings and Asian language capabilities. If you are purely concerned with generating PDFs for Western European languages, you can just read the "Unicode is the default" section below and skip the rest on a first reading. We expect this section to grow considerably over time. We hope that Open Source will enable us to give better support for more of the world's languages than other tools, and we welcome feedback and help in this area. """) heading2("Unicode and UTF8 are the default input encodings") disc(""" Starting with reportlab Version 2.0 (May 2006), all text input you provide to our APIs should be in UTF8 or as Python Unicode objects. This applies to arguments to canvas.drawString and related APIs, table cell content, drawing object parameters, and paragraph source text. """) disc(""" We considered making the input encoding configurable or even locale-dependent, but decided that "explicit is better than implicit".""") disc(""" This simplifies many things we used to do previously regarding greek letters, symbols and so on. To display any character, find out its unicode code point, and make sure the font you are using is able to display it.""") disc(""" If you are adapting a ReportLab 1.x application, or reading data from another source which contains single-byte data (e.g. latin-1 or WinAnsi), you need to do a conversion into Unicode. The Python codecs package now includes converters for all the common encodings, including Asian ones. """) disc(""" If your data is not encoded as UTF8, you will get a UnicodeDecodeError as soon as you feed in a non-ASCII character. For example, this snippet below is attempting to read in and print a series of names, including one with a French accent: ^Marc-Andr\u00e9 Lemburg^. The standard error is quite helpful and tells you what character it doesn't like: """) eg(""" >>> from reportlab.pdfgen.canvas import Canvas >>> c = Canvas('temp.pdf') >>> y = 700 >>> for line in file('latin_python_gurus.txt','r'): ... c.drawString(100, y, line.strip()) ... Traceback (most recent call last): ... UnicodeDecodeError: 'utf8' codec can't decode bytes in position 9-11: invalid data -->\u00e9 L<--emburg >>> """) disc(""" The simplest fix is just to convert your data to unicode, saying which encoding it comes from, like this:""") eg(""" >>> for line in file('latin_input.txt','r'): ... uniLine = unicode(line, 'latin-1') ... c.drawString(100, y, uniLine.strip()) >>> >>> c.save() """) heading2("Automatic output font substitution") disc(""" There are still a number of places in the code, including the rl_config defaultEncoding parameter, and arguments passed to various Font constructors, which refer to encodings. These were useful in the past when people needed to use glyphs in the Symbol and ZapfDingbats fonts which are supported by PDF viewing devices. By default the standard fonts (Helvetica, Courier, Times Roman) will offer the glyphs available in Latin-1. However, if our engine detects a character not in the font, it will attempt to switch to Symbol or ZapfDingbats to display these. For example, if you include the Unicode character for a pair of right-facing scissors, \\u2702, in a call to ^drawString^, you should see them (there is an example in ^test_pdfgen_general.py/pdf^). It is not necessary to switch fonts in your code. """) heading2("Using non-standard Type 1 fonts") disc(""" As discussed in the previous chapter, every copy of Acrobat Reader comes with 14 standard fonts built in. Therefore, the ReportLab PDF Library only needs to refer to these by name. If you want to use other fonts, they must be available to your code and will be embedded in the PDF document.""") disc(""" You can use the mechanism described below to include arbitrary fonts in your documents. We have an open source font named DarkGardenMK which we may use for testing and/or documenting purposes (and which you may use as well). It comes bundled with the ReportLab distribution in the directory $reportlab/fonts$. """) disc(""" Right now font-embedding relies on font description files in the Adobe AFM ('Adobe Font Metrics') and PFB ('Printer Font Binary') format. The former is an ASCII file and contains information about the characters ('glyphs') in the font such as height, width, bounding box info and other 'metrics', while the latter is a binary file that describes the shapes of the font. The $reportlab/fonts$ directory contains the files $'DarkGardenMK.afm'$ and $'DarkGardenMK.pfb'$ that are used as an example font. """) disc(""" In the following example locate the folder containing the test font and register it for future use with the $pdfmetrics$ module, after which we can use it like any other standard font. """) eg(""" import os import reportlab folder = os.path.dirname(reportlab.__file__) + os.sep + 'fonts' afmFile = os.path.join(folder, 'DarkGardenMK.afm') pfbFile = os.path.join(folder, 'DarkGardenMK.pfb') from reportlab.pdfbase import pdfmetrics justFace = pdfmetrics.EmbeddedType1Face(afmFile, pfbFile) faceName = 'DarkGardenMK' # pulled from AFM file pdfmetrics.registerTypeFace(justFace) justFont = pdfmetrics.Font('DarkGardenMK', faceName, 'WinAnsiEncoding') pdfmetrics.registerFont(justFont) canvas.setFont('DarkGardenMK', 32) canvas.drawString(10, 150, 'This should be in') canvas.drawString(10, 100, 'DarkGardenMK') """) disc(""" Note that the argument "WinAnsiEncoding" has nothing to do with the input; it's to say which set of characters within the font file will be active and available. """) illust(examples.customfont1, "Using a very non-standard font") disc(""" The font's facename comes from the AFM file's $FontName$ field. In the example above we knew the name in advance, but quite often the names of font description files are pretty cryptic and then you might want to retrieve the name from an AFM file automatically. When lacking a more sophisticated method you can use some code as simple as this: """) eg(""" class FontNameNotFoundError(Exception): pass def findFontName(path): "Extract a font name from an AFM file." f = open(path) found = 0 while not found: line = f.readline()[:-1] if not found and line[:16] == 'StartCharMetrics': raise FontNameNotFoundError, path if line[:8] == 'FontName': fontName = line[9:] found = 1 return fontName """) disc(""" In the DarkGardenMK example we explicitely specified the place of the font description files to be loaded. In general, you'll prefer to store your fonts in some canonic locations and make the embedding mechanism aware of them. Using the same configuration mechanism we've already seen at the beginning of this section we can indicate a default search path for Type-1 fonts. """) disc(""" Unfortunately, there is no reliable standard yet for such locations (not even on the same platform) and, hence, you might have to edit the file $reportlab/rl_config.py$ to modify the value of the $T1SearchPath$ identifier to contain additional directories. Our own recommendation is to use the ^reportlab/fonts^ folder in development; and to have any needed fonts as packaged parts of your application in any kind of controlled server deployment. This insulates you from fonts being installed and uninstalled by other software or system administrator. """) heading3("Warnings about missing glyphs") disc("""If you specify an encoding, it is generally assumed that the font designer has provided all the needed glyphs. However, this is not always true. In the case of our example font, the letters of the alphabet are present, but many symbols and accents are missing. The default behaviour is for the font to print a 'notdef' character - typically a blob, dot or space - when passed a character it cannot draw. However, you can ask the library to warn you instead; the code below (executed before loading a font) will cause warnings to be generated for any glyphs not in the font when you register it.""") eg(""" import reportlab.rl_config reportlab.rl_config.warnOnMissingFontGlyphs = 0 """) heading2("Standard Single-Byte Font Encodings") disc(""" This section shows you the glyphs available in the common encodings. """) disc("""The code chart below shows the characters in the $WinAnsiEncoding$. This is the standard encoding on Windows and many Unix systems in America and Western Europe. It is also knows as Code Page 1252, and is practically identical to ISO-Latin-1 (it contains one or two extra characters). This is the default encoding used by the Reportlab PDF Library. It was generated from a standard routine in $reportlab/lib$, $codecharts.py$, which can be used to display the contents of fonts. The index numbers along the edges are in hex.""") cht1 = SingleByteEncodingChart(encodingName='WinAnsiEncoding',charsPerRow=32, boxSize=12) illust(lambda canv: cht1.drawOn(canv, 0, 0), "WinAnsi Encoding", cht1.width, cht1.height) disc("""The code chart below shows the characters in the $MacRomanEncoding$. as it sounds, this is the standard encoding on Macintosh computers in America and Western Europe. As usual with non-unicode encodings, the first 128 code points (top 4 rows in this case) are the ASCII standard and agree with the WinAnsi code chart above; but the bottom 4 rows differ.""") cht2 = SingleByteEncodingChart(encodingName='MacRomanEncoding',charsPerRow=32, boxSize=12) illust(lambda canv: cht2.drawOn(canv, 0, 0), "MacRoman Encoding", cht2.width, cht2.height) disc("""These two encodings are available for the standard fonts (Helvetica, Times-Roman and Courier and their variants) and will be available for most commercial fonts including those from Adobe. However, some fonts contain non- text glyphs and the concept does not really apply. For example, ZapfDingbats and Symbol can each be treated as having their own encoding.""") cht3 = SingleByteEncodingChart(faceName='ZapfDingbats',encodingName='ZapfDingbatsEncoding',charsPerRow=32, boxSize=12) illust(lambda canv: cht3.drawOn(canv, 0, 0), "ZapfDingbats and its one and only encoding", cht3.width, cht3.height) cht4 = SingleByteEncodingChart(faceName='Symbol',encodingName='SymbolEncoding',charsPerRow=32, boxSize=12) illust(lambda canv: cht4.drawOn(canv, 0, 0), "Symbol and its one and only encoding", cht4.width, cht4.height) CPage(5) heading2("TrueType Font Support") disc(""" Marius Gedminas ($mgedmin@delfi.lt$) with the help of Viktorija Zaksiene ($vika@pov.lt$) have contributed support for embedded TrueType fonts. TrueType fonts work in Unicode/UTF8 and are not limited to 256 characters.""") CPage(3) disc("""We use $reportlab.pdfbase.ttfonts.TTFont$ to create a true type font object and register using $reportlab.pdfbase.pdfmetrics.registerFont$. In pdfgen drawing directly to the canvas we can do""") eg(""" # we know some glyphs are missing, suppress warnings import reportlab.rl_config reportlab.rl_config.warnOnMissingFontGlyphs = 0 from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont pdfmetrics.registerFont(TTFont('Vera', 'Vera.ttf')) pdfmetrics.registerFont(TTFont('VeraBd', 'VeraBd.ttf')) pdfmetrics.registerFont(TTFont('VeraIt', 'VeraIt.ttf')) pdfmetrics.registerFont(TTFont('VeraBI', 'VeraBI.ttf')) canvas.setFont('Vera', 32) canvas.drawString(10, 150, "Some text encoded in UTF-8") canvas.drawString(10, 100, "In the Vera TT Font!") """) illust(examples.ttffont1, "Using a the Vera TrueType Font") disc("""In the above example the true type font object is created using""") eg(""" TTFont(name,filename) """) disc("""so that the ReportLab internal name is given by the first argument and the second argument is a string(or file like object) denoting the font's TTF file. In Marius' original patch the filename was supposed to be exactly correct, but we have modified things so that if the filename is relative then a search for the corresponding file is done in the current directory and then in directories specified by $reportlab.rl_config.TTFSearchpath$!""") from reportlab.lib.styles import ParagraphStyle from reportlab.pdfbase.pdfmetrics import registerFontFamily registerFontFamily('Vera',normal='Vera',bold='VeraBd',italic='VeraIt',boldItalic='VeraBI') disc("""Before using the TT Fonts in Platypus we should add a mapping from the family name to the individual font names that describe the behaviour under the $<b>$ and $<i>$ attributes.""") eg(""" from reportlab.pdfbase.pdfmetrics import registerFontFamily registerFontFamily('Vera',normal='Vera',bold='VeraBd',italic='VeraIt',boldItalic='VeraBI') """) disc("""If we only have a Vera regular font, no bold or italic then we must map all to the same internal fontname. ^<b>^ and ^<i>^ tags may now be used safely, but have no effect. After registering and mapping the Vera font as above we can use paragraph text like""") parabox2("""This is in Times-Roman and this is in magenta Vera!""","Using TTF fonts in paragraphs") heading2("Asian Font Support") disc("""The Reportlab PDF Library aims to expose full support for Asian fonts. PDF is the first really portable solution for Asian text handling. There are two main approaches for this: Adobe's Asian Language Packs, or TrueType fonts. """) heading3("Asian Language Packs") disc(""" This approach offers the best performance since nothing needs embedding in the PDF file; as with the standard fonts, everything is on the reader.""") disc(""" Adobe makes available add-ons for each main language. In Adobe Reader 6.0 and 7.0, you will be prompted to download and install these as soon as you try to open a document using them. In earlier versions, you would see an error message on opening an Asian document and had to know what to do. """) disc(""" Japanese, Traditional Chinese (Taiwan/Hong Kong), Simplified Chinese (mainland China) and Korean are all supported and our software knows about the following fonts: """) bullet(""" $chs$ = Chinese Simplified (mainland): '$STSong-Light$' """) bullet(""" $cht$ = Chinese Traditional (Taiwan): '$MSung-Light$', '$MHei-Medium$' """) bullet(""" $kor$ = Korean: '$HYSMyeongJoStd-Medium$','$HYGothic-Medium$' """) bullet(""" $jpn$ = Japanese: '$HeiseiMin-W3$', '$HeiseiKakuGo-W5$' """) disc("""Since many users will not have the font packs installed, we have included a rather grainy ^bitmap^ of some Japanese characters. We will discuss below what is needed to generate them.""") # include a bitmap of some Asian text I=os.path.join(os.path.dirname(reportlab.__file__),'docs','images','jpnchars.jpg') try: getStory().append(Image(I)) except: disc("""An image should have appeared here.""") disc("""Prior to Version 2.0, you had to specify one of many native encodings when registering a CID Font. In version 2.0 you should a new UnicodeCIDFont class.""") eg(""" from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.cidfonts import UnicodeCIDFont pdfmetrics.registerFont(UnicodeCIDFont('HeiseiMin-W3')) canvas.setFont('HeiseiMin-W3', 16) # the two unicode characters below are "Tokyo" msg = u'\u6771\u4EAC : Unicode font, unicode input' canvas.drawString(100, 675, msg) """) #had to double-escape the slashes above to get escapes into the PDF disc("""The old coding style with explicit encodings should still work, but is now only relevant if you need to construct vertical text. We aim to add more readable options for horizontal and vertical text to the UnicodeCIDFont constructor in future. The following four test scripts generate samples in the corresponding languages:""") eg("""tests/test_multibyte_jpn.py tests/test_multibyte_kor.py tests/test_multibyte_chs.py tests/test_multibyte_cht.py""") ## put back in when we have vertical text... ##disc("""The illustration below shows part of the first page ##of the Japanese output sample. It shows both horizontal and vertical ##writing, and illustrates the ability to mix variable-width Latin ##characters in Asian sentences. The choice of horizontal and vertical ##writing is determined by the encoding, which ends in 'H' or 'V'. ##Whether an encoding uses fixed-width or variable-width versions ##of Latin characters also depends on the encoding used; see the definitions ##below.""") ## ##Illustration(image("../images/jpn.gif", width=531*0.50, ##height=435*0.50), 'Output from test_multibyte_jpn.py') ## ##caption(""" ##Output from test_multibyte_jpn.py ##""") disc("""In previous versions of the ReportLab PDF Library, we had to make use of Adobe's CMap files (located near Acrobat Reader if the Asian Language packs were installed). Now that we only have one encoding to deal with, the character width data is embedded in the package, and CMap files are not needed for generation. The CMap search path in ^rl_config.py^ is now deprecated and has no effect if you restrict yourself to UnicodeCIDFont. """) heading3("TrueType fonts with Asian characters") disc(""" This is the easy way to do it. No special handling at all is needed to work with Asian TrueType fonts. Windows users who have installed, for example, Japanese as an option in Control Panel, will have a font "msmincho.ttf" which can be used. However, be aware that it takes time to parse the fonts, and that quite large subsets may need to be embedded in your PDFs. We can also now parse files ending in .ttc, which are a slight variation of .ttf. """) heading3("To Do") disc("""We expect to be developing this area of the package for some time.accept2dyear Here is an outline of the main priorities. We welcome help!""") bullet(""" Ensure that we have accurate character metrics for all encodings in horizontal and vertical writing.""") bullet(""" Add options to ^UnicodeCIDFont^ to allow vertical and proportional variants where the font permits it.""") bullet(""" Improve the word wrapping code in paragraphs and allow vertical writing.""") CPage(5) heading2("RenderPM tests") disc("""This may also be the best place to mention the test function of $reportlab/graphics/renderPM.py$, which can be considered the cannonical place for tests which exercise renderPM (the "PixMap Renderer", as opposed to renderPDF, renderPS or renderSVG).""") disc("""If you run this from the command line, you should see lots of output like the following.""") eg("""C:\\code\\reportlab\\graphics>renderPM.py wrote pmout\\renderPM0.gif wrote pmout\\renderPM0.tif wrote pmout\\renderPM0.png wrote pmout\\renderPM0.jpg wrote pmout\\renderPM0.pct ... wrote pmout\\renderPM12.gif wrote pmout\\renderPM12.tif wrote pmout\\renderPM12.png wrote pmout\\renderPM12.jpg wrote pmout\\renderPM12.pct wrote pmout\\index.html""") disc("""This runs a number of tests progressing from a "Hello World" test, through various tests of Lines; text strings in a number of sizes, fonts, colours and alignments; the basic shapes; translated and rotated groups; scaled coordinates; rotated strings; nested groups; anchoring and non-standard fonts.""") disc("""It creates a subdirectory called $pmout$, writes the image files into it, and writes an $index.html$ page which makes it easy to refer to all the results.""") disc("""The font-related tests which you may wish to look at are test #11 ('Text strings in a non-standard font') and test #12 ('Test Various Fonts').""") ##### FILL THEM IN reportlab-3.3.0/docs/userguide/ch7_custom.py0000664000175000017500000000515612661063723020431 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch7_custom.py from tools.docco.rl_doc_utils import * heading1("Writing your own $Flowable$ Objects") disc(""" Flowables are intended to be an open standard for creating reusable report content, and you can easily create your own objects. We hope that over time we will build up a library of contributions, giving reportlab users a rich selection of charts, graphics and other "report widgets" they can use in their own reports. This section shows you how to create your own flowables.""") todo("""we should put the Figure class in the standard library, as it is a very useful base.""") heading2("A very simple $Flowable$") disc(""" Recall the $hand$ function from the $pdfgen$ section of this user guide which generated a drawing of a hand as a closed figure composed from Bezier curves. """) illust(examples.hand, "a hand") disc(""" To embed this or any other drawing in a Platypus flowable we must define a subclass of $Flowable$ with at least a $wrap$ method and a $draw$ method. """) eg(examples.testhandannotation) disc(""" The $wrap$ method must provide the size of the drawing -- it is used by the Platypus mainloop to decide whether this element fits in the space remaining on the current frame. The $draw$ method performs the drawing of the object after the Platypus mainloop has translated the $(0,0)$ origin to an appropriate location in an appropriate frame. """) disc(""" Below are some example uses of the $HandAnnotation$ flowable. """) from reportlab.lib.colors import blue, pink, yellow, cyan, brown from reportlab.lib.units import inch handnote() disc("""The default.""") handnote(size=inch) disc("""Just one inch high.""") handnote(xoffset=3*inch, size=inch, strokecolor=blue, fillcolor=cyan) disc("""One inch high and shifted to the left with blue and cyan.""") heading2("Modifying a Built in $Flowable$") disc("""To modify an existing flowable, you should create a derived class and override the methods you need to change to get the desired behaviour""") disc("""As an example to create a rotated image you need to override the wrap and draw methods of the existing Image class""") import os from reportlab.platypus import * I = '../images/replogo.gif' EmbeddedCode(""" class RotatedImage(Image): def wrap(self,availWidth,availHeight): h, w = Image.wrap(self,availHeight,availWidth) return w, h def draw(self): self.canv.rotate(90) Image.draw(self) I = RotatedImage('%s') I.hAlign = 'CENTER' """ % I,'I') reportlab-3.3.0/docs/userguide/graph_concepts.py0000664000175000017500000002701412661063723021352 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details __version__='3.3.0' from tools.docco.rl_doc_utils import * heading2("General Concepts") disc(""" In this section we will present some of the more fundamental principles of the graphics library, which will show-up later in various places. """) heading3("Drawings and Renderers") disc(""" A Drawing is a platform-independent description of a collection of shapes. It is not directly associated with PDF, Postscript or any other output format. Fortunately, most vector graphics systems have followed the Postscript model and it is possible to describe shapes unambiguously. """) disc(""" A drawing contains a number of primitive Shapes. Normal shapes are those widely known as rectangles, circles, lines, etc. One special (logic) shape is a Group, which can hold other shapes and apply a transformation to them. Groups represent composites of shapes and allow to treat the composite as if it were a single shape. Just about anything can be built up from a small number of basic shapes. """) disc(""" The package provides several Renderers which know how to draw a drawing into different formats. These include PDF (renderPDF), Postscript (renderPS), and bitmap output (renderPM). The bitmap renderer uses Raph Levien's libart rasterizer and Fredrik Lundh's Python Imaging Library (PIL). The SVG renderer makes use of Python's standard library XML modules, so you don't need to install the XML-SIG's additional package named PyXML. If you have the right extensions installed, you can generate drawings in bitmap form for the web as well as vector form for PDF documents, and get "identical output". """) disc(""" The PDF renderer has special "privileges" - a Drawing object is also a Flowable and, hence, can be placed directly in the story of any Platypus document, or drawn directly on a Canvas with one line of code. In addition, the PDF renderer has a utility function to make a one-page PDF document quickly. """) disc(""" The SVG renderer is special as it is still pretty experimental. The SVG code it generates is not really optimised in any way and maps only the features available in ReportLab Graphics (RLG) to SVG. This means there is no support for SVG animation, interactivity, scripting or more sophisticated clipping, masking or graduation shapes. So, be careful, and please report any bugs you find! """) heading3("Coordinate System") disc(""" The Y-direction in our X-Y coordinate system points from the bottom up. This is consistent with PDF, Postscript and mathematical notation. It also appears to be more natural for people, especially when working with charts. Note that in other graphics models (such as SVG) the Y-coordinate points down. For the SVG renderer this is actually no problem as it will take your drawings and flip things as needed, so your SVG output looks just as expected. """) disc(""" The X-coordinate points, as usual, from left to right. So far there doesn't seem to be any model advocating the opposite direction - at least not yet (with interesting exceptions, as it seems, for Arabs looking at time series charts...). """) heading3("Getting Started") disc(""" Let's create a simple drawing containing the string "Hello World" and some special characters, displayed on top of a coloured rectangle. After creating it we will save the drawing to a standalone PDF file. """) eg(""" from reportlab.lib import colors from reportlab.graphics.shapes import * d = Drawing(400, 200) d.add(Rect(50, 50, 300, 100, fillColor=colors.yellow)) d.add(String(150,100, 'Hello World', fontSize=18, fillColor=colors.red)) d.add(String(180,86, 'Special characters \\ \\xc2\\xa2\\xc2\\xa9\\xc2\\xae\\xc2\\xa3\\xce\\xb1\\xce\\xb2', fillColor=colors.red)) from reportlab.graphics import renderPDF renderPDF.drawToFile(d, 'example1.pdf', 'My First Drawing') """) disc("This will produce a PDF file containing the following graphic:") from reportlab.graphics.shapes import * from reportlab.graphics import testshapes t = testshapes.getDrawing01() draw(t, "'Hello World'") disc(""" Each renderer is allowed to do whatever is appropriate for its format, and may have whatever API is needed. If it refers to a file format, it usually has a $drawToFile$ function, and that's all you need to know about the renderer. Let's save the same drawing in Encapsulated Postscript format: """) ##eg(""" ## from reportlab.graphics import renderPS ## renderPS.drawToFile(D, 'example1.eps', 'My First Drawing') ##""") eg(""" from reportlab.graphics import renderPS renderPS.drawToFile(d, 'example1.eps') """) disc(""" This will produce an EPS file with the identical drawing, which may be imported into publishing tools such as Quark Express. If we wanted to generate the same drawing as a bitmap file for a website, say, all we need to do is write code like this: """) eg(""" from reportlab.graphics import renderPM renderPM.drawToFile(d, 'example1.png', 'PNG') """) disc(""" Many other bitmap formats, like GIF, JPG, TIFF, BMP and PPN are genuinely available, making it unlikely you'll need to add external postprocessing steps to convert to the final format you need. """) disc(""" To produce an SVG file containing the identical drawing, which may be imported into graphical editing tools such as Illustrator all we need to do is write code like this: """) eg(""" from reportlab.graphics import renderSVG renderSVG.drawToFile(d, 'example1.svg') """) heading3("Attribute Verification") disc(""" Python is very dynamic and lets us execute statements at run time that can easily be the source for unexpected behaviour. One subtle 'error' is when assigning to an attribute that the framework doesn't know about because the used attribute's name contains a typo. Python lets you get away with it (adding a new attribute to an object, say), but the graphics framework will not detect this 'typo' without taking special counter-measures. """) disc(""" There are two verification techniques to avoid this situation. The default is for every object to check every assignment at run time, such that you can only assign to 'legal' attributes. This is what happens by default. As this imposes a small performance penalty, this behaviour can be turned off when you need it to be. """) eg(""" >>> r = Rect(10,10,200,100, fillColor=colors.red) >>> >>> r.fullColor = colors.green # note the typo >>> r.x = 'not a number' # illegal argument type >>> del r.width # that should confuse it """) disc(""" These statements would be caught by the compiler in a statically typed language, but Python lets you get away with it. The first error could leave you staring at the picture trying to figure out why the colors were wrong. The second error would probably become clear only later, when some back-end tries to draw the rectangle. The third, though less likely, results in an invalid object that would not know how to draw itself. """) eg(""" >>> r = shapes.Rect(10,10,200,80) >>> r.fullColor = colors.green Traceback (most recent call last): File "", line 1, in ? File "C:\\code\\users\\andy\\graphics\\shapes.py", line 254, in __setattr__ validateSetattr(self,attr,value) #from reportlab.lib.attrmap File "C:\\code\\users\\andy\\lib\\attrmap.py", line 74, in validateSetattr raise AttributeError, "Illegal attribute '%s' in class %s" % (name, obj.__class__.__name__) AttributeError: Illegal attribute 'fullColor' in class Rect >>> """) disc(""" This imposes a performance penalty, so this behaviour can be turned off when you need it to be. To do this, you should use the following lines of code before you first import reportlab.graphics.shapes: """) eg(""" >>> import reportlab.rl_config >>> reportlab.rl_config.shapeChecking = 0 >>> from reportlab.graphics import shapes >>> """) disc(""" Once you turn off $shapeChecking$, the classes are actually built without the verification hook; code should get faster, then. Currently the penalty seems to be about 25% on batches of charts, so it is hardly worth disabling. However, if we move the renderers to C in future (which is eminently possible), the remaining 75% would shrink to almost nothing and the saving from verification would be significant. """) disc(""" Each object, including the drawing itself, has a $verify()$ method. This either succeeds, or raises an exception. If you turn off automatic verification, then you should explicitly call $verify()$ in testing when developing the code, or perhaps once in a batch process. """) heading3("Property Editing") disc(""" A cornerstone of the reportlab/graphics which we will cover below is that you can automatically document widgets. This means getting hold of all of their editable properties, including those of their subcomponents. """) disc(""" Another goal is to be able to create GUIs and config files for drawings. A generic GUI can be built to show all editable properties of a drawing, and let you modify them and see the results. The Visual Basic or Delphi development environment are good examples of this kind of thing. In a batch charting application, a file could list all the properties of all the components in a chart, and be merged with a database query to make a batch of charts. """) disc(""" To support these applications we have two interfaces, $getProperties$ and $setProperties$, as well as a convenience method $dumpProperties$. The first returns a dictionary of the editable properties of an object; the second sets them en masse. If an object has publicly exposed 'children' then one can recursively set and get their properties too. This will make much more sense when we look at Widgets later on, but we need to put the support into the base of the framework. """) eg(""" >>> r = shapes.Rect(0,0,200,100) >>> import pprint >>> pprint.pprint(r.getProperties()) {'fillColor': Color(0.00,0.00,0.00), 'height': 100, 'rx': 0, 'ry': 0, 'strokeColor': Color(0.00,0.00,0.00), 'strokeDashArray': None, 'strokeLineCap': 0, 'strokeLineJoin': 0, 'strokeMiterLimit': 0, 'strokeWidth': 1, 'width': 200, 'x': 0, 'y': 0} >>> r.setProperties({'x':20, 'y':30, 'strokeColor': colors.red}) >>> r.dumpProperties() fillColor = Color(0.00,0.00,0.00) height = 100 rx = 0 ry = 0 strokeColor = Color(1.00,0.00,0.00) strokeDashArray = None strokeLineCap = 0 strokeLineJoin = 0 strokeMiterLimit = 0 strokeWidth = 1 width = 200 x = 20 y = 30 >>> """) disc(""" Note: $pprint$ is the standard Python library module that allows you to 'pretty print' output over multiple lines rather than having one very long line. """) disc(""" These three methods don't seem to do much here, but as we will see they make our widgets framework much more powerful when dealing with non-primitive objects. """) heading3("Naming Children") disc(""" You can add objects to the $Drawing$ and $Group$ objects. These normally go into a list of contents. However, you may also give objects a name when adding them. This allows you to refer to and possibly change any element of a drawing after constructing it. """) eg(""" >>> d = shapes.Drawing(400, 200) >>> s = shapes.String(10, 10, 'Hello World') >>> d.add(s, 'caption') >>> s.caption.text 'Hello World' >>> """) disc(""" Note that you can use the same shape instance in several contexts in a drawing; if you choose to use the same $Circle$ object in many locations (e.g. a scatter plot) and use different names to access it, it will still be a shared object and the changes will be global. """) disc(""" This provides one paradigm for creating and modifying interactive drawings. """) reportlab-3.3.0/docs/userguide/genuserguide.py0000664000175000017500000000672512661063723021047 0ustar rptlabrptlab#!/bin/env python #Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/genuserguide.py __version__='3.3.0' __doc__ = """ This module contains the script for building the user guide. """ def run(pagesize=None, verbose=0, outDir=None): import sys,os from reportlab.lib.utils import open_and_read, asUnicode cwd = os.getcwd() docsDir=os.path.dirname(os.path.dirname(sys.argv[0]) or cwd) topDir=os.path.dirname(docsDir) if not outDir: outDir=docsDir G = {} sys.path.insert(0,topDir) from reportlab.pdfbase.pdfmetrics import registerFontFamily from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont pdfmetrics.registerFont(TTFont('Vera', 'Vera.ttf')) pdfmetrics.registerFont(TTFont('VeraBd', 'VeraBd.ttf')) pdfmetrics.registerFont(TTFont('VeraIt', 'VeraIt.ttf')) pdfmetrics.registerFont(TTFont('VeraBI', 'VeraBI.ttf')) registerFontFamily('Vera',normal='Vera',bold='VeraBd',italic='VeraIt',boldItalic='VeraBI') from tools.docco.rl_doc_utils import setStory, getStory, RLDocTemplate, defaultPageSize, H1, H2, H3, H4 from tools.docco import rl_doc_utils exec('from tools.docco.rl_doc_utils import *', G, G) destfn = os.path.join(outDir,'reportlab-userguide.pdf') doc = RLDocTemplate(destfn,pagesize = pagesize or defaultPageSize) #this builds the story setStory() for f in ( 'ch1_intro', 'ch2_graphics', 'ch2a_fonts', 'ch3_pdffeatures', 'ch4_platypus_concepts', 'ch5_paragraphs', 'ch6_tables', 'ch7_custom', 'graph_intro', 'graph_concepts', 'graph_charts', 'graph_shapes', 'graph_widgets', 'app_demos', ): #python source is supposed to be utf8 these days exec(asUnicode(open_and_read(f+'.py')), G, G) del G story = getStory() if verbose: print('Built story contains %d flowables...' % len(story)) doc.multiBuild(story) if verbose: print('Saved "%s"' % destfn) def makeSuite(): "standard test harness support - run self as separate process" from tests.utils import ScriptThatMakesFileTest return ScriptThatMakesFileTest('../docs/userguide', 'genuserguide.py', 'reportlab-userguide.pdf') def main(): import sys outDir = [x for x in sys.argv if x[:9]=='--outdir='] if outDir: outDir = outDir[0] sys.argv.remove(outDir) outDir = outDir[9:] else: outDir = None verbose = '-s' not in sys.argv if not verbose: sys.argv.remove('-s') timing = '-timing' in sys.argv if timing: sys.argv.remove('-timing') prof = '-prof' in sys.argv if prof: sys.argv.remove('-prof') if len(sys.argv) > 1: try: pagesize = (w,h) = eval(sys.argv[1]) except: print('Expected page size in argument 1', sys.argv[1]) raise if verbose: print('set page size to',sys.argv[1]) else: pagesize = None if timing: from time import time t0 = time() run(pagesize, verbose,outDir) if verbose: print('Generation of userguide took %.2f seconds' % (time()-t0)) elif prof: import profile profile.run('run(pagesize,verbose,outDir)','genuserguide.stats') else: run(pagesize, verbose,outDir) if __name__=="__main__": main() reportlab-3.3.0/docs/userguide/ch4_platypus_concepts.py0000664000175000017500000004561612661063723022700 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch4_platypus_concepts.py from tools.docco.rl_doc_utils import * #####################################################################################################3 heading1("PLATYPUS - Page Layout and Typography Using Scripts") heading2("Design Goals") disc(""" Platypus stands for "Page Layout and Typography Using Scripts". It is a high level page layout library which lets you programmatically create complex documents with a minimum of effort. """) disc(""" The design of Platypus seeks to separate "high level" layout decisions from the document content as much as possible. Thus, for example, paragraphs are constructed using paragraph styles and pages are constructed using page templates with the intention that hundreds of documents with thousands of pages can be reformatted to different style specifications with the modifications of a few lines in a single shared file which contains the paragraph styles and page layout specifications. """) disc(""" The overall design of Platypus can be thought of has having several layers, top down, these are""") disc("$DocTemplates$ the outermost container for the document;") disc("$PageTemplates$ specifications for layouts of pages of various kinds;") disc("$Frames$ specifications of regions in pages that can contain flowing text or graphics.") disc("""$Flowables$ text or graphic elements that should be "flowed into the document (i.e. things like images, paragraphs and tables, but not things like page footers or fixed page graphics).""") disc("""$pdfgen.Canvas$ the lowest level which ultimately receives the painting of the document from the other layers.""") illust(examples.doctemplateillustration, "Illustration of DocTemplate structure") disc(""" The illustration above graphically illustrates the concepts of $DocTemplates$, $PageTemplates$ and $Flowables$. It is deceptive, however, because each of the $PageTemplates$ actually may specify the format for any number of pages (not just one as might be inferred from the diagram). """) disc(""" $DocTemplates$ contain one or more $PageTemplates$ each of which contain one or more $Frames$. $Flowables$ are things which can be flowed into a $Frame$ e.g. a $Paragraph$ or a $Table$. """) disc(""" To use platypus you create a document from a $DocTemplate$ class and pass a list of $Flowable$s to its $build$ method. The document $build$ method knows how to process the list of flowables into something reasonable. """) disc(""" Internally the $DocTemplate$ class implements page layout and formatting using various events. Each of the events has a corresponding handler method called $handle_XXX$ where $XXX$ is the event name. A typical event is $frameBegin$ which occurs when the machinery begins to use a frame for the first time. """) disc(""" A Platypus story consists of a sequence of basic elements called $Flowables$ and these elements drive the data driven Platypus formatting engine. To modify the behavior of the engine a special kind of flowable, $ActionFlowables$, tell the layout engine to, for example, skip to the next column or change to another $PageTemplate$. """) heading2("""Getting started""") disc("""Consider the following code sequence which provides a very simple "hello world" example for Platypus.""") eg(examples.platypussetup) disc("""First we import some constructors, some paragraph styles and other conveniences from other modules.""") eg(examples.platypusfirstpage) disc("""We define the fixed features of the first page of the document with the function above.""") eg(examples.platypusnextpage) disc("""Since we want pages after the first to look different from the first we define an alternate layout for the fixed features of the other pages. Note that the two functions above use the $pdfgen$ level canvas operations to paint the annotations for the pages. """) eg(examples.platypusgo) disc(""" Finally, we create a story and build the document. Note that we are using a "canned" document template here which comes pre-built with page templates. We are also using a pre-built paragraph style. We are only using two types of flowables here -- $Spacers$ and $Paragraphs$. The first $Spacer$ ensures that the Paragraphs skip past the title string. """) disc(""" To see the output of this example program run the module $docs/userguide/examples.py$ (from the ReportLab $docs$ distribution) as a "top level script". The script interpretation $python examples.py$ will generate the Platypus output $phello.pdf$. """) heading2("$Flowables$") disc(""" $Flowables$ are things which can be drawn and which have $wrap$, $draw$ and perhaps $split$ methods. $Flowable$ is an abstract base class for things to be drawn and an instance knows its size and draws in its own coordinate system (this requires the base API to provide an absolute coordinate system when the $Flowable.draw$ method is called). To get an instance use $f=Flowable()$. """) disc(""" It should be noted that the $Flowable$ class is an abstract class and is normally only used as a base class. """) k=startKeep() disc(""" To illustrate the general way in which $Flowables$ are used we show how a derived class $Paragraph$ is used and drawn on a canvas. $Paragraphs$ are so important they will get a whole chapter to themselves. """) eg(""" from reportlab.lib.styles import getSampleStyleSheet from reportlab.platypus import Paragraph from reportlab.pdfgen.canvas import Canvas styleSheet = getSampleStyleSheet() style = styleSheet['BodyText'] P=Paragraph('This is a very silly example',style) canv = Canvas('doc.pdf') aW = 460 # available width and height aH = 800 w,h = P.wrap(aW, aH) # find required space if w<=aW and h<=aH: P.drawOn(canv,0,aH) aH = aH - h # reduce the available height canv.save() else: raise ValueError, "Not enough room" """) endKeep(k) heading3("$Flowable$ User Methods") eg(""" Flowable.draw() """) disc("""This will be called to ask the flowable to actually render itself. The $Flowable$ class does not implement $draw$. The calling code should ensure that the flowable has an attribute $canv$ which is the $pdfgen.Canvas$ which should be drawn to an that the $Canvas$ is in an appropriate state (as regards translations rotations, etc). Normally this method will only be called internally by the $drawOn$ method. Derived classes must implement this method. """) eg(""" Flowable.drawOn(canvas,x,y) """) disc(""" This is the method which controlling programs use to render the flowable to a particular canvas. It handles the translation to the canvas coordinate (x,y) and ensuring that the flowable has a $canv$ attribute so that the $draw$ method (which is not implemented in the base class) can render in an absolute coordinate frame. """) eg(""" Flowable.wrap(availWidth, availHeight) """) disc("""This will be called by the enclosing frame before objects are asked their size, drawn or whatever. It returns the size actually used.""") eg(""" Flowable.split(self, availWidth, availheight): """) disc("""This will be called by more sophisticated frames when wrap fails. Stupid flowables should return [] meaning that they are unable to split. Clever flowables should split themselves and return a list of flowables. It is up to the client code to ensure that repeated attempts to split are avoided. If the space is sufficient the split method should return [self]. Otherwise the flowable should rearrange itself and return a list $[f0,...]$ of flowables which will be considered in order. The implemented split method should avoid changing $self$ as this will allow sophisticated layout mechanisms to do multiple passes over a list of flowables. """) heading2("Guidelines for flowable positioning") disc("""Two methods, which by default return zero, provide guidance on vertical spacing of flowables: """) eg(""" Flowable.getSpaceAfter(self): Flowable.getSpaceBefore(self): """) disc("""These methods return how much space should follow or precede the flowable. The space doesn't belong to the flowable itself i.e. the flowable's $draw$ method shouldn't consider it when rendering. Controlling programs will use the values returned in determining how much space is required by a particular flowable in context. """) disc("""All flowables have an $hAlign$ property: $('LEFT', 'RIGHT', 'CENTER' or 'CENTRE')$. For paragraphs, which fill the full width of the frame, this has no effect. For tables, images or other objects which are less than the width of the frame, this determines their horizontal placement. """) disc("""The chapters which follow will cover the most important specific types of flowables: Paragraphs and Tables.""") heading2("Frames") disc(""" $Frames$ are active containers which are themselves contained in $PageTemplates$. $Frames$ have a location and size and maintain a concept of remaining drawable space. The command """) eg(""" Frame(x1, y1, width,height, leftPadding=6, bottomPadding=6, rightPadding=6, topPadding=6, id=None, showBoundary=0) """) disc("""creates a $Frame$ instance with lower left hand corner at coordinate $(x1,y1)$ (relative to the canvas at use time) and with dimensions $width$ x $height$. The $Padding$ arguments are positive quantities used to reduce the space available for drawing. The $id$ argument is an identifier for use at runtime e.g. 'LeftColumn' or 'RightColumn' etc. If the $showBoundary$ argument is non-zero then the boundary of the frame will get drawn at run time (this is useful sometimes). """) heading3("$Frame$ User Methods") eg(""" Frame.addFromList(drawlist, canvas) """) disc("""consumes $Flowables$ from the front of $drawlist$ until the frame is full. If it cannot fit one object, raises an exception.""") eg(""" Frame.split(flowable,canv) """) disc('''Asks the flowable to split using up the available space and return the list of flowables. ''') eg(""" Frame.drawBoundary(canvas) """) disc("draws the frame boundary as a rectangle (primarily for debugging).") heading3("Using $Frames$") disc(""" $Frames$ can be used directly with canvases and flowables to create documents. The $Frame.addFromList$ method handles the $wrap$ & $drawOn$ calls for you. You don't need all of the Platypus machinery to get something useful into PDF. """) eg(""" from reportlab.pdfgen.canvas import Canvas from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.units import inch from reportlab.platypus import Paragraph, Frame styles = getSampleStyleSheet() styleN = styles['Normal'] styleH = styles['Heading1'] story = [] #add some flowables story.append(Paragraph("This is a Heading",styleH)) story.append(Paragraph("This is a paragraph in Normal style.", styleN)) c = Canvas('mydoc.pdf') f = Frame(inch, inch, 6*inch, 9*inch, showBoundary=1) f.addFromList(story,c) c.save() """) heading2("Documents and Templates") disc(""" The $BaseDocTemplate$ class implements the basic machinery for document formatting. An instance of the class contains a list of one or more $PageTemplates$ that can be used to describe the layout of information on a single page. The $build$ method can be used to process a list of $Flowables$ to produce a PDF document. """) CPage(3.0) heading3("The $BaseDocTemplate$ class") eg(""" BaseDocTemplate(self, filename, pagesize=defaultPageSize, pageTemplates=[], showBoundary=0, leftMargin=inch, rightMargin=inch, topMargin=inch, bottomMargin=inch, allowSplitting=1, title=None, author=None, _pageBreakQuick=1, encrypt=None) """) disc(""" creates a document template suitable for creating a basic document. It comes with quite a lot of internal machinery, but no default page templates. The required $filename$ can be a string, the name of a file to receive the created PDF document; alternatively it can be an object which has a $write$ method such as a $BytesIO$ or $file$ or $socket$. """) disc(""" The allowed arguments should be self explanatory, but $showBoundary$ controls whether or not $Frame$ boundaries are drawn which can be useful for debugging purposes. The $allowSplitting$ argument determines whether the builtin methods should try to split individual $Flowables$ across $Frames$. The $_pageBreakQuick$ argument determines whether an attempt to do a page break should try to end all the frames on the page or not, before ending the page. The encrypt argument determines wether or not and how the document is encrypted. By default, the document is not encrypted. If $encrypt$ is a string object, it is used as the user password for the pdf. If $encrypt$ is an instance of $reportlab.lib.pdfencrypt.StandardEncryption$, this object is used to encrypt the pdf. This allows more finegrained control over the encryption settings. """) heading4("User $BaseDocTemplate$ Methods") disc("""These are of direct interest to client programmers in that they are normally expected to be used. """) eg(""" BaseDocTemplate.addPageTemplates(self,pageTemplates) """) disc(""" This method is used to add one or a list of $PageTemplates$ to an existing documents. """) eg(""" BaseDocTemplate.build(self, flowables, filename=None, canvasmaker=canvas.Canvas) """) disc(""" This is the main method which is of interest to the application programmer. Assuming that the document instance is correctly set up the $build$ method takes the story in the shape of the list of flowables (the $flowables$ argument) and loops through the list forcing the flowables one at a time through the formatting machinery. Effectively this causes the $BaseDocTemplate$ instance to issue calls to the instance $handle_XXX$ methods to process the various events. """) heading4("User Virtual $BaseDocTemplate$ Methods") disc(""" These have no semantics at all in the base class. They are intended as pure virtual hooks into the layout machinery. Creators of immediately derived classes can override these without worrying about affecting the properties of the layout engine. """) eg(""" BaseDocTemplate.afterInit(self) """) disc(""" This is called after initialisation of the base class; a derived class could overide the method to add default $PageTemplates$. """) eg(""" BaseDocTemplate.afterPage(self) """) disc("""This is called after page processing, and immediately after the afterDrawPage method of the current page template. A derived class could use this to do things which are dependent on information in the page such as the first and last word on the page of a dictionary. """) eg(""" BaseDocTemplate.beforeDocument(self) """) disc("""This is called before any processing is done on the document, but after the processing machinery is ready. It can therefore be used to do things to the instance\'s $pdfgen.canvas$ and the like. """) eg(""" BaseDocTemplate.beforePage(self) """) disc("""This is called at the beginning of page processing, and immediately before the beforeDrawPage method of the current page template. It could be used to reset page specific information holders.""") eg(""" BaseDocTemplate.filterFlowables(self,flowables) """) disc("""This is called to filter flowables at the start of the main handle_flowable method. Upon return if flowables[0] has been set to None it is discarded and the main method returns immediately. """) eg(""" BaseDocTemplate.afterFlowable(self, flowable) """) disc("""Called after a flowable has been rendered. An interested class could use this hook to gather information about what information is present on a particular page or frame.""") heading4("$BaseDocTemplate$ Event handler Methods") disc(""" These methods constitute the greater part of the layout engine. Programmers shouldn't have to call or override these methods directly unless they are trying to modify the layout engine. Of course, the experienced programmer who wants to intervene at a particular event, $XXX$, which does not correspond to one of the virtual methods can always override and call the base method from the drived class version. We make this easy by providing a base class synonym for each of the handler methods with the same name prefixed by an underscore '_'. """) eg(""" def handle_pageBegin(self): doStuff() BaseDocTemplate.handle_pageBegin(self) doMoreStuff() #using the synonym def handle_pageEnd(self): doStuff() self._handle_pageEnd() doMoreStuff() """) disc(""" Here we list the methods only as an indication of the events that are being handled. Interested programmers can take a look at the source. """) eg(""" handle_currentFrame(self,fx) handle_documentBegin(self) handle_flowable(self,flowables) handle_frameBegin(self,*args) handle_frameEnd(self) handle_nextFrame(self,fx) handle_nextPageTemplate(self,pt) handle_pageBegin(self) handle_pageBreak(self) handle_pageEnd(self) """) disc(""" Using document templates can be very easy; $SimpleDoctemplate$ is a class derived from $BaseDocTemplate$ which provides its own $PageTemplate$ and $Frame$ setup. """) eg(""" from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.pagesizes import letter from reportlab.platypus import Paragraph, SimpleDocTemplate styles = getSampleStyleSheet() styleN = styles['Normal'] styleH = styles['Heading1'] story = [] #add some flowables story.append(Paragraph("This is a Heading",styleH)) story.append(Paragraph("This is a paragraph in Normal style.", styleN)) doc = SimpleDocTemplate('mydoc.pdf',pagesize = letter) doc.build(story) """) heading3("$PageTemplates$") disc(""" The $PageTemplate$ class is a container class with fairly minimal semantics. Each instance contains a list of $Frames$ and has methods which should be called at the start and end of each page. """) eg("PageTemplate(id=None,frames=[],onPage=_doNothing,onPageEnd=_doNothing)") disc(""" is used to initialize an instance, the $frames$ argument should be a list of $Frames$ whilst the optional $onPage$ and $onPageEnd$ arguments are callables which should have signature $def XXX(canvas,document)$ where $canvas$ and $document$ are the canvas and document being drawn. These routines are intended to be used to paint non-flowing (i.e. standard) parts of pages. These attribute functions are exactly parallel to the pure virtual methods $PageTemplate.beforPage$ and $PageTemplate.afterPage$ which have signature $beforPage(self,canvas,document)$. The methods allow class derivation to be used to define standard behaviour, whilst the attributes allow instance changes. The $id$ argument is used at run time to perform $PageTemplate$ switching so $id='FirstPage'$ or $id='TwoColumns'$ are typical. """) reportlab-3.3.0/docs/userguide/graph_charts.py0000664000175000017500000014704112661063723021023 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details __version__='3.3.0' from tools.docco.rl_doc_utils import * from reportlab.graphics.shapes import * heading2("Charts") disc(""" The motivation for much of this is to create a flexible chart package. This section presents a treatment of the ideas behind our charting model, what the design goals are and what components of the chart package already exist. """) heading3("Design Goals") disc("Here are some of the design goals: ") disc("Make simple top-level use really simple ") disc("""It should be possible to create a simple chart with minimum lines of code, yet have it 'do the right things' with sensible automatic settings. The pie chart snippets above do this. If a real chart has many subcomponents, you still should not need to interact with them unless you want to customize what they do.""") disc("Allow precise positioning ") disc("""An absolute requirement in publishing and graphic design is to control the placing and style of every element. We will try to have properties that specify things in fixed sizes and proportions of the drawing, rather than having automatic resizing. Thus, the 'inner plot rectangle' will not magically change when you make the font size of the y labels bigger, even if this means your labels can spill out of the left edge of the chart rectangle. It is your job to preview the chart and choose sizes and spaces which will work.""") disc("""Some things do need to be automatic. For example, if you want to fit N bars into a 200 point space and don't know N in advance, we specify bar separation as a percentage of the width of a bar rather than a point size, and let the chart work it out. This is still deterministic and controllable.""") disc("Control child elements individually or as a group") disc("""We use smart collection classes that let you customize a group of things, or just one of them. For example you can do this in our experimental pie chart:""") eg(""" d = Drawing(400,200) pc = Pie() pc.x = 150 pc.y = 50 pc.data = [10,20,30,40,50,60] pc.labels = ['a','b','c','d','e','f'] pc.slices.strokeWidth=0.5 pc.slices[3].popout = 20 pc.slices[3].strokeWidth = 2 pc.slices[3].strokeDashArray = [2,2] pc.slices[3].labelRadius = 1.75 pc.slices[3].fontColor = colors.red d.add(pc, '') """) disc("""pc.slices[3] actually lazily creates a little object which holds information about the slice in question; this will be used to format a fourth slice at draw-time if there is one.""") disc("Only expose things you should change ") disc("""It would be wrong from a statistical viewpoint to let you directly adjust the angle of one of the pie wedges in the above example, since that is determined by the data. So not everything will be exposed through the public properties. There may be 'back doors' to let you violate this when you really need to, or methods to provide advanced functionality, but in general properties will be orthogonal.""") disc("Composition and component based ") disc("""Charts are built out of reusable child widgets. A Legend is an easy-to-grasp example. If you need a specialized type of legend (e.g. circular colour swatches), you should subclass the standard Legend widget. Then you could either do something like...""") eg(""" c = MyChartWithLegend() c.legend = MyNewLegendClass() # just change it c.legend.swatchRadius = 5 # set a property only relevant to the new one c.data = [10,20,30] # and then configure as usual... """) disc("""...or create/modify your own chart or drawing class which creates one of these by default. This is also very relevant for time series charts, where there can be many styles of x axis.""") disc("""Top level chart classes will create a number of such components, and then either call methods or set private properties to tell them their height and position - all the stuff which should be done for you and which you cannot customise. We are working on modelling what the components should be and will publish their APIs here as a consensus emerges.""") disc("Multiples ") disc("""A corollary of the component approach is that you can create diagrams with multiple charts, or custom data graphics. Our favourite example of what we are aiming for is the weather report in our gallery contributed by a user; we'd like to make it easy to create such drawings, hook the building blocks up to their legends, and feed that data in a consistent way.""") disc("""(If you want to see the image, it is available on our website
here)""") ##heading3("Key Concepts and Components") heading3("Overview") disc("""A chart or plot is an object which is placed on a drawing; it is not itself a drawing. You can thus control where it goes, put several on the same drawing, or add annotations.""") disc("""Charts have two axes; axes may be Value or Category axes. Axes in turn have a Labels property which lets you configure all text labels or each one individually. Most of the configuration details which vary from chart to chart relate to axis properties, or axis labels.""") disc("""Objects expose properties through the interfaces discussed in the previous section; these are all optional and are there to let the end user configure the appearance. Things which must be set for a chart to work, and essential communication between a chart and its components, are handled through methods.""") disc("""You can subclass any chart component and use your replacement instead of the original provided you implement the essential methods and properties.""") heading2("Labels") disc(""" A label is a string of text attached to some chart element. They are used on axes, for titles or alongside axes, or attached to individual data points. Labels may contain newline characters, but only one font. """) disc("""The text and 'origin' of a label are typically set by its parent object. They are accessed by methods rather than properties. Thus, the X axis decides the 'reference point' for each tickmark label and the numeric or date text for each label. However, the end user can set properties of the label (or collection of labels) directly to affect its position relative to this origin and all of its formatting.""") eg(""" from reportlab.graphics import shapes from reportlab.graphics.charts.textlabels import Label d = Drawing(200, 100) # mark the origin of the label d.add(Circle(100,90, 5, fillColor=colors.green)) lab = Label() lab.setOrigin(100,90) lab.boxAnchor = 'ne' lab.angle = 45 lab.dx = 0 lab.dy = -20 lab.boxStrokeColor = colors.green lab.setText('Some\nMulti-Line\nLabel') d.add(lab) """) from reportlab.graphics import shapes from reportlab.graphics.charts.textlabels import Label d = Drawing(200, 100) # mark the origin of the label d.add(Circle(100,90, 5, fillColor=colors.green)) lab = Label() lab.setOrigin(100,90) lab.boxAnchor = 'ne' lab.angle = 45 lab.dx = 0 lab.dy = -20 lab.boxStrokeColor = colors.green lab.setText('Some\nMulti-Line\nLabel') d.add(lab) draw(d, 'Label example') disc(""" In the drawing above, the label is defined relative to the green blob. The text box should have its north-east corner ten points down from the origin, and be rotated by 45 degrees about that corner. """) disc(""" At present labels have the following properties, which we believe are sufficient for all charts we have seen to date: """) disc("") data=[["Property", "Meaning"], ["dx", """The label's x displacement."""], ["dy", """The label's y displacement."""], ["angle", """The angle of rotation (counterclockwise) applied to the label."""], ["boxAnchor", "The label's box anchor, one of 'n', 'e', 'w', 's', 'ne', 'nw', 'se', 'sw'."], ["textAnchor", """The place where to anchor the label's text, one of 'start', 'middle', 'end'."""], ["boxFillColor", """The fill color used in the label's box."""], ["boxStrokeColor", "The stroke color used in the label's box."], ["boxStrokeWidth", """The line width of the label's box."""], ["fontName", """The label's font name."""], ["fontSize", """The label's font size."""], ["leading", """The leading value of the label's text lines."""], ["x", """The X-coordinate of the reference point."""], ["y", """The Y-coordinate of the reference point."""], ["width", """The label's width."""], ["height", """The label's height."""] ] t=Table(data, colWidths=(100,330)) t.setStyle(TableStyle([ ('FONT',(0,0),(-1,0),'Times-Bold',10,12), ('FONT',(0,1),(0,-1),'Courier',8,8), ('FONT',(1,1),(1,-1),'Times-Roman',10,12), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) getStory().append(t) caption("""Table - Label properties""") disc(""" To see many more examples of $Label$ objects with different combinations of properties, please have a look into the ReportLab test suite in the folder $tests$, run the script $test_charts_textlabels.py$ and look at the PDF document it generates! """) heading2("Axes") disc(""" We identify two basic kinds of axes - Value and Category ones. Both come in horizontal and vertical flavors. Both can be subclassed to make very specific kinds of axis. For example, if you have complex rules for which dates to display in a time series application, or want irregular scaling, you override the axis and make a new one. """) disc(""" Axes are responsible for determining the mapping from data to image coordinates; transforming points on request from the chart; drawing themselves and their tickmarks, gridlines and axis labels. """) disc(""" This drawing shows two axes, one of each kind, which have been created directly without reference to any chart: """) from reportlab.graphics import shapes from reportlab.graphics.charts.axes import XCategoryAxis,YValueAxis drawing = Drawing(400, 200) data = [(10, 20, 30, 40), (15, 22, 37, 42)] xAxis = XCategoryAxis() xAxis.setPosition(75, 75, 300) xAxis.configure(data) xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] xAxis.labels.boxAnchor = 'n' xAxis.labels[3].dy = -15 xAxis.labels[3].angle = 30 xAxis.labels[3].fontName = 'Times-Bold' yAxis = YValueAxis() yAxis.setPosition(50, 50, 125) yAxis.configure(data) drawing.add(xAxis) drawing.add(yAxis) draw(drawing, 'Two isolated axes') disc("Here is the code that created them: ") eg(""" from reportlab.graphics import shapes from reportlab.graphics.charts.axes import XCategoryAxis,YValueAxis drawing = Drawing(400, 200) data = [(10, 20, 30, 40), (15, 22, 37, 42)] xAxis = XCategoryAxis() xAxis.setPosition(75, 75, 300) xAxis.configure(data) xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] xAxis.labels.boxAnchor = 'n' xAxis.labels[3].dy = -15 xAxis.labels[3].angle = 30 xAxis.labels[3].fontName = 'Times-Bold' yAxis = YValueAxis() yAxis.setPosition(50, 50, 125) yAxis.configure(data) drawing.add(xAxis) drawing.add(yAxis) """) disc(""" Remember that, usually, you won't have to create axes directly; when using a standard chart, it comes with ready-made axes. The methods are what the chart uses to configure it and take care of the geometry. However, we will talk through them in detail below. The orthogonally dual axes to those we describe have essentially the same properties, except for those refering to ticks. """) heading3("XCategoryAxis class") disc(""" A Category Axis doesn't really have a scale; it just divides itself into equal-sized buckets. It is simpler than a value axis. The chart (or programmer) sets its location with the method $setPosition(x, y, length)$. The next stage is to show it the data so that it can configure itself. This is easy for a category axis - it just counts the number of data points in one of the data series. The $reversed$ attribute (if 1) indicates that the categories should be reversed. When the drawing is drawn, the axis can provide some help to the chart with its $scale()$ method, which tells the chart where a given category begins and ends on the page. We have not yet seen any need to let people override the widths or positions of categories. """) disc("An XCategoryAxis has the following editable properties:") disc("") data=[["Property", "Meaning"], ["visible", """Should the axis be drawn at all? Sometimes you don't want to display one or both axes, but they still need to be there as they manage the scaling of points."""], ["strokeColor", "Color of the axis"], ["strokeDashArray", """Whether to draw axis with a dash and, if so, what kind. Defaults to None"""], ["strokeWidth", "Width of axis in points"], ["tickUp", """How far above the axis should the tick marks protrude? (Note that making this equal to chart height gives you a gridline)"""], ["tickDown", """How far below the axis should the tick mark protrude?"""], ["categoryNames", """Either None, or a list of strings. This should have the same length as each data series."""], ["labels", """A collection of labels for the tick marks. By default the 'north' of each text label (i.e top centre) is positioned 5 points down from the centre of each category on the axis. You may redefine any property of the whole label group or of any one label. If categoryNames=None, no labels are drawn."""], ["title", """Not Implemented Yet. This needs to be like a label, but also lets you set the text directly. It would have a default location below the axis."""]] t=Table(data, colWidths=(100,330)) t.setStyle(TableStyle([ ('FONT',(0,0),(-1,0),'Times-Bold',10,12), ('FONT',(0,1),(0,-1),'Courier',8,8), ('FONT',(1,1),(1,-1),'Times-Roman',10,12), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) getStory().append(t) caption("""Table - XCategoryAxis properties""") heading3("YValueAxis") disc(""" The left axis in the diagram is a YValueAxis. A Value Axis differs from a Category Axis in that each point along its length corresponds to a y value in chart space. It is the job of the axis to configure itself, and to convert Y values from chart space to points on demand to assist the parent chart in plotting. """) disc(""" $setPosition(x, y, length)$ and $configure(data)$ work exactly as for a category axis. If you have not fully specified the maximum, minimum and tick interval, then $configure()$ results in the axis choosing suitable values. Once configured, the value axis can convert y data values to drawing space with the $scale()$ method. Thus: """) eg(""" >>> yAxis = YValueAxis() >>> yAxis.setPosition(50, 50, 125) >>> data = [(10, 20, 30, 40),(15, 22, 37, 42)] >>> yAxis.configure(data) >>> yAxis.scale(10) # should be bottom of chart 50.0 >>> yAxis.scale(40) # should be near the top 167.1875 >>> """) disc("""By default, the highest data point is aligned with the top of the axis, the lowest with the bottom of the axis, and the axis choose 'nice round numbers' for its tickmark points. You may override these settings with the properties below. """) disc("") data=[["Property", "Meaning"], ["visible", """Should the axis be drawn at all? Sometimes you don't want to display one or both axes, but they still need to be there as they manage the scaling of points."""], ["strokeColor", "Color of the axis"], ["strokeDashArray", """Whether to draw axis with a dash and, if so, what kind. Defaults to None"""], ["strokeWidth", "Width of axis in points"], ["tickLeft", """How far to the left of the axis should the tick marks protrude? (Note that making this equal to chart height gives you a gridline)"""], ["tickRight", """How far to the right of the axis should the tick mark protrude?"""], ["valueMin", """The y value to which the bottom of the axis should correspond. Default value is None in which case the axis sets it to the lowest actual data point (e.g. 10 in the example above). It is common to set this to zero to avoid misleading the eye."""], ["valueMax", """The y value to which the top of the axis should correspond. Default value is None in which case the axis sets it to the highest actual data point (e.g. 42 in the example above). It is common to set this to a 'round number' so data bars do not quite reach the top."""], ["valueStep", """The y change between tick intervals. By default this is None, and the chart tries to pick 'nice round numbers' which are just wider than the minimumTickSpacing below."""], ["valueSteps", """A list of numbers at which to place ticks."""], ["minimumTickSpacing", """This is used when valueStep is set to None, and ignored otherwise. The designer specified that tick marks should be no closer than X points apart (based, presumably, on considerations of the label font size and angle). The chart tries values of the type 1,2,5,10,20,50,100... (going down below 1 if necessary) until it finds an interval which is greater than the desired spacing, and uses this for the step."""], ["labelTextFormat", """This determines what goes in the labels. Unlike a category axis which accepts fixed strings, the labels on a ValueAxis are supposed to be numbers. You may provide either a 'format string' like '%0.2f' (show two decimal places), or an arbitrary function which accepts a number and returns a string. One use for the latter is to convert a timestamp to a readable year-month-day format."""], ["title", """Not Implemented Yet. This needs to be like a label, but also lets you set the text directly. It would have a default location below the axis."""]] t=Table(data, colWidths=(100,330)) t.setStyle(TableStyle([ ('FONT',(0,0),(-1,0),'Times-Bold',10,12), ('FONT',(0,1),(0,-1),'Courier',8,8), ('FONT',(1,1),(1,-1),'Times-Roman',10,12), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) getStory().append(t) caption("""Table - YValueAxis properties""") disc(""" The $valueSteps$ property lets you explicitly specify the tick mark locations, so you don't have to follow regular intervals. Hence, you can plot month ends and month end dates with a couple of helper functions, and without needing special time series chart classes. The following code show how to create a simple $XValueAxis$ with special tick intervals. Make sure to set the $valueSteps$ attribute before calling the configure method! """) eg(""" from reportlab.graphics.shapes import Drawing from reportlab.graphics.charts.axes import XValueAxis drawing = Drawing(400, 100) data = [(10, 20, 30, 40)] xAxis = XValueAxis() xAxis.setPosition(75, 50, 300) xAxis.valueSteps = [10, 15, 20, 30, 35, 40] xAxis.configure(data) xAxis.labels.boxAnchor = 'n' drawing.add(xAxis) """) from reportlab.graphics import shapes from reportlab.graphics.charts.axes import XValueAxis drawing = Drawing(400, 100) data = [(10, 20, 30, 40)] xAxis = XValueAxis() xAxis.setPosition(75, 50, 300) xAxis.valueSteps = [10, 15, 20, 30, 35, 40] xAxis.configure(data) xAxis.labels.boxAnchor = 'n' drawing.add(xAxis) draw(drawing, 'An axis with non-equidistant tick marks') disc(""" In addition to these properties, all axes classes have three properties describing how to join two of them to each other. Again, this is interesting only if you define your own charts or want to modify the appearance of an existing chart using such axes. These properties are listed here only very briefly for now, but you can find a host of sample functions in the module $reportlab/graphics/axes.py$ which you can examine... """) disc(""" One axis is joined to another, by calling the method $joinToAxis(otherAxis, mode, pos)$ on the first axis, with $mode$ and $pos$ being the properties described by $joinAxisMode$ and $joinAxisPos$, respectively. $'points'$ means to use an absolute value, and $'value'$ to use a relative value (both indicated by the the $joinAxisPos$ property) along the axis. """) disc("") data=[["Property", "Meaning"], ["joinAxis", """Join both axes if true."""], ["joinAxisMode", """Mode used for connecting axis ('bottom', 'top', 'left', 'right', 'value', 'points', None)."""], ["joinAxisPos", """Position at which to join with other axis."""], ] t=Table(data, colWidths=(100,330)) t.setStyle(TableStyle([ ('FONT',(0,0),(-1,0),'Times-Bold',10,12), ('FONT',(0,1),(0,-1),'Courier',8,8), ('FONT',(1,1),(1,-1),'Times-Roman',10,12), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) getStory().append(t) caption("""Table - Axes joining properties""") heading2("Bar Charts") disc(""" This describes our current $VerticalBarChart$ class, which uses the axes and labels above. We think it is step in the right direction but is is far from final. Note that people we speak to are divided about 50/50 on whether to call this a 'Vertical' or 'Horizontal' bar chart. We chose this name because 'Vertical' appears next to 'Bar', so we take it to mean that the bars rather than the category axis are vertical. """) disc(""" As usual, we will start with an example: """) from reportlab.graphics.shapes import Drawing from reportlab.graphics.charts.barcharts import VerticalBarChart drawing = Drawing(400, 200) data = [ (13, 5, 20, 22, 37, 45, 19, 4), (14, 6, 21, 23, 38, 46, 20, 5) ] bc = VerticalBarChart() bc.x = 50 bc.y = 50 bc.height = 125 bc.width = 300 bc.data = data bc.strokeColor = colors.black bc.valueAxis.valueMin = 0 bc.valueAxis.valueMax = 50 bc.valueAxis.valueStep = 10 bc.categoryAxis.labels.boxAnchor = 'ne' bc.categoryAxis.labels.dx = 8 bc.categoryAxis.labels.dy = -2 bc.categoryAxis.labels.angle = 30 bc.categoryAxis.categoryNames = ['Jan-99','Feb-99','Mar-99', 'Apr-99','May-99','Jun-99','Jul-99','Aug-99'] drawing.add(bc) draw(drawing, 'Simple bar chart with two data series') eg(""" # code to produce the above chart from reportlab.graphics.shapes import Drawing from reportlab.graphics.charts.barcharts import VerticalBarChart drawing = Drawing(400, 200) data = [ (13, 5, 20, 22, 37, 45, 19, 4), (14, 6, 21, 23, 38, 46, 20, 5) ] bc = VerticalBarChart() bc.x = 50 bc.y = 50 bc.height = 125 bc.width = 300 bc.data = data bc.strokeColor = colors.black bc.valueAxis.valueMin = 0 bc.valueAxis.valueMax = 50 bc.valueAxis.valueStep = 10 bc.categoryAxis.labels.boxAnchor = 'ne' bc.categoryAxis.labels.dx = 8 bc.categoryAxis.labels.dy = -2 bc.categoryAxis.labels.angle = 30 bc.categoryAxis.categoryNames = ['Jan-99','Feb-99','Mar-99', 'Apr-99','May-99','Jun-99','Jul-99','Aug-99'] drawing.add(bc) """) disc(""" Most of this code is concerned with setting up the axes and labels, which we have already covered. Here are the top-level properties of the $VerticalBarChart$ class: """) disc("") data=[["Property", "Meaning"], ["data", """This should be a "list of lists of numbers" or "list of tuples of numbers". If you have just one series, write it as data = [(10,20,30,42),]"""], ["x, y, width, height", """These define the inner 'plot rectangle'. We highlighted this with a yellow border above. Note that it is your job to place the chart on the drawing in a way which leaves room for all the axis labels and tickmarks. We specify this 'inner rectangle' because it makes it very easy to lay out multiple charts in a consistent manner."""], ["strokeColor", """Defaults to None. This will draw a border around the plot rectangle, which may be useful in debugging. Axes will overwrite this."""], ["fillColor", """Defaults to None. This will fill the plot rectangle with a solid color. (Note that we could implement dashArray etc. as for any other solid shape)"""], ["useAbsolute", """Defaults to 0. If 1, the three properties below are absolute values in points (which means you can make a chart where the bars stick out from the plot rectangle); if 0, they are relative quantities and indicate the proportional widths of the elements involved."""], ["barWidth", """As it says. Defaults to 10."""], ["groupSpacing", """Defaults to 5. This is the space between each group of bars. If you have only one series, use groupSpacing and not barSpacing to split them up. Half of the groupSpacing is used before the first bar in the chart, and another half at the end."""], ["barSpacing", """Defaults to 0. This is the spacing between bars in each group. If you wanted a little gap between green and red bars in the example above, you would make this non-zero."""], ["barLabelFormat", """Defaults to None. As with the YValueAxis, if you supply a function or format string then labels will be drawn next to each bar showing the numeric value. They are positioned automatically above the bar for positive values and below for negative ones."""], ["barLabels", """A collection of labels used to format all bar labels. Since this is a two-dimensional array, you may explicitly format the third label of the second series using this syntax: chart.barLabels[(1,2)].fontSize = 12"""], ["valueAxis", """The value axis, which may be formatted as described previously."""], ["categoryAxis", """The category axis, which may be formatted as described previously."""], ["title", """Not Implemented Yet. This needs to be like a label, but also lets you set the text directly. It would have a default location below the axis."""]] t=Table(data, colWidths=(100,330)) t.setStyle(TableStyle([ ('FONT',(0,0),(-1,0),'Times-Bold',10,12), ('FONT',(0,1),(0,-1),'Courier',8,8), ('FONT',(1,1),(1,-1),'Times-Roman',10,12), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) getStory().append(t) caption("""Table - VerticalBarChart properties""") disc(""" From this table we deduce that adding the following lines to our code above should double the spacing between bar groups (the $groupSpacing$ attribute has a default value of five points) and we should also see some tiny space between bars of the same group ($barSpacing$). """) eg(""" bc.groupSpacing = 10 bc.barSpacing = 2.5 """) disc(""" And, in fact, this is exactly what we can see after adding these lines to the code above. Notice how the width of the individual bars has changed as well. This is because the space added between the bars has to be 'taken' from somewhere as the total chart width stays unchanged. """) from reportlab.graphics.shapes import Drawing from reportlab.graphics.charts.barcharts import VerticalBarChart drawing = Drawing(400, 200) data = [ (13, 5, 20, 22, 37, 45, 19, 4), (14, 6, 21, 23, 38, 46, 20, 5) ] bc = VerticalBarChart() bc.x = 50 bc.y = 50 bc.height = 125 bc.width = 300 bc.data = data bc.strokeColor = colors.black bc.groupSpacing = 10 bc.barSpacing = 2.5 bc.valueAxis.valueMin = 0 bc.valueAxis.valueMax = 50 bc.valueAxis.valueStep = 10 bc.categoryAxis.labels.boxAnchor = 'ne' bc.categoryAxis.labels.dx = 8 bc.categoryAxis.labels.dy = -2 bc.categoryAxis.labels.angle = 30 bc.categoryAxis.categoryNames = ['Jan-99','Feb-99','Mar-99', 'Apr-99','May-99','Jun-99','Jul-99','Aug-99'] drawing.add(bc) draw(drawing, 'Like before, but with modified spacing') disc(""" Bars labels are automatically displayed for negative values below the lower end of the bar for positive values above the upper end of the other ones. """) disc(""" Stacked bars are also supported for vertical bar graphs. You enable this layout for your chart by setting the $style$ attribute to $'stacked'$ on the $categoryAxis$. """) eg(""" bc.categoryAxis.style = 'stacked' """) disc(""" Here is an example of the previous chart values arranged in the stacked style. """) drawing = Drawing(400, 200) data = [ (13, 5, 20, 22, 37, 45, 19, 4), (14, 6, 21, 23, 38, 46, 20, 5) ] bc = VerticalBarChart() bc.x = 50 bc.y = 50 bc.height = 125 bc.width = 300 bc.data = data bc.strokeColor = colors.black bc.groupSpacing = 10 bc.barSpacing = 2.5 bc.valueAxis.valueMin = 0 bc.valueAxis.valueMax = 100 bc.valueAxis.valueStep = 20 bc.categoryAxis.labels.boxAnchor = 'ne' bc.categoryAxis.labels.dx = 8 bc.categoryAxis.labels.dy = -2 bc.categoryAxis.labels.angle = 30 bc.categoryAxis.categoryNames = ['Jan-99','Feb-99','Mar-99', 'Apr-99','May-99','Jun-99','Jul-99','Aug-99'] bc.categoryAxis.style = 'stacked' drawing.add(bc) draw(drawing, 'Stacking bars on top of each other.') ##Property Value ##data This should be a "list of lists of numbers" or "list of tuples of numbers". If you have just one series, write it as ##data = [(10,20,30,42),] ## ##x, y, width, height These define the inner 'plot rectangle'. We highlighted this with a yellow border above. Note that it is your job to place the chart on the drawing in a way which leaves room for all the axis labels and tickmarks. We specify this 'inner rectangle' because it makes it very easy to lay out multiple charts in a consistent manner. ##strokeColor Defaults to None. This will draw a border around the plot rectangle, which may be useful in debugging. Axes will overwrite this. ##fillColor Defaults to None. This will fill the plot rectangle with a solid color. (Note that we could implement dashArray etc. as for any other solid shape) ##barLabelFormat This is a format string or function used for displaying labels above each bar. We're working on ways to position these labels so that they work for positive and negative bars. ##useAbsolute Defaults to 0. If 1, the three properties below are absolute values in points (which means you can make a chart where the bars stick out from the plot rectangle); if 0, they are relative quantities and indicate the proportional widths of the elements involved. ##barWidth As it says. Defaults to 10. ##groupSpacing Defaults to 5. This is the space between each group of bars. If you have only one series, use groupSpacing and not barSpacing to split them up. Half of the groupSpacing is used before the first bar in the chart, and another half at the end. ##barSpacing Defaults to 0. This is the spacing between bars in each group. If you wanted a little gap between green and red bars in the example above, you would make this non-zero. ##barLabelFormat Defaults to None. As with the YValueAxis, if you supply a function or format string then labels will be drawn next to each bar showing the numeric value. ##barLabels A collection of labels used to format all bar labels. Since this is a two-dimensional array, you may explicitly format the third label of the second series using this syntax: ## chart.barLabels[(1,2)].fontSize = 12 ## ##valueAxis The value axis, which may be formatted as described previously ##categoryAxis The categoryAxis, which may be formatted as described previously ##title, subTitle Not implemented yet. These would be label-like objects whose text could be set directly and which would appear in sensible locations. For now, you can just place extra strings on the drawing. heading2("Line Charts") disc(""" We consider "Line Charts" to be essentially the same as "Bar Charts", but with lines instead of bars. Both share the same pair of Category/Value axes pairs. This is in contrast to "Line Plots", where both axes are Value axes. """) disc(""" The following code and its output shall serve as a simple example. More explanation will follow. For the time being you can also study the output of running the tool $reportlab/lib/graphdocpy.py$ withough any arguments and search the generated PDF document for examples of Line Charts. """) eg(""" from reportlab.graphics.charts.linecharts import HorizontalLineChart drawing = Drawing(400, 200) data = [ (13, 5, 20, 22, 37, 45, 19, 4), (5, 20, 46, 38, 23, 21, 6, 14) ] lc = HorizontalLineChart() lc.x = 50 lc.y = 50 lc.height = 125 lc.width = 300 lc.data = data lc.joinedLines = 1 catNames = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ') lc.categoryAxis.categoryNames = catNames lc.categoryAxis.labels.boxAnchor = 'n' lc.valueAxis.valueMin = 0 lc.valueAxis.valueMax = 60 lc.valueAxis.valueStep = 15 lc.lines[0].strokeWidth = 2 lc.lines[1].strokeWidth = 1.5 drawing.add(lc) """) from reportlab.graphics.charts.linecharts import HorizontalLineChart drawing = Drawing(400, 200) data = [ (13, 5, 20, 22, 37, 45, 19, 4), (5, 20, 46, 38, 23, 21, 6, 14) ] lc = HorizontalLineChart() lc.x = 50 lc.y = 50 lc.height = 125 lc.width = 300 lc.data = data lc.joinedLines = 1 catNames = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ') lc.categoryAxis.categoryNames = catNames lc.categoryAxis.labels.boxAnchor = 'n' lc.valueAxis.valueMin = 0 lc.valueAxis.valueMax = 60 lc.valueAxis.valueStep = 15 lc.lines[0].strokeWidth = 2 lc.lines[1].strokeWidth = 1.5 drawing.add(lc) draw(drawing, 'HorizontalLineChart sample') disc("") data=[["Property","Meaning"], ["data", "Data to be plotted, list of (lists of) numbers."], ["x, y, width, height", """Bounding box of the line chart. Note that x and y do NOT specify the centre but the bottom left corner"""], ["valueAxis", """The value axis, which may be formatted as described previously."""], ["categoryAxis", """The category axis, which may be formatted as described previously."""], ["strokeColor", """Defaults to None. This will draw a border around the plot rectangle, which may be useful in debugging. Axes will overwrite this."""], ["fillColor", """Defaults to None. This will fill the plot rectangle with a solid color."""], ["lines.strokeColor", """Color of the line."""], ["lines.strokeWidth", """Width of the line."""], ["lineLabels", """A collection of labels used to format all line labels. Since this is a two-dimensional array, you may explicitly format the third label of the second line using this syntax: chart.lineLabels[(1,2)].fontSize = 12"""], ["lineLabelFormat", """Defaults to None. As with the YValueAxis, if you supply a function or format string then labels will be drawn next to each line showing the numeric value. You can also set it to 'values' to display the values explicity defined in lineLabelArray."""], ["lineLabelArray", """Explicit array of line label values, must match size of data if present. These labels values will be displayed only if the property lineLabelFormat above is set to 'values'."""]] t=Table(data, colWidths=(100,330)) t.setStyle(TableStyle([ ('FONT',(0,0),(-1,0),'Times-Bold',10,12), ('FONT',(0,1),(0,-1),'Courier',8,8), ('FONT',(1,1),(1,-1),'Times-Roman',10,12), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) getStory().append(t) caption("""Table - HorizontalLineChart properties""") heading2("Line Plots") disc(""" Below we show a more complex example of a Line Plot that also uses some experimental features like line markers placed at each data point. """) eg(""" from reportlab.graphics.charts.lineplots import LinePlot from reportlab.graphics.widgets.markers import makeMarker drawing = Drawing(400, 200) data = [ ((1,1), (2,2), (2.5,1), (3,3), (4,5)), ((1,2), (2,3), (2.5,2), (3.5,5), (4,6)) ] lp = LinePlot() lp.x = 50 lp.y = 50 lp.height = 125 lp.width = 300 lp.data = data lp.joinedLines = 1 lp.lines[0].symbol = makeMarker('FilledCircle') lp.lines[1].symbol = makeMarker('Circle') lp.lineLabelFormat = '%2.0f' lp.strokeColor = colors.black lp.xValueAxis.valueMin = 0 lp.xValueAxis.valueMax = 5 lp.xValueAxis.valueSteps = [1, 2, 2.5, 3, 4, 5] lp.xValueAxis.labelTextFormat = '%2.1f' lp.yValueAxis.valueMin = 0 lp.yValueAxis.valueMax = 7 lp.yValueAxis.valueSteps = [1, 2, 3, 5, 6] drawing.add(lp) """) from reportlab.graphics.charts.lineplots import LinePlot from reportlab.graphics.widgets.markers import makeMarker drawing = Drawing(400, 200) data = [ ((1,1), (2,2), (2.5,1), (3,3), (4,5)), ((1,2), (2,3), (2.5,2), (3.5,5), (4,6)) ] lp = LinePlot() lp.x = 50 lp.y = 50 lp.height = 125 lp.width = 300 lp.data = data lp.joinedLines = 1 lp.lines[0].symbol = makeMarker('FilledCircle') lp.lines[1].symbol = makeMarker('Circle') lp.lineLabelFormat = '%2.0f' lp.strokeColor = colors.black lp.xValueAxis.valueMin = 0 lp.xValueAxis.valueMax = 5 lp.xValueAxis.valueSteps = [1, 2, 2.5, 3, 4, 5] lp.xValueAxis.labelTextFormat = '%2.1f' lp.yValueAxis.valueMin = 0 lp.yValueAxis.valueMax = 7 lp.yValueAxis.valueSteps = [1, 2, 3, 5, 6] drawing.add(lp) draw(drawing, 'LinePlot sample') disc("") data=[["Property","Meaning"], ["data", "Data to be plotted, list of (lists of) numbers."], ["x, y, width, height", """Bounding box of the line chart. Note that x and y do NOT specify the centre but the bottom left corner"""], ["xValueAxis", """The vertical value axis, which may be formatted as described previously."""], ["yValueAxis", """The horizontal value axis, which may be formatted as described previously."""], ["strokeColor", """Defaults to None. This will draw a border around the plot rectangle, which may be useful in debugging. Axes will overwrite this."""], ["strokeWidth", """Defaults to None. Width of the border around the plot rectangle."""], ["fillColor", """Defaults to None. This will fill the plot rectangle with a solid color."""], ["lines.strokeColor", """Color of the line."""], ["lines.strokeWidth", """Width of the line."""], ["lines.symbol", """Marker used for each point. You can create a new marker using the function makeMarker(). For example to use a circle, the function call would be makeMarker('Circle')"""], ["lineLabels", """A collection of labels used to format all line labels. Since this is a two-dimensional array, you may explicitly format the third label of the second line using this syntax: chart.lineLabels[(1,2)].fontSize = 12"""], ["lineLabelFormat", """Defaults to None. As with the YValueAxis, if you supply a function or format string then labels will be drawn next to each line showing the numeric value. You can also set it to 'values' to display the values explicity defined in lineLabelArray."""], ["lineLabelArray", """Explicit array of line label values, must match size of data if present. These labels values will be displayed only if the property lineLabelFormat above is set to 'values'."""]] t=Table(data, colWidths=(100,330)) t.setStyle(TableStyle([ ('FONT',(0,0),(-1,0),'Times-Bold',10,12), ('FONT',(0,1),(0,-1),'Courier',8,8), ('FONT',(1,1),(1,-1),'Times-Roman',10,12), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) getStory().append(t) caption("""Table - LinePlot properties""") heading2("Pie Charts") disc(""" As usual, we will start with an example: """) eg(""" from reportlab.graphics.charts.piecharts import Pie d = Drawing(200, 100) pc = Pie() pc.x = 65 pc.y = 15 pc.width = 70 pc.height = 70 pc.data = [10,20,30,40,50,60] pc.labels = ['a','b','c','d','e','f'] pc.slices.strokeWidth=0.5 pc.slices[3].popout = 10 pc.slices[3].strokeWidth = 2 pc.slices[3].strokeDashArray = [2,2] pc.slices[3].labelRadius = 1.75 pc.slices[3].fontColor = colors.red d.add(pc) """) from reportlab.graphics.charts.piecharts import Pie d = Drawing(400, 200) pc = Pie() pc.x = 125 pc.y = 25 pc.width = 150 pc.height = 150 pc.data = [10,20,30,40,50,60] pc.labels = ['a','b','c','d','e','f'] pc.slices.strokeWidth=0.5 pc.slices[3].popout = 10 pc.slices[3].strokeWidth = 2 pc.slices[3].strokeDashArray = [2,2] pc.slices[3].labelRadius = 1.25 pc.slices[3].fontColor = colors.red d.add(pc) draw(d, 'A bare bones pie chart') disc(""" Properties are covered below. The pie has a 'wedges' collection and we document wedge properties in the same table. """) disc("") data=[["Property", "Meaning"], ["data", "A list or tuple of numbers"], ["x, y, width, height", """Bounding box of the pie. Note that x and y do NOT specify the centre but the bottom left corner, and that width and height do not have to be equal; pies may be elliptical and wedges will be drawn correctly."""], ["labels", """None, or a list of strings. Make it None if you don't want labels around the edge of the pie. Since it is impossible to know the size of slices, we generally discourage placing labels in or around pies; it is much better to put them in a legend alongside."""], ["startAngle", """Where is the start angle of the first pie slice? The default is '90' which is twelve o'clock."""], ["direction", """Which direction do slices progress in? The default is 'clockwise'."""], ["sideLabels", """This creates a chart with the labels in two columns, one on either side."""], ["sideLabelsOffset", """This is a fraction of the width of the pie that defines the horizontal distance between the pie and the columns of labels."""], ["simpleLabels", """Default is 1. Set to 0 to enable the use of customizable labels and of properties prefixed by label_ in the collection slices."""], ["wedges", """Collection of wedges. This lets you customise each wedge, or individual ones. See below"""], ["wedges.strokeWidth", "Border width for wedge"], ["wedges.strokeColor", "Border color"], ["wedges.strokeDashArray", "Solid or dashed line configuration"], ["wedges.popout", """How far out should the slice(s) stick from the centre of the pie? Default is zero."""], ["wedges.fontName", "Name of the label font"], ["wedges.fontSize", "Size of the label font"], ["wedges.fontColor", "Color of the label text"], ["wedges.labelRadius", """This controls the anchor point for a text label. It is a fraction of the radius; 0.7 will place the text inside the pie, 1.2 will place it slightly outside. (note that if we add labels, we will keep this to specify their anchor point)"""]] t=Table(data, colWidths=(130,300)) t.setStyle(TableStyle([ ('FONT',(0,0),(-1,0),'Times-Bold',10,12), ('FONT',(0,1),(0,-1),'Courier',8,8), ('FONT',(1,1),(1,-1),'Times-Roman',10,12), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) getStory().append(t) caption("""Table - Pie properties""") heading3("Customizing Labels") disc(""" Each slide label can be customized individually by changing the properties prefixed by $label_$ in the collection $wedges$. For example $pc.slices[2].label_angle = 10$ changes the angle of the third label. """) disc(""" Before being able to use these customization properties, you need to disable simple labels with: $pc.simplesLabels = 0$ """) disc("") data=[["Property", "Meaning"], ["label_dx", """X Offset of the label"""], ["label_dy", """Y Offset of the label"""], ["label_angle", """Angle of the label, default (0) is horizontal, 90 is vertical, 180 is upside down"""], ["label_boxAnchor", """Anchoring point of the label"""], ["label_boxStrokeColor", """Border color for the label box"""], ["label_boxStrokeWidth", """Border width for the label box"""], ["label_boxFillColor", """Filling color of the label box"""], ["label_strokeColor", """Border color for the label text"""], ["label_strokeWidth", """Border width for the label text"""], ["label_text", """Text of the label"""], ["label_width", """Width of the label"""], ["label_maxWidth", """Maximum width the label can grow to"""], ["label_height", """Height of the label"""], ["label_textAnchor", """Maximum height the label can grow to"""], ["label_visible", """True if the label is to be drawn"""], ["label_topPadding", """Padding at top of box"""], ["label_leftPadding", """Padding at left of box"""], ["label_rightPadding", """Padding at right of box"""], ["label_bottomPadding", """Padding at bottom of box"""], ["label_simple_pointer", """Set to 1 for simple pointers"""], ["label_pointer_strokeColor", """Color of indicator line"""], ["label_pointer_strokeWidth", """Width of indicator line"""]] t=Table(data, colWidths=(130,300)) t.setStyle(TableStyle([ ('FONT',(0,0),(-1,0),'Times-Bold',10,12), ('FONT',(0,1),(0,-1),'Courier',8,8), ('FONT',(1,1),(1,-1),'Times-Roman',10,12), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black), ])) getStory().append(t) caption("""Table - Pie.wedges label customization properties""") heading3("Side Labels") disc(""" If the sideLabels attribute is set to true, then the labels of the slices are placed in two columns, one on either side of the pie and the start angle of the pie will be set automatically. The anchor of the right hand column is set to 'start' and the anchor of the left hand column is set to 'end'. The distance from the edge of the pie from the edge of either column is decided by the sideLabelsOffset attribute, which is a fraction of the width of the pie. If xradius is changed, the pie can overlap the labels, and so we advise leaving xradius as None. There is an example below. """) from reportlab.graphics.charts.piecharts import sample5, sample7, sample8 drawing5 = sample5() draw(drawing5, 'An example of a piechart with sideLabels =1') disc(""" If you have sideLabels set to True, then some of the attributes become redundant, such as pointerLabelMode. Also sideLabelsOffset only changes the piechart if sideLabels is set to true. """) heading4("Some issues") disc(""" The pointers can cross if there are too many slices. """) drawing7 = sample7() draw(drawing7, 'An example of pointers crossing') disc(""" Also the labels can overlap despite checkLabelOverlap if they correspond to slices that are not adjacent. """) drawing8 = sample8() draw(drawing8, 'An example of labels overlapping') heading2("Legends") disc(""" Various preliminary legend classes can be found but need a cleanup to be consistent with the rest of the charting model. Legends are the natural place to specify the colors and line styles of charts; we propose that each chart is created with a $legend$ attribute which is invisible. One would then do the following to specify colors: """) eg(""" myChart.legend.defaultColors = [red, green, blue] """) disc(""" One could also define a group of charts sharing the same legend: """) eg(""" myLegend = Legend() myLegend.defaultColor = [red, green.....] #yuck! myLegend.columns = 2 # etc. chart1.legend = myLegend chart2.legend = myLegend chart3.legend = myLegend """) # Hack to force a new paragraph before the todo() :-( disc("") todo("""Does this work? Is it an acceptable complication over specifying chart colors directly?""") heading3("Remaining Issues") disc(""" There are several issues that are almost solved, but for which is is a bit too early to start making them really public. Nevertheless, here is a list of things that are under way: """) bullet(""" Color specification - right now the chart has an undocumented property $defaultColors$, which provides a list of colors to cycle through, such that each data series gets its own color. Right now, if you introduce a legend, you need to make sure it shares the same list of colors. Most likely, this will be replaced with a scheme to specify a kind of legend containing attributes with different values for each data series. This legend can then also be shared by several charts, but need not be visible itself. """) bullet(""" Additional chart types - when the current design will have become more stable, we expect to add variants of bar charts to deal with percentile bars as well as the side-by-side variant seen here. """) heading3("Outlook") disc(""" It will take some time to deal with the full range of chart types. We expect to finalize bars and pies first and to produce trial implementations of more general plots, thereafter. """) heading3("X-Y Plots") disc(""" Most other plots involve two value axes and directly plotting x-y data in some form. The series can be plotted as lines, marker symbols, both, or custom graphics such as open-high-low-close graphics. All share the concepts of scaling and axis/title formatting. At a certain point, a routine will loop over the data series and 'do something' with the data points at given x-y locations. Given a basic line plot, it should be very easy to derive a custom chart type just by overriding a single method - say, $drawSeries()$. """) heading3("Marker customisation and custom shapes") disc(""" Well known plotting packages such as excel, Mathematica and Excel offer ranges of marker types to add to charts. We can do better - you can write any kind of chart widget you want and just tell the chart to use it as an example. """) heading4("Combination plots") disc(""" Combining multiple plot types is really easy. You can just draw several charts (bar, line or whatever) in the same rectangle, suppressing axes as needed. So a chart could correlate a line with Scottish typhoid cases over a 15 year period on the left axis with a set of bars showing inflation rates on the right axis. If anyone can remind us where this example came from we'll attribute it, and happily show the well-known graph as an example. """) heading3("Interactive editors") disc(""" One principle of the Graphics package is to make all 'interesting' properties of its graphic components accessible and changeable by setting apropriate values of corresponding public attributes. This makes it very tempting to build a tool like a GUI editor that that helps you with doing that interactively. """) disc(""" ReportLab has built such a tool using the Tkinter toolkit that loads pure Python code describing a drawing and records your property editing operations. This "change history" is then used to create code for a subclass of that chart, say, that can be saved and used instantly just like any other chart or as a new starting point for another interactive editing session. """) disc(""" This is still work in progress, though, and the conditions for releasing this need to be further elaborated. """) heading3("Misc.") disc(""" This has not been an exhaustive look at all the chart classes. Those classes are constantly being worked on. To see exactly what is in the current distribution, use the $graphdocpy.py$ utility. By default, it will run on reportlab/graphics, and produce a full report. (If you want to run it on other modules or packages, $graphdocpy.py -h$ prints a help message that will tell you how.) """) disc(""" This is the tool that was mentioned in the section on 'Documenting Widgets'. """) reportlab-3.3.0/docs/gen_epydoc0000755000175000017500000000021712120332300016015 0ustar rptlabrptlab#!/bin/sh d=`dirname $0` [ x$d = x ] && d=`pwd` export PYTHONPATH=$d/../src epydoc --html reportlab --docformat=restructuredtext --no-private reportlab-3.3.0/docs/source/0000755000175000017500000000000012314632712015272 5ustar rptlabrptlabreportlab-3.3.0/docs/source/conf.py0000664000175000017500000001443312314632712016600 0ustar rptlabrptlab# -*- coding: utf-8 -*- # # reportlab documentation build configuration file, created by # sphinx-quickstart on Fri Feb 5 21:41:03 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = 'reportlab' copyright = '2010, Robinson, Becker, Watters and many more' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '2.4' # The full version, including alpha/beta/rc tags. release = '2.4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: 'text') to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'reportlabdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'reportlab.tex', 'reportlab Documentation', 'Robinson, Becker, Watters and many more', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True reportlab-3.3.0/docs/source/index.rst0000644000175000017500000000203112120332300017110 0ustar rptlabrptlab.. reportlab documentation master file, created by sphinx-quickstart on Fri Feb 5 21:41:03 2010. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. =================================== ReportLab PDF Toolkit documentation =================================== .. rubric:: API references and more for the ReportLab PDF Toolkit Contents: .. toctree:: :maxdepth: 2 pdfgen package (reportlab.pdfgen) - the basics Platypus (reportlab.platypus) - long flowing documents Graphics and Charts (reportlab.graphics) Library and utilities (reportlab.lib) Sphinx is being used for the automated API references. We have always been able to make our own documents in PDF, without using other peoples' libraries, and don't want to back away from this. So, the main User Guide will remain in PDF format for now. However, programmers need API refs online, and we want to learn Sphinx (and use rst2pdf) in due course. reportlab-3.3.0/docs/source/lib.rst0000644000175000017500000000156512120332300016562 0ustar rptlabrptlab=================================== ReportLab Library Reference =================================== .. rubric:: API main index This page covers modules within reportlab/lib which may be of use to developers. We have tried only to include things we intend people to use; a lot of code in /lib/ is used by our package itself, and we reserve the right to change it if it's not documented here. Hint: review tests and user guide and see what is actually imported Users definitely need: * units * colors * utils See what else is commonly imported and used in the test scripts! If in doubt, include it and put a comment so Andy can review. units ----------------------- .. automodule:: reportlab.lib.units :members: colors -------------------- .. automodule:: reportlab.lib.colors :members: utils -------------------- .. automodule:: reportlab.lib.utils :members: reportlab-3.3.0/docs/source/_static/0000755000175000017500000000000012120332300016701 5ustar rptlabrptlabreportlab-3.3.0/docs/source/_static/default.css0000644000175000017500000004004312120332300021040 0ustar rptlabrptlab/* ReportLab website stylesheet Reorder into 1. reset 2. standard styles, which should appear normally in the main content div 3. named divs for layout, with clear explanation of where they go 4. everything else that's left over. (this will be most of it at first and we can work through seeing where each item is used) */ html, body { margin: 0 0 1px; padding: 0; } body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, input, p, blockquote, th, td { margin:0; padding:0; font-family:"Trebuchet MS",Verdana,sans-serif; font-weight:normal; font-size: 13px; color: #444444; } body{ background: #43505d; } a { font-weight:bold; /* text-decoration: underline; */ } A:link.menu2{ color:white; text-decoration:none; } A:visited.menu2{ color:white; text-decoration:none; } A:active.menu2{ color:white; } A:hover.menu2{ color:white; } A:link{ color:#00337f; text-decoration:none; /* text-decoration: underline;*/ } A:visited{ color:#00337f; text-decoration:none; } A:active{ color:#ca002a; } A:hover{ color:#ca002a; } a.txt-green { color:#00800d; } a.txt-green:link { color:#00800d; } a.txt-green:active { color:#00800d; } a.txt-green:visited { color:#00800d; } ul{ margin: 5px 0; list-style-type: square; /*#list-style-position: inside;*/ } ol{ list-style-type:decimal; } /*ul.sidemenu {*/ /* padding-top: 0;*/ /*}*/ li.sidemenu { margin-left:15px; padding: 2px 0; /*text-indent: -1em;*/ /*#overflow:hidden;*/ /*#list-style: none; url('http://www.reportlab.com/media/level2/menu_arrow_bg.gif');*/ } li.mlevel1 { width:150px; margin-left:4px; /*#padding: 10px 0;*/ /*#background:url('http://www.reportlab.com/media/level2/menu_bar_bg.gif') repeat-x;*/ /*#list-style: none; url('http://www.reportlab.com/media/level2/menu_arrow_bg.gif');*/ } li.selected { color: #ca002a; font-weight: bold; } pre, .dp-highlighter li { /* Javascript code syntax highlighter */ font-family: monospace; font-size:11px; } .blue{background-color:#00337f} .red{background-color:#ca002a} .lightblue{background-color:#44ADC7} .green{background-color:#9ac083} .purple{background-color:#714893} .pink{background-color:#c04b95} .gray{background-color:#ccc} .txt-blue{color:#00337f} .txt-red{color:#ca002a} .txt-lightblue{color:#44ADC7} .txt-green{color:#00800d} .txt-purple{color:#714893} .txt-pink{color:#c04b95} .txt-gray{color:#ccc} .txt-white{color:#fff} .txt-blue>a, .txt-blue>a:link, .txt-blue>a:visited {color:#00337f} .txt-red>a, .txt-red>a:link, .txt-red>a:visited {color:#ca002a} .txt-lightblue>a, .txt-lightblue>a:link, .txt-lightblue>a:visited {color:#44ADC7} .txt-green>a, .txt-green>a:link, .txt-green>a:visited {color:#00800d} .txt-purple>a, .txt-purple>a:link, .txt-purple>a:visited {color:#714893} .txt-pink>a, .txt-pink>a:link, .txt-pink>a:visited {color:#c04b95} .txt-gray>a, .txt-gray>a:link, .txt-gray>a:visited {color:#ccc} .txt-white>a, .txt-white>a:link, .txt-white>a:visited {color:#fff} a#logo{ display: block; width:182px; height:113px; position:absolute; left:41px; top:23px; } a#logo img{ border: 0 none; } div#logo{ width:182px; height:113px; position:absolute; left:41px; top:23px; background: url('http://www.reportlab.com/media/base/logo_bg.gif') no-repeat; } div#topbar{ width:566px; height:7px; position:absolute; left:241px; top:77px; background: url('http://www.reportlab.com/media/base/topbar_bg.gif') no-repeat; } div#container{ position:relative; /*#border:1px none red;*/ top:0px; width:926px; margin: 17px auto 17px auto; background: #fdfdfd url('http://www.reportlab.com/media/base/page_bg.jpg') no-repeat 797px -1px; } div#content{ margin-left:43px; margin-right:43px; margin-bottom:20px; } div#footer{ margin-left:43px; margin-right:43px; } div#footer_slogan{ width: 838px; height: 19px; background: #fff url('http://www.reportlab.com/media/base/slogan_img.gif') no-repeat; } .footer{ font-size:11px; } img#login_btn{ cursor:pointer; position:absolute; top:10px; right:125px; } div#header{ height: 150px; } #call_us{ position:absolute; text-align:right; top:100px; right:125px; font-size:12pt; line-height:25px; } ul#menu{ padding-top: 0; margin-top: 0; cursor:pointer; position:absolute; top:56px; left:270px; list-style-type:none; } li.menu{ margin-left: 0; padding-top: 0; width: 95px; line-height:18px; text-align:center; font-size:13px; font-weight:bold; float:left; border-right:2px solid #00337f; } li.menu:hover, li.menu:hover>a, li.menu:hover>a:link, li.menu:hover>a:visited, li.menu:hover>a:hover { color: black; } li.menu>a { display: block; } li.highlight_blue:hover{ background: #00337f; } li.highlight_lightblue:hover{ background: #44ADC7; } li.highlight_green:hover{ background: #9ac083; } li.highlight_purple:hover{ background: #714893; } li.highlight_pink:hover{ background: #c04b95; } div#shadow{ position:absolute; top:68px; left:163px; background: url('http://www.reportlab.com/media/base/menu_bg.jpg') no-repeat; width:550px; height:48px; } div#clear{ clear:both; height:10px; } .intro{ padding:20px; padding-top:30px; font-size:10pt; line-height:14pt; font-weight:normal; text-align:justify; } td#slogan{ text-align:center; } table#footer_table{ width:100%; } p.title{ font-size:13pt; line-height:16pt; color: #00337F; } p.teaser{ font-style: italic; font-weight: bold; } /* Added by andy. This is all wrong; the base styles should cover standard text, and the 'home page' or 'menu' styles. should be overridden in each page that needs them, and with selectors like '#menu li'. */ table { /*#border: 1px solid #e5e5e5;*/ border-bottom: 0; border-collapse: collapse; } table th { font-weight: bold; text-align: left; background-color: #e5e5e5; padding: 3px; } table th.left { width: 12em; } table td { /*#border-bottom: 1px solid #e5e5e5;*/ padding: 3px 8px; } table#main_content td { /*#border-bottom: 1px solid #e5e5e5;*/ padding: 0; } hr { border: 0; border-top: 1px solid #e5e5e5; } #main_content_teaser{ font-style:italic; font-weight:bold; font-size:13pt; margin: 15px 0; } #main_content_sub_pages{ width:450px; min-height:700px; margin:10px; float:left; /*font-size:16px;*/ } #main_content_sub_pages_wide{ width:600px; min-height:700px; margin:10px; float:left; /*font-size:16px;*/ } #content h1 { font-size:20px; margin: 0 0 20px 0; color:#264D90; font-weight:bold; line-height:40px; } #content h2 { font-size:18px; margin: 25px 0 10px 0; font-weight: bold; color: #264D90; } #content h3 { font-size:15px; margin: 12px 0 4px 0; font-weight: bold; color: #264D90; } #content h4 { font-size:13px; margin: 5px 0 0 0; font-weight: bold; color: #264D90; } #content p { margin: 10px 0; font-size: 13px; } li { margin-left: 15px; list-style-position: outside; /*#list-style-type:square;*/ } ul { margin-left: 10px; list-style-position: outside; } #breadcrumb{ position: absolute; left: 250px; top: 90px; height: 22px; width: 560px; font-weight:bold; /*background:pink; uncomment to see it*/ margin-top:20px; /*border:1px solid #f2f2f2;*/ font-size:12px; line-height:24px; text-indent:21px; color:#264D90; } #divider{ height:100%; background:white; border-top:1px solid #E5E5E5; margin-top:10px; } #nav_bar{ float:left; margin-left:0px; margin-top:16px; } #nav_bar_top{ width:180px; height:100%; background:url('http://www.reportlab.com/media/level2/menu_bg.gif') repeat-y; color:white; /*#text-indent:10px;*/ color:#264D90; /*#padding-left:10pt;*/ padding: 5px 10px; } #nav_bar_bottom{ height:37px; width:200px; background:url('http://www.reportlab.com/media/level2/menu_footer_bg.gif') no-repeat; line-height:37px; } #nav_bar li { /*#margin-left: 8px;*/ /*#list-style-position: inside;*/ font-size:12px; font-weight:bold; } #nav_bar ul { margin-left: 5px; list-style-position: inside; } #side_div h1 { font-size: 13px; margin: 0 0 12px 25px; color: #000; font-weight:bold; text-align: left; } #side_div a.related_item img { width:100px; height:100px; display:block; margin:0 auto; border: 0; padding-top: 10px; } #side_div li { list-style: none; } table.price_table th { padding-right:10pt; text-align:right; } td.tick { text-align:center; } .notification { border: 2px solid #43505d; padding: 15px; background: #c0c4c9; color: #000; margin: 10px 0; } /* juraj starts here */ #users-navigation { float: right; height: 22px; margin: 10px 120px 10px 10px; background-image: url(status_left.gif); background-repeat: no-repeat; background-position: left; padding-left:2px; } #users-navigation div, #users-navigation a{ color: white; font-size: 90%; line-height: 22px; } #users-navigation div{ float: left; height: 22px; background-image: url(status_middle.gif); padding-left: 3px; } #users-navigation a{ padding-right: 3px; } #users-navigation a:hover{ text-decoration: underline; } #users-navigation div.last{ background-image: url(status_right.gif); background-repeat: no-repeat; } #loggedAs{ float: right; margin: 10px 10px 10px; height: 22px; line-height: 25px; font-size: 10px; } #messages{ margin-left:57px; margin-right:43px; font-size:14px; line-height:24px; text-indent:21px; color:#264D90; } #overlay{ display: block; position: absolute; top: 0pt; left: 0pt; z-index: 2; width: 100%; /* background-image:url('overlay.png'); */ } * html #overlay{ background-color: #333; back\ground-color: transparent; background-image: url(blank.gif); filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="overlay.png", sizingMethod="scale"); } .overlaydiv{ background-color: #ffffff; position: absolute; width: 550px; left: 50%; top: 100px; margin-left: -275px; padding: 10px; z-index: 102; border: 2px solid #000000; display: none; } .overlaydiv div{ padding: 10px; width: 500px; margin-left: auto; margin-right: auto; z-index: 102; /* border: 2px solid #00337f; */ } .noborder{ border: none; } /* :::::::::::: START accesible forms styles :::::::::::: */ /* color #00337f is reportlab blue */ form.accform li { list-style: none; border: none; } form.accform ul{ margin: 0px 0px 0px 0px; padding: 0px 0px 0px 0px; border: none; } form.accform li{ margin: 0px 0px 5px 0px; padding: 0px 0px 5px 0px; } /* borders */ form.accform textarea, form.accform input[type="text"]{ border: 1px solid #CCCCCC; display: inline-block; } /* labels are used for alignement : overide width if needed */ form.accform label{ display: block; text-align: left; float: left; width: 80px; } /* fieldset with legend */ form.accform fieldset{ border: none; border-top: 2px solid #00337f; padding-top: 10px; } form.accform legend{ font-weight: bold; color: #00337f; } /* highlight elements with errors not working in IE6 */ form.accform ul.errorlist + label + input, form.accform ul.errorlist + label + textarea, form.accform ul.errorlist + label + dl + textarea, form.accform ul.errorlist + label + div + textarea, form.accform ul.errorlist + label + select{ border: 1px solid #800000; } /* this margin should be same as label width */ form.accform ul.errorlist li { margin-left: 80px; } /* some help text dd dt styles */ dl.accform { float: right; } dl.accform > dt:first-child{ padding-bottom: 5px; color: #00337f; } /* :::::::::::: END accesible forms styles :::::::::::: */ /* :::::::::::: START definition list :::::::::::: */ /* inline ; definition type bold */ /* #content #sidebar dl dt, #content #sidebar dl dd { */ /* float: left; */ /* margin-right: 0.5em; */ /* margin-bottom: 0.5em; */ /* } */ /* #content #sidebar dl dt { */ /* clear: left; */ /* font-weight: bold; */ /* } */ dl.inline dt, dl.inline dd { float: left; margin-right: 0.5em; margin-bottom: 0.5em; } dl.inline dd { *float: none; } dl.inline dt{ clear: left; font-weight: bold; } /* :::::::::::: END definition list :::::::::::: */ /* :::::::::::: START snippets form :::::::::::: */ #snippetForm label { width: 130px; } #snippetForm ul.errorlist li { margin-left: 100px; } #snippetForm textarea, #snippetForm input[type="text"]{ width: 410px } .help{ display: none; float: right; width: 270px; } .help textarea{ width: 250px !important; } ul.snippets li{ padding-bottom: 5px; } /* :::::::::::: END snippets form :::::::::::: */ /* :::::::::::: START contact us form :::::::::::: */ #contactusContainer{ display: none; background-color: white; } #contactusContainer fieldset{ border: none; width: 500px; } #contactusContainer textarea, #contactusContainer input[type="text"]{ width: 410px } /* :::::::::::: END contact us form :::::::::::: */ /* :::::::::::: START footer :::::::::::: */ .newFooter{ position: relative; clear: both; } hr#topbar{ width:566px; height:7px; position:absolute; left:241px; top:77px; } .newFooter hr, hr#topbar{ color: #00337f; background-color: #00337f; height: 5px; } .newFooter span{ color: red; } .newFooter div{ position: absolute; left: 500px; top: -13px; background-color: #ffffff; padding: 5px; color: #00337f; font-weight: bold; font-size: 120%; } /* :::::::::::: END footer :::::::::::: */ /* :::::::::::: START columns div :::::::::::: */ div.columns{ overflow: auto; width: 100%; padding: 5px 0px; } div.columns br{ clear: both; } div.columns > div{ float: left; width: 47%; /* padding: 1%; */ /* border: 1px solid red; */ } div.columns div + div{ float: right; clear: right; } div.columns_80_20 > div{ width: 78%; } div.columns_80_20 > div + div{ width: 17% } div.columns_70_30 > div{ width: 68%; } div.columns_70_30 > div + div{ width: 27% } /* :::::::::::: END columns div :::::::::::: */ /* :::::::::::: START general :::::::::::: */ .floatright{ display: block; float: right; } span.contact{ color: #00337f; text-decoration: underline; font-weight: bold; cursor: pointer; } span.contact:hover{ color: #ca002a; } /* :::::::::::: END general :::::::::::: */ .center{ margin-left:auto; margin-right:auto; display:block; } .border{ border: 1px solid #cccccc; padding: 2px; } /* :::::::::::: END general :::::::::::: */ /* :::::::::::: START prifile :::::::::::: */ #accordion{ display: none; } #accordion h3{ margin: 0px; padding: 0px; } #accordion div.notification{ margin: 0px; padding: 5px; } div.gcbutton{ /* height: 48px; */ line-height: 40px; width: 200px; overflow-x: hidden; } div.gcbutton img{ display: inline; } span.expand{ font-weight: normal; font-size: 80%; cursor: pointer; padding-left: 10px; /* display: inline-block; */ } div.profileSection{ border-bottom: 2px solid #00337f; padding: 5px 0px; } /* div.profileSection div.toogle{ */ /* display: nonex; */ /* } */ /* remove important later when css is fixed !!! */ div.profileSection h2{ margin: 0px 0px 0px 0px !important; display: inline } div.profileSection p{ margin: 5px 0px !important; } div.center{ width: 600px; margin-left: auto; margin-right: auto; } div.profileSection div.notification{ margin: 0px 0px 0px 0px !important; } div.profileSection div.notification h3{ margin: 0px 0px 0px 0px !important; } dl.profile dt{ width: 100px; } .tag_icons { float: right; } .tag_icons img { border: 0; } /* Fix for Jquery accordion in IE~6 */ .ui-accordion-content{ zoom: 1; } /* Sphinx docs */ div.related, body>div.sphinxsidebar, body>div.document>div.sphinxsidebar, body>div.footer { display: none; } pre { background:#ECF0F3; border:1px solid #d7e7f3; border-left:0; border-right:0; padding:0.5em; margin:10px 0; } dl { margin-bottom:15px; } tt { background-color:#ECF0F3; font-size:0.95em; padding:0 1px; } tt.descname { font-size:1.2em; font-weight:bold; } dd { margin-bottom:10px; margin-left:30px; margin-top:3px; } div.body p, div.body dd, div.body li { line-height:130%; } a.headerlink { color:#ECF0F3; } a.headerlink:hover{ color:#ca002a; } reportlab-3.3.0/docs/source/_static/basic.css0000644000175000017500000000000012120332300020462 0ustar rptlabrptlabreportlab-3.3.0/docs/source/_templates/0000755000175000017500000000000012120332300017410 5ustar rptlabrptlabreportlab-3.3.0/docs/source/_templates/layout.html0000644000175000017500000000024712120332300021616 0ustar rptlabrptlab{% extends "!layout.html" %} {% block rootrellink %}
  • ReportLab Homepage »
  • {{ super() }} {% endblock %} reportlab-3.3.0/docs/source/_templates/page.html0000644000175000017500000000262712120332300021221 0ustar rptlabrptlab{% extends "layout.html" %} {% block body %}
    {% block relbar1 %}{{ relbar() }}{% endblock %} {{ body }} {% block relbar2 %}{{ relbar() }}{% endblock %}
    {% endblock %} reportlab-3.3.0/docs/source/pdfgen.rst0000644000175000017500000000035612120332300017254 0ustar rptlabrptlab=================================== package reportlab.pdfgen =================================== .. rubric:: The basics module reportlab.pdfgen.canvas ------------------------------ .. automodule:: reportlab.pdfgen.canvas :members: reportlab-3.3.0/docs/source/platypus.rst0000644000175000017500000000173012120332300017667 0ustar rptlabrptlab=================================== Platypus =================================== .. rubric:: long flowing documents doctemplate ----------------------- .. automodule:: reportlab.platypus.doctemplate :members: paragraph ----------------------- .. automodule:: reportlab.platypus.paragraph :members: paraparser ----------------------- .. automodule:: reportlab.platypus.paraparser :members: flowables ----------------------- .. automodule:: reportlab.platypus.flowables :members: frames ----------------------- .. automodule:: reportlab.platypus.frames :members: figures ----------------------- .. automodule:: reportlab.platypus.figures :members: tables ----------------------- .. automodule:: reportlab.platypus.tables :members: tableofcontents ----------------------- .. automodule:: reportlab.platypus.tableofcontents :members: xpreformatted ----------------------- .. automodule:: reportlab.platypus.xpreformatted :members: reportlab-3.3.0/docs/source/graphics.rst0000644000175000017500000000250112120332300017603 0ustar rptlabrptlab=================================== Graphics and Charts =================================== .. rubric:: Graphics and Charts barcode ----------------------- .. automodule:: reportlab.graphics.barcode :members: charts ----------------------- .. automodule:: reportlab.graphics.charts :members: renderPDF ----------------------- .. automodule:: reportlab.graphics.renderPDF :members: renderPM ----------------------- .. automodule:: reportlab.graphics.renderPM :members: renderPS ----------------------- .. automodule:: reportlab.graphics.renderPS :members: renderSVG ----------------------- .. automodule:: reportlab.graphics.renderSVG :members: renderbase ----------------------- .. automodule:: reportlab.graphics.renderbase :members: samples ----------------------- .. automodule:: reportlab.graphics.samples :members: shapes ----------------------- .. automodule:: reportlab.graphics.shapes :members: testdrawings ----------------------- .. automodule:: reportlab.graphics.testdrawings :members: testshapes ----------------------- .. automodule:: reportlab.graphics.testshapes :members: widgetbase ----------------------- .. automodule:: reportlab.graphics.widgetbase :members: widgets ----------------------- .. automodule:: reportlab.graphics.widgets :members: reportlab-3.3.0/docs/Makefile0000644000175000017500000000610012120332300015410 0ustar rptlabrptlab# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/reportlab.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/reportlab.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." reportlab-3.3.0/docs/genAll.py0000664000175000017500000000273112314632712015553 0ustar rptlabrptlab#!/bin/env python import os, sys, traceback def _genAll(verbose=1): from reportlab.lib.testutils import setOutDir setOutDir(__name__) from reportlab.lib.testutils import testsFolder topDir=os.path.dirname(testsFolder) L = [os.path.join(topDir,f) for f in ( #'docs/reference/genreference.py', 'docs/userguide/genuserguide.py', #'tools/docco/graphdocpy.py', ) ] for f in ('src/rl_addons/pyRXP/docs/PyRXP_Documentation.rml', ): f = os.path.join(topDir,f) if os.path.isfile(f): L += [f] break for p in L: os.chdir(os.path.dirname(p)) if p[-4:]=='.rml': try: from rlextra.rml2pdf.rml2pdf import main main(exe=0,fn=[os.path.basename(p)], quiet=not verbose, outDir=d) except: if verbose: traceback.print_exc() else: cmd = '"%s" %s %s' % (sys.executable,os.path.basename(p), not verbose and '-s' or '') if verbose: print(cmd) os.system(cmd) """Runs the manual-building scripts""" if __name__=='__main__': #need a quiet mode for the test suite if '-s' in sys.argv: # 'silent verbose = 0 else: verbose = 1 d = os.path.dirname(sys.argv[0]) if not d: d = os.getcwd() elif not os.path.isabs(d): d = os.path.abspath(d) sys.path.insert(0,os.path.dirname(d)) _genAll(verbose) reportlab-3.3.0/CHANGES.md0000664000175000017500000006410212661063723014446 0ustar rptlabrptlabCHANGES ======= This is a summary of changes made to the reportlab source code for each release. Please refer to subversion backlogs (using the release dates) for more details or for releases which we have not provide a higher level changes list for. E.g. to retrieve the changes made between release 3.1 and release 3.2, type:: $ hg log -r adb3f0d The contributors lists are in no order and apologies to those accidentally not mentioned. If we missed you, please let us know! RELEASE 3.3 17/02/2016 ----------------------- * Canvas & Doctemplate now allow specification of the initial font Name, Size & Leading. Prevously you had to mess with rl_settings to accomplish this. * Canvas & Doctemplate now support specification of the crop/art/trim/bleed boxes. * Add option to auto generate missing TTF font names. Handy for CJKers with home produced fonts. Also attempt to prevent usage of multiple TTFs with same name. * Paragraph styles now have justifyBreaks to control justification of lines broken with
    . * Paragraph styles now have justifyLastLine=n to control justification of last lines with more than n words (0 means do not). * Added EAN-5 and ISBN barcode widgets (contribution by Edward Greve). * Bug fix of QrCodeWidget (prompted by https://bitbucket.org/fubu/). * Frames now have support for automatic flowables at the top of frame. story support via the class reportlab.platypus.flowables.SetTopFlowables. * Added support for Trapped and ModDate PDF info dictionary keys. * Bug fix for pie charts with no data (raised by Michael Spector). * New barcodes BarcodeCode128Auto & BarcodeECC200DataMatrix (contributed by Kyle MacFarlane). * Improved LinePlot marker handling. * PyPy improvements inspired by Marius Gedminas. * Bug fix in reportlab.lib.utils.simpleSplit (reported by Chris Buergi ). * Unwanted escaping in renderSVG fixed (reported by Ruby Yocum). * Bug fix in _rl_accel.c (remove excess state and fix refcount breakage reported by Mark De Wit ). * Code128 barcode length optimization inspired by Klaas Feenstra. * Paragraph / & tags now support rise & size attributes to allow special control over position & font size. * Splitting tables now remove unwanted styles in the first part of the split (reported by Lele Gaifax). * test changes inspired by https://bitbucket.org/stoneleaf * ReportLab now runs all tests under Python 2.7, 3.3, 3.4 & 3.5. ### Contributors: * Edward Greve * https://bitbucket.org/fubu/ * Michael Spector * Kyle MacFarlane * Marius Gedminas * Chris Buergi * Ruby Yocum * Mark de Wit * Klaas Feenstra * Lele Gaifax * https://bitbucket.org/stoneleaf RELEASE 3.2 01/06/2015 ----------------------- * Added proportional underlining specific to font sizes, set via the `underlineProportion` attribute of ParagraphStyles. * TrueType fonts: added support for cmaps 10 & 13 * DocTemplate class now supports a boolean `displayDocTitle` argument. * TableofContents now supports a formatter argument to allow formatting of the displayed page numbers (eg for appendices etc). * Table `repeatRows` can now be a tuple of row numbers to allow incomplete ranges of rows to be repeated. * Tables now do pass instance.`spaceBefore` & `spaceAfter` to their split children when split * Several strangenesses were fixed in the pdfbase.pdfform module; Multiple usage is now allowed. * Error message fixes * Various environment fixes for Google Application Environment * Resource fixes * PDFDoc can now set the `Lang` attribute * canvas.drawString and similar now allow the character spacing to be set * Index of accented stuff has been improved * RTL code was improved * fix Propertyset.clone * `flowables.py`: fix ImageAndFlowables so it avoids testing negative availableWidth ### Contributors: * Steven Jacobs * Philip Semanchuk * Marius Gedminas * masklinn * Kale Franz * Albertas Agejavas • Anders Hammarquist * jvanzuela @ bitbucket * Glen Lindermann * Greg Jones * James Bynd * fcoelho @ bitbucket RELEASE 3.1 22/04/2014 ----------------------- If you are running ReportLab 3.0.x, the changes are minor. * support for emoji - characters outside the Unicode basic multilingual plane * improved pip-based installers will pull in all the needed dependencies; Pillow 2.4 appears to deal with all our issues. ### Contributors * Ivan Tchomgue * Waldemar Osuch * masayuku * alexandrel_sgi RELEASE 3.0 14/02/2014 ----------------------- ReportLab 3.0 now supports Python 2.7, 3.3 and higher. There has been a substantial internal rewrite to ensure consistent use of unicode strings for natural-language text, and of bytes for all file format internals. The intent is to make as few API changes as possible so that there should be little or no impact on users and their applications. Changes are too numerous but can be seen on Bitbucket. ### Python 3.x compatibility * Python 3.x compatibility. A single line of code should run on 2.7 and 3.3 * __init__.py restricts to 2.7 or >=3.3 * __init__.py allow the import of on optional reportlab.local_rl_mods to allow monkey patching etc. * rl_config now imports rl_settings & optionally local_rl_settings * ReportLab C extensions now live inside reportlab; _rl_accel is no longer required; All _rl_accel imports now pass through reportlab.lib.rl_accel * xmllib is gone, alongside the paraparser stuff that caused issues in favour of HTMLParser. * some obsolete C extensions (sgmlop and pyHnj) are gone * Improved support for multi-threaded systems to the _rl_accel extension module. * Removed reportlab/lib/ para.py & pycanvas.py; these would better belong in third party packages, which can make use of the monkeypatching feature above. ### New features * Add ability to output greyscale and 1-bit PIL images without conversion to RGB. (contributed by Matthew Duggan) * highlight annotation (contributed by Ben Echols) ### Other * numerous very minor fixes, visible through BitBucket. RELEASE 2.7 04/04/2013 ----------------------- #### Charts / graphics enhancements * Added SimpleTimeSeriesPlot * added _computeMaxSpace * added in lineStyle (for bars) * improved SVG rendering * Pie Chart now has an `innerRadiusFraction` to allow doughnut-like appearance for 2d charts (it has no effect with 3d charts). The separate 'doughnut' chart lacks many pie chart features and should only be used if you wanted multiple nested doughnuts. #### Charts/graphics bug fixes * piecharts.py: fix Pie3d __init__ to call its superclass * linecharts.py: fix swatch creation * fixed `y` axis in the simple time series plot #### PDF * Fixes to testshapes & pdfform resetting * colors.py * various minor fixes #### Platypus * Defined a small bullet rather than a big circle as the default for unordered lists * fixed attribute spelling bug * fixed CJK + endDots ### Acknowledgements Many thanks to Andrew Cutler, Dinu Gherman, Matthias Kirst and Stephan Richter for their contributions to this release. RELEASE 2.6 27/09/2012 ----------------------- This is a minor release focusing mainly on improved documentation. There are a number of minor enhancements, and a larger number of previous-undocumented enhancements which we have documented better. #### General changes * Manuals have been reformatted with more pleasing code snippets and tables of contents, and reviewed and expanded #### Flowing documents (Platypus) * Added support for HTML-style list objects * Added flexible mechanism for drawing bullets * Allowed XPreformatted objects to use Asian line wrapping * Added an `autoNextPageTemplate` attribute to PageTemplates. For example you can now set up a 'chapter first page template' which will always be followed by a 'continuation template' on the next page break, saving the programmer from having to issue control flow commands in the story. * added a TopPadder flowable, which will 'wrap' another Flowable and move it to the bottom of the current page. * More helpful error messages when large tables cannot be rendered * Documentation for images within text (`test_032_images`) * Trailing dots for use on contents pages #### Charts and graphics * Support for UPCA bar codes * We now have a semi-intelligent system for labelling pie charts with callout lines. Thanks to James Martin-Collar, a maths student at Warwick University, who did this as his summer internship. * Axes - added startOffset and endOffset properties; allowed for axis background annotations. * Bar charts - allow more control of z Index (i.e. drawing order of axes and lines) * Pie charts - fixed bugs in 3d appearance * SVG output back end has seen some bugs fixed and now outputs resizeable SVG ### Contributors * Alex Buck * Felix Labrecque * Peter Johnson * James Martin-Collar * Guillaume Francois RELEASE 2.5 at 18:00 GMT 01/Oct/2010 -------------------------------------- Many new features have been added and numerous bugs have been fixed. Thanks to everybody who has contributed to the open-source toolkit in the run-up to the 2.5 release, whether by reporting bugs, sending patches, or contributing to the reportlab-users mailing list. Major contributors are credited in the user documentation. * Support for colour separated PDF output and other optimisations and features for high-quality printing, including enforcement of colour models for CMYK, RGB, and "spot colours" * Long table optimisations are now turned on by default. Previously, documents with very long tables spanning many pages could take a long time to create because we considered the whole table to work out row and column sizes. A patch was submitted some time ago to fix this controlled by a flag in the rl_config file, but this was set 'off' for compatibility. Users are often not aware of this and we haven't found any real-world cases where the new layout technique works badly, so we are turning this behaviour on. * New support for QR barcodes - [try our demo!](https://www.reportlab.com/demos/qr/) #### PDF * Colour separation and other enhancements for high-end print * Python 2.7 support #### Charts * reportlab.graphics.charts.axes * ValueAxis * avoidBoundSpace - Space to allow above and below * abf_ignore_zero - Set to True to make the avoidBoundFrac calculations treat zero as non-special * keepTickLabelsInside - Ensure tick labels do not project beyond bounds of axis if true * NormalDateXValueAxis * specialTickClear - clear rather than delete close ticks when forced first/end dates * AdjYValueAxis * labelVOffset - add this to the labels * reportlab.graphics.charts.barcharts * BarChart * categoryLabelBarSize - width to leave for a category label to go between categories * categoryLabelBarOrder - where any label bar should appear first/last * barRecord (advanced) - callable(bar,label=labelText,value=value,**kwds) to record bar information * reportlab.graphics.charts.legends * SubColProperty * dx - x offset from default position * dy - y offset from default position * Legend * swdx - x position adjustment for the swatch * swdy - y position adjustment for the swatch * reportlab.graphics.charts.piecharts * Pie * wedgeRecord (advanced) - callable(wedge,*args,**kwds) * reportlab.graphics.charts.utils * DrawTimeCollector - generic mechanism for collecting information about nodes at the time they are about to be drawn RELEASE 2.4 at 18:00 GMT 20/Jan/2010 -------------------------------------- #### PDF * lots of improvements and verbosity to error messages and the way they are handled. * font size can now be specified in pixels * unicode file names are now accepted #### Platypus * canvas auto cropmarks * added support for styles h4-h6 * Improved support for onDraw and SimpleIndex * Add support for index tableStyle * Added an alphabetic grouping indexing class * Added support for multi-level and alphabetical indexes * Added support for an unlimited number of TOC levels with default styles * Index entries can now be clickable. #### Graphics * Axes values can be reversible. * Labels on the axes can now be drawn above or below the axes (hi or low). * A per swatch callout is now allowed in the legend. * A new anchroing mode for string 'numeric' that align numerical strings by their decimal place. * Shapes have new attributes to specify if the shape should grow to take all canvas area (vertically or horizontally) or if the canvas should shrink to fit the shape size. * color objects now have a clone method. * colors module has a fade function that returns a list of different shades made up of one base colour. * added in support for Overprint/Opacity & Separated colours #### Bugs fixes * word counting in complex paragraphs has been fixed. * SimpleIndex and TableOfContents bugs have been fixed. * Fix for position of hyperlinks when crop marks are added. * flowables.py: fix special case of doctemplate with no frames * PDFFormXObject.format missing Resources bug patch from Scott Meyer * KeepInFrame justification bug has been fixed. * paragraph.py: fix linebreaking bug thanks to Roberto Alsina * fix unicode/str issue bug found by Michael Egorov * YCategoryAxis makeTickLabels fix contributed by Mike Folwell * pdfdoc.py: fix ro PDFDate contributed by Robert Alsina * and others .. ### Contributors * PJACock's () * Hans Brand * Ian Stevens * Yoann Roman * Randolph Bentson * Volker Haas * Simon King * Henning Vonbargen * Michael Egorov * Mike Folwell * Robert Alsina * and more ... RELEASE 2.3 at 18:00 GMT 04/Feb/2009 -------------------------------------- #### PDF * Encryption support (see encrypt parameter on Canvas and BaseDocTemplate constructor) #### Platypus * TableOfContents - Creates clickable tables of contents * Variable border padding for paragraphs (using the borderPadding style attribute) * New programming Flowable, docAssert, used to assert expressions on wrap time. #### Bug fixes * Fixed old documentation and installation issues * 610 - Fixed Image anchoring code to match documentation * 704 - renderSVG groups problem * 706 - rl_codecs.py now compatible with WordAxe * and others... ### Contributors * Yoann Roman * Dinu Gherman * Dirk Holtwick * Marcel Tromp * Henning von Bargen * Paul Barrass * Adrian Klaver * Hans Brand * Ian Stevens RELEASE 2.2 at 18:00 GMT 10/Sep/2008 -------------------------------------- #### PDF * pdfmetrics: Added registerFontFamily function * Basic support for pdf document viewer preferences (e.g.: fullscreen). #### Platypus * Paragraph tag support for inline images. * Paragraph autoleading support (helps with tags). * Platypus doctemplate programming support. * Support for tables with non-uniform row length. #### Graphics * RGBA image support for suitable bitmap types. * LTO labelling barcode. And many bugfixes... ### Contributors * Matt Folwell * Jerome Alet * Harald Armin Massa * kevin@booksys.com * Sebastian Ware * Martin Tate * Wietse Jacobs * Christian Jacobs * Volker Haas * Dinu Gherman * Dirk Datzert * Yuan Hong * Ilpo Nyyss�nen * Thomas Heller * Gael Chardon * Alex Smishlajev * Martin Loewis * Dirk Holtwick * Philippe Makowskic * Ian Sparks * Albertas Agejevas * Gary Poster * Martin Zohlhuber * Francesco Pierfederici * michael@stroeder.com * Derik Barclay * Publio da Costa Melo * Jon Dyte * David Horkoff * picodello@yahoo.it * R�diger M�hl * Paul Winkler * Bernhard Herzog * Alex Martelli * Stuart Bishop * Gael Chardon RELEASE 2.1 at 15:00 GMT 24/May/2007 -------------------------------------- ### Contributors * Ilpo Nyyss�nen * Thomas Heller * Gael Chardon * Alex Smishlajev * Martin Loewis * Dirk Holtwick * Philippe Makowskic * Dinu Gherman * Ian Sparks RELEASE 2.0 at 15:00 GMT 23/May/2006 -------------------------------------- ### Contributions * Andre Reitz * Max M * Albertas Agejevas * T Blatter * Ron Peleg * Gary Poster * Steve Halasz * Andrew Mercer * Paul McNett * Chad Miller ### Unicode support This is the Big One, and the reason some apps may break. You must now pass in text either in UTF-8 or as unicode string objects. The library will handle everything to do with output encoding. There is more information on this below. Since this is the biggest change, we'll start by reviewing how it worked in the past. In ReportLab 1.x, any string input you passed to our APIs was supposed to be in the same encoding as the font you selected for output. If using the default fonts in Acrobat Reader (Helvetica/Times/Courier), you would have implicitly used WinAnsi encoding, which is almost exactly the same as Latin-1. However, if using TrueType fonts, you would have been using UTF-8. For Asian fonts, you had a wide choice of encodings but had to specify which one (e.g Shift-JIS or EUC for Japanese). This state of affairs meant that you had to make sure that every piece of text input was in the same encoding as the font used to display it. With ReportLab 2, none of that necessary. Instead: Here is what's different now: #### Input text encoding is UTF-8 or Python Unicode strings Any text you pass to a canvas API (drawString etc.), Paragraph or other flowable constructor, into a table cell, or as an attribute of a graphic (e.g. chart.title.text), is supposed to be unicode. If you use a traditional Python string, it is assumed to be UTF-8. If you pass a Unicode object, we know it's unicode. #### Font encodings Fonts still work in different ways, and the built-in ones will still use WinAnsi or MacRoman internally while TrueType will use UTF-8. However, the library hides this from you; it converts as it writes out the PDF file. As before, it's still your job to make sure the font you use has the characters you need, or you may get either a traceback or a visible error character. Asian CID fonts You no longer need to specify the encoding for the built-in Asian fonts, just the face name. ReportLab knows about the standard fonts in Adobe's Asian Language Packs. #### Asian Truetype fonts The standard Truetype fonts differ slightly for Asian languages (e.g msmincho.ttc). These can now be read and used, albeit somewhat inefficiently. Asian word wrapping Previously we could display strings in Asian languages, but could not properly wrap paragraphs as there are no gaps between the words. We now have a basic word wrapping algorithm. #### unichar tag A convenience tag, has also been added. You can now do or and get a lowercase u umlaut. Names should be those in the Unicode Character Database. Accents, Greeks and symbols The correct way to refer to all non-ASCII characters is to use their unicode representation. This can be literal Unicode or UTF-8. Special symbols and Greek letters (collectively, "greeks") inserted in paragraphs using the greek tag (e.g. lambda) or using the entity references (e.g. λ) are now processed in a different way than in version 1. Previously, these were always rendered using the Zapf Dingbats font. Now they are always output in the font you specified, unless that font does not support that character. If the font does not support the character, and the font you specified was an Adobe Type 1 font, Zapf Dingbats is used as a fallback. However, at present there is no fallback in the case of TTF fonts. Note that this means that documents that contain greeks and specify a TTF font may need changing to explicitly specify the font to use for the greek character, or you will see a black square in place of that character when you view your PDF output in Acrobat Reader. ### Other New Features #### PDF * Improved low-level annotation support for PDF "free text annotations" FreeTextAnnotation allows showing and hiding of an arbitrary PDF "form" (reusable chunk of PDF content) depending on whether the document is printed or viewed on-screen, or depending on whether the mouse is hovered over the content, etc. * TTC font collection files are now readable: ReportLab now supports using TTF fonts packaged in .TTC files * East Asian font support (CID and TTF): You no longer need to specify the encoding for the built-in Asian fonts, just the face name. ReportLab knows about the standard fonts in Adobe's Asian Language Packs. * Native support for JPEG CMYK images: ReportLab now takes advantage of PDF's native JPEG CMYK image support, so that JPEG CMYK images are no longer (lossily) converted to RGB format before including them in PDF. #### Platypus * Link support in paragraphs: Platypus paragraphs can now contain link elements, which support both internal links to the same PDF document, links to other local PDF documents, and URL links to pages on the web. Some examples: Web links:: ReportLab Internal link to current PDF document:: ReportLab External link to a PDF document on the local filesystem:: ReportLab * Improved wrapping support: Support for wrapping arbitrary sequence of flowables around an image, using reportlab.platypus.flowables.ImageAndFlowables (similar to ParagraphAndImage). * `KeepInFrame`: Sometimes the length of a piece of text you'd like to include in a fixed piece of page "real estate" is not guaranteed to be constrained to a fixed maximum length. In these cases, KeepInFrame allows you to specify an appropriate action to take when the text is too long for the space allocated for it. In particular, it can shrink the text to fit, mask (truncate) overflowing text, allow the text to overflow into the rest of the document, or raise an error. * Improved convenience features for inserting unicode symbols and other characters: `` lets you conveniently insert unicode characters using the standard long name or code point. Characters inserted with the `` tags (e.g. `lambda`) or corresponding entity references (e.g. λ) support arbitrary fonts (rather than only Zapf Dingbats). * Table spans and splitting improved: Cell spanning in tables used to go wrong sometimes when the table split over a page. We believe this is improved, although there are so many table features that it's hard to define correct behaviour in all cases. * `KeepWithNext` improved: Paragraph styles have long had an attribute keepWithNext, but this was buggy when set to True. We believe this is fixed now. keepWithNext is important for widows and orphans control; you typically set it to True on headings, to ensure at least one paragraph appears after the heading and that you don't get headings alone at the bottom of a column. #### Graphics * Barcodes: The barcode package has been added to the standard reportlab toolkit distribution (it used to live separately in our contributions area). It has also seen fairly extensive reworking for production use in a recent project. These changes include adding support for the standard European EAN barcodes (EAN 8 and EAN13). * Improvements to Legending: Instead of manual placement, there is now a attachment point (N, S, E, W, etc.), so that the legend is always automatically positioned correctly relative to the chart. Swatches (the small sample squares of colour / pattern fill sometimes displayed in the legend) can now be automatically created from the graph data. Legends can now have automatically-computed totals (useful for financial applications). * More and better ways to place piechart labels: New smart algorithms for automatic pie chart label positioning have been added. You can now produce nice-looking labels without manual positioning even for awkward cases in big runs of charts. * Adjustable piechart slice ordering: For example. pie charts with lots of small slices can be configured to alternate thin and thick slices to help the label placement algorithm work better. * Improved spiderplots #### Noteworthy bug fixes * Fixes to TTF splitting (patch from Albertas Agejevas): This affected some documents using font subsetting * Tables with spans improved splitting: Splitting of tables across pages did not work correctly when the table had row/column spans * Fix runtime error affecting keepWithNext Older releases -------------- Please refer to subversion backlogs for a low level change list RELEASE 1.20 at 18:00 GMT 25/Nov/2004 RELEASE 1.19 at 18:00 GMT 21/Jan/2004 RELEASE 1.18 at 12:00 GMT 9/Jul/2003 RELEASE 1.17 at 16:00 GMT 3/Jan/2003 RELEASE 1.16 at 16:00 GMT 7/Nov/2002 RELEASE 1.15 at 14:00 GMT 9/Aug/2002 RELEASE 1.14 at 18:00 GMT 28/May/2002 RELEASE 1.13 at 15:00 GMT 27/March/2002 RELEASE 1.12 at 17:00 GMT 28/February/2002 RELEASE 1.11 at 14:00 GMT 12/December/2001 RELEASE 1.10 at 14:00 GMT 06/November/2001 RELEASE 1.09 at 14:00 BST 13/August/2001 RELEASE 1.08 at 12:00 BST 19/June/2001 RELEASE 1.07 at 11:54 BST 2001/05/02 RELEASE 1.06 at 14:00 BST 2001/03/30 RELEASE 1.03 on 2001/02/09 RELEASE 1.02 on 2000/12/11 RELEASE 1.01 on 2000/10/10 RELEASE 1.00 on 2000/07/20 RELEASE 0.95 on 2000/07/14 RELEASE 0.94 on 2000/06/20 reportlab-3.3.0/src/0000755000175000017500000000000012120332301013613 5ustar rptlabrptlabreportlab-3.3.0/src/reportlab/0000755000175000017500000000000012661063724015631 5ustar rptlabrptlabreportlab-3.3.0/src/reportlab/rl_settings.py0000664000175000017500000002623712661063724020554 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details '''default settings for reportlab to override these drop a module rl_local_settings.py parallel to this file or anywhere on the path. ''' import os, sys __version__='3.3.0' __all__=tuple('''allowTableBoundsErrors shapeChecking defaultEncoding defaultGraphicsFontName pageCompression useA85 defaultPageSize defaultImageCaching ZLIB_WARNINGS warnOnMissingFontGlyphs verbose showBoundary emptyTableAction invariant eps_preview_transparent eps_preview eps_ttf_embed eps_ttf_embed_uid overlapAttachedSpace longTableOptimize autoConvertEncoding _FUZZ wrapA85 fsEncodings odbc_driver platypus_link_underline canvas_basefontname allowShortTableRows imageReaderFlags paraFontSizeHeightOffset canvas_baseColor ignoreContainerActions ttfAsciiReadable pdfMultiLine pdfComments debug rtlSupport listWrapOnFakeWidth T1SearchPath TTFSearchPath CMapSearchPath baseUnderlineProportion decimalSymbol errorOnDuplicatePageLabelPage autoGenerateMissingTTFName'''.split()) allowTableBoundsErrors = 1 # set to 0 to die on too large elements in tables in debug (recommend 1 for production use) shapeChecking = 1 defaultEncoding = 'WinAnsiEncoding' # 'WinAnsi' or 'MacRoman' defaultGraphicsFontName= 'Times-Roman' #initializer for STATE_DEFAULTS in shapes.py pageCompression = 1 # default page compression mode useA85 = 1 #set to 0 to disable Ascii Base 85 stream filters defaultPageSize = 'A4' #default page size defaultImageCaching = 0 #set to zero to remove those annoying cached images ZLIB_WARNINGS = 1 warnOnMissingFontGlyphs = 0 #if 1, warns of each missing glyph verbose = 0 showBoundary = 0 # turns on and off boundary behaviour in Drawing emptyTableAction= 'error' # one of 'error', 'indicate', 'ignore' invariant= 0 #produces repeatable,identical PDFs with same timestamp info (for regression testing) eps_preview_transparent= None #set to white etc eps_preview= 1 #set to False to disable eps_ttf_embed= 1 #set to False to disable eps_ttf_embed_uid= 0 #set to 1 to enable overlapAttachedSpace= 1 #if set non false then adajacent flowable space after #and space before are merged (max space is used). longTableOptimize = 1 #default don't use Henning von Bargen's long table optimizations autoConvertEncoding = 0 #convert internally as needed (experimental) _FUZZ= 1e-6 #fuzz for layout arithmetic wrapA85= 0 #set to 1 to get old wrapped line behaviour fsEncodings=('utf8','cp1252','cp430') #encodings to attempt utf8 conversion with odbc_driver= 'odbc' #default odbc driver platypus_link_underline= 0 #paragraph links etc underlined if true canvas_basefontname= 'Helvetica' #this is used to initialize the canvas; if you override to make #something else you are responsible for ensuring the font is registered etc etc #this will be used everywhere and the font family connections will be made #if the bold/italic/bold italic fonts are also registered and defined as a family. allowShortTableRows=1 #allows some rows in a table to be short imageReaderFlags=0 #attempt to convert images into internal memory files to reduce #the number of open files (see lib.utils.ImageReader) #if imageReaderFlags&2 then attempt autoclosing of those files #if imageReaderFlags&4 then cache data #if imageReaderFlags==-1 then use Ralf Schmitt's re-opening approach paraFontSizeHeightOffset= 1 #if true paragraphs start at height-fontSize canvas_baseColor= None #initialize the canvas fill and stroke colors if this is set ignoreContainerActions= 1 #if true then action flowables in flowable _Containers will be ignored ttfAsciiReadable= 1 #smaller subsets when set to 0 pdfMultiLine= 0 #use more lines in pdf etc pdfComments= 0 #put in pdf comments debug= 0 #for debugging code rtlSupport= 0 #set to 1 to attempt import of RTL assistance eg fribidi etc etc listWrapOnFakeWidth= 1 #set to 0/False to force platypus.flowables._listWrapOn to report correct widths #else it reports minimum(required,available) width baseUnderlineProportion= 0.0 #non-zero for doing font size proportional lines in Paragraph.py #by default typical value 0.05. may be overridden on a parastyle. decimalSymbol= '.' #what we use to align floats numerically errorOnDuplicatePageLabelPage= 0 #if True will cause repeated PageLabel page numbers to raise an error. autoGenerateMissingTTFName=0 #if true we try to auto generate any missing TTF font name # places to look for T1Font information T1SearchPath = ( 'c:/Program Files/Adobe/Acrobat 9.0/Resource/Font', 'c:/Program Files/Adobe/Acrobat 8.0/Resource/Font', 'c:/Program Files/Adobe/Acrobat 7.0/Resource/Font', 'c:/Program Files/Adobe/Acrobat 6.0/Resource/Font', #Win32, Acrobat 6 'c:/Program Files/Adobe/Acrobat 5.0/Resource/Font', #Win32, Acrobat 5 'c:/Program Files/Adobe/Acrobat 4.0/Resource/Font', #Win32, Acrobat 4 '%(disk)s/Applications/Python %(sys_version)s/reportlab/fonts', #Mac? '/usr/lib/Acrobat9/Resource/Font', #Linux, Acrobat 5? '/usr/lib/Acrobat8/Resource/Font', #Linux, Acrobat 5? '/usr/lib/Acrobat7/Resource/Font', #Linux, Acrobat 5? '/usr/lib/Acrobat6/Resource/Font', #Linux, Acrobat 5? '/usr/lib/Acrobat5/Resource/Font', #Linux, Acrobat 5? '/usr/lib/Acrobat4/Resource/Font', #Linux, Acrobat 4 '/usr/local/Acrobat9/Resource/Font', #Linux, Acrobat 5? '/usr/local/Acrobat8/Resource/Font', #Linux, Acrobat 5? '/usr/local/Acrobat7/Resource/Font', #Linux, Acrobat 5? '/usr/local/Acrobat6/Resource/Font', #Linux, Acrobat 5? '/usr/local/Acrobat5/Resource/Font', #Linux, Acrobat 5? '/usr/local/Acrobat4/Resource/Font', #Linux, Acrobat 4 '/usr/share/fonts/default/Type1', #Linux, Fedora '%(REPORTLAB_DIR)s/fonts', #special '%(REPORTLAB_DIR)s/../fonts', #special '%(REPORTLAB_DIR)s/../../fonts', #special '%(CWD)s/fonts', #special '~/fonts', '~/.fonts', '%(XDG_DATA_HOME)s/fonts', '~/.local/share/fonts', ) # places to look for TT Font information TTFSearchPath = ( 'c:/winnt/fonts', 'c:/windows/fonts', '/usr/lib/X11/fonts/TrueType/', '/usr/share/fonts/truetype', '/usr/share/fonts', #Linux, Fedora '/usr/share/fonts/dejavu', #Linux, Fedora '%(REPORTLAB_DIR)s/fonts', #special '%(REPORTLAB_DIR)s/../fonts', #special '%(REPORTLAB_DIR)s/../../fonts',#special '%(CWD)s/fonts', #special '~/fonts', '~/.fonts', '%(XDG_DATA_HOME)s/fonts', '~/.local/share/fonts', #mac os X - from #http://developer.apple.com/technotes/tn/tn2024.html '~/Library/Fonts', '/Library/Fonts', '/Network/Library/Fonts', '/System/Library/Fonts', ) # places to look for CMap files - should ideally merge with above CMapSearchPath = ( '/usr/lib/Acrobat9/Resource/CMap', '/usr/lib/Acrobat8/Resource/CMap', '/usr/lib/Acrobat7/Resource/CMap', '/usr/lib/Acrobat6/Resource/CMap', '/usr/lib/Acrobat5/Resource/CMap', '/usr/lib/Acrobat4/Resource/CMap', '/usr/local/Acrobat9/Resource/CMap', '/usr/local/Acrobat8/Resource/CMap', '/usr/local/Acrobat7/Resource/CMap', '/usr/local/Acrobat6/Resource/CMap', '/usr/local/Acrobat5/Resource/CMap', '/usr/local/Acrobat4/Resource/CMap', 'C:\\Program Files\\Adobe\\Acrobat\\Resource\\CMap', 'C:\\Program Files\\Adobe\\Acrobat 9.0\\Resource\\CMap', 'C:\\Program Files\\Adobe\\Acrobat 8.0\\Resource\\CMap', 'C:\\Program Files\\Adobe\\Acrobat 7.0\\Resource\\CMap', 'C:\\Program Files\\Adobe\\Acrobat 6.0\\Resource\\CMap', 'C:\\Program Files\\Adobe\\Acrobat 5.0\\Resource\\CMap', 'C:\\Program Files\\Adobe\\Acrobat 4.0\\Resource\\CMap', '%(REPORTLAB_DIR)s/fonts/CMap', #special '%(REPORTLAB_DIR)s/../fonts/CMap', #special '%(REPORTLAB_DIR)s/../../fonts/CMap', #special '%(CWD)s/fonts/CMap', #special '%(CWD)s/fonts', #special '~/fonts/CMap', '~/.fonts/CMap', '%(XDG_DATA_HOME)s/fonts/CMap', '~/.local/share/fonts/CMap', ) if sys.platform.startswith('linux'): def _findFontDirs(*ROOTS): R = [].append for rootd in ROOTS: for root, dirs, files in os.walk(rootd): if not files: continue R(root) return tuple(R.__self__) T1SearchPath = T1SearchPath + _findFontDirs( '/usr/share/fonts/type1', '/usr/share/fonts/Type1', ) TTFSearchPath = TTFSearchPath + _findFontDirs( '/usr/share/fonts/truetype', '/usr/share/fonts/TTF', ) reportlab-3.3.0/src/reportlab/platypus/0000755000175000017500000000000012661063724017512 5ustar rptlabrptlabreportlab-3.3.0/src/reportlab/platypus/xpreformatted.py0000664000175000017500000003061412661063724022756 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/xpreformatted.py __version__='3.3.0' __doc__='''A 'rich preformatted text' widget allowing internal markup''' from reportlab.lib import PyFontify from reportlab.platypus.paragraph import Paragraph, cleanBlockQuotedText, _handleBulletWidth, \ ParaLines, _getFragWords, stringWidth, getAscentDescent, imgVRange, imgNormV from reportlab.lib.utils import isSeq from reportlab.platypus.flowables import _dedenter def _getFragLines(frags): lines = [] cline = [] W = frags[:] while W != []: w = W[0] t = w.text del W[0] i = t.find('\n') if i>=0: tleft = t[i+1:] cline.append(w.clone(text=t[:i])) lines.append(cline) cline = [] if tleft!='': W.insert(0,w.clone(text=tleft)) else: cline.append(w) if cline!=[]: lines.append(cline) return lines def _split_blPara(blPara,start,stop): f = blPara.clone() for a in ('lines', 'text'): if hasattr(f,a): delattr(f,a) f.lines = blPara.lines[start:stop] return [f] # Will be removed shortly. def _countSpaces(text): return text.count(' ') ## i = 0 ## s = 0 ## while 1: ## j = text.find(' ',i) ## if j<0: return s ## s = s + 1 ## i = j + 1 def _getFragWord(frags,maxWidth): ''' given a fragment list return a list of lists [size, spaces, (f00,w00), ..., (f0n,w0n)] each pair f,w represents a style and some string ''' W = [] n = 0 s = 0 for f in frags: text = f.text[:] W.append((f,text)) cb = getattr(f,'cbDefn',None) if cb: _w = getattr(cb,'width',0) if hasattr(_w,'normalizedValue'): _w._normalizer = maxWidth n = n + stringWidth(text, f.fontName, f.fontSize) #s = s + _countSpaces(text) s = s + text.count(' ') # much faster for many blanks #del f.text # we can't do this until we sort out splitting # of paragraphs return n, s, W class XPreformatted(Paragraph): def __init__(self, text, style, bulletText = None, frags=None, caseSensitive=1, dedent=0): self.caseSensitive = caseSensitive cleaner = lambda text, dedent=dedent: '\n'.join(_dedenter(text or '',dedent)) self._setup(text, style, bulletText, frags, cleaner) def breakLines(self, width): """ Returns a broken line structure. There are two cases A) For the simple case of a single formatting input fragment the output is A fragment specifier with - kind = 0 - fontName, fontSize, leading, textColor - lines= A list of lines Each line has two items: 1. unused width in points 2. a list of words B) When there is more than one input formatting fragment the out put is A fragment specifier with - kind = 1 - lines = A list of fragments each having fields: - extraspace (needed for justified) - fontSize - words=word list - each word is itself a fragment with - various settings This structure can be used to easily draw paragraphs with the various alignments. You can supply either a single width or a list of widths; the latter will have its last item repeated until necessary. A 2-element list is useful when there is a different first line indent; a longer list could be created to facilitate custom wraps around irregular objects.""" if not isSeq(width): maxWidths = [width] else: maxWidths = width lines = [] lineno = 0 maxWidth = maxWidths[lineno] style = self.style fFontSize = float(style.fontSize) requiredWidth = 0 #for bullets, work out width and ensure we wrap the right amount onto line one _handleBulletWidth(self.bulletText,style,maxWidths) self.height = 0 autoLeading = getattr(self,'autoLeading',getattr(style,'autoLeading','')) calcBounds = autoLeading not in ('','off') frags = self.frags nFrags= len(frags) if nFrags==1: f = frags[0] if hasattr(f,'text'): fontSize = f.fontSize fontName = f.fontName ascent, descent = getAscentDescent(fontName,fontSize) kind = 0 L=f.text.split('\n') for l in L: currentWidth = stringWidth(l,fontName,fontSize) requiredWidth = max(currentWidth,requiredWidth) extraSpace = maxWidth-currentWidth lines.append((extraSpace,l.split(' '),currentWidth)) lineno = lineno+1 maxWidth = lineno', ''), 'keyword' : ('', ''), 'parameter' : ('', ''), 'identifier' : ('', ''), 'string' : ('', '') } def __init__(self, text, style, bulletText = None, dedent=0, frags=None): if text: text = self.fontify(self.escapeHtml(text)) XPreformatted.__init__(self, text, style,bulletText=bulletText,dedent=dedent,frags=frags) def escapeHtml(self, text): s = text.replace('&', '&') s = s.replace('<', '<') s = s.replace('>', '>') return s def fontify(self, code): "Return a fontified version of some Python code." if code[0] == '\n': code = code[1:] tags = PyFontify.fontify(code) fontifiedCode = '' pos = 0 for k, i, j, dummy in tags: fontifiedCode = fontifiedCode + code[pos:i] s, e = self.formats[k] fontifiedCode = fontifiedCode + s + code[i:j] + e pos = j fontifiedCode = fontifiedCode + code[pos:] return fontifiedCode if __name__=='__main__': #NORUNTESTS import sys def dumpXPreformattedLines(P): print('\n############dumpXPreforemattedLines(%s)' % str(P)) lines = P.blPara.lines n =len(lines) outw=sys.stdout.write for l in range(n): line = lines[l] words = line.words nwords = len(words) outw('line%d: %d(%d)\n ' % (l,nwords,line.wordCount)) for w in range(nwords): outw(" %d:'%s'"%(w,words[w].text)) print() def dumpXPreformattedFrags(P): print('\n############dumpXPreforemattedFrags(%s)' % str(P)) frags = P.frags n =len(frags) for l in range(n): print("frag%d: '%s'" % (l, frags[l].text)) outw=sys.stdout.write l = 0 for L in _getFragLines(frags): n=0 for W in _getFragWords(L,360): outw("frag%d.%d: size=%d" % (l, n, W[0])) n = n + 1 for w in W[1:]: outw(" '%s'" % w[1]) print() l = l + 1 def try_it(text,style,dedent,aW,aH): P=XPreformatted(text,style,dedent=dedent) dumpXPreformattedFrags(P) w,h = P.wrap(aW, aH) dumpXPreformattedLines(P) S = P.split(aW,aH) dumpXPreformattedLines(P) for s in S: s.wrap(aW,aH) dumpXPreformattedLines(s) aH = 500 from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle styleSheet = getSampleStyleSheet() B = styleSheet['BodyText'] DTstyle = ParagraphStyle("discussiontext", parent=B) DTstyle.fontName= 'Helvetica' for (text,dedent,style, aW, aH, active) in [(''' The CMYK or subtractive method follows the way a printer mixes three pigments (cyan, magenta, and yellow) to form colors. Because mixing chemicals is more difficult than combining light there is a fourth parameter for darkness. For example a chemical combination of the CMY pigments generally never makes a perfect black -- instead producing a muddy color -- so, to get black printers don't use the CMY pigments but use a direct black ink. Because CMYK maps more directly to the way printer hardware works it may be the case that &| & | colors specified in CMYK will provide better fidelity and better control when printed. ''',0,DTstyle, 456.0, 42.8, 0), (''' This is a non rearranging form of the Paragraph class; XML tags are allowed in text and have the same meanings as for the Paragraph class. As for Preformatted, if dedent is non zero dedent common leading spaces will be removed from the front of each line. ''',3, DTstyle, 456.0, 42.8, 0), ("""\ class FastXMLParser: # Nonsense method def nonsense(self): self.foo = 'bar' """,0, styleSheet['Code'], 456.0, 4.8, 1), ]: if active: try_it(text,style,dedent,aW,aH) reportlab-3.3.0/src/reportlab/platypus/figures.py0000664000175000017500000004446612661063724021550 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/figures.py """This includes some demos of platypus for use in the API proposal""" __version__='3.3.0' import os from reportlab.lib import colors from reportlab.pdfgen.canvas import Canvas from reportlab.lib.styles import ParagraphStyle from reportlab.lib.utils import recursiveImport, strTypes from reportlab.platypus import Frame from reportlab.platypus import Flowable from reportlab.platypus import Paragraph from reportlab.lib.units import inch from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER from reportlab.lib.validators import isColor from reportlab.lib.colors import toColor from reportlab.lib.styles import _baseFontName, _baseFontNameI captionStyle = ParagraphStyle('Caption', fontName=_baseFontNameI, fontSize=10, alignment=TA_CENTER) class Figure(Flowable): def __init__(self, width, height, caption="", captionFont=_baseFontNameI, captionSize=12, background=None, captionTextColor=toColor('black'), captionBackColor=None, border=None, spaceBefore=12, spaceAfter=12, captionGap=None, captionAlign='centre', captionPosition='bottom', hAlign='CENTER', ): Flowable.__init__(self) self.width = width self.figureHeight = height self.caption = caption self.captionFont = captionFont self.captionSize = captionSize self.captionTextColor = captionTextColor self.captionBackColor = captionBackColor self.captionGap = captionGap or 0.5*captionSize self.captionAlign = captionAlign self.captionPosition = captionPosition self._captionData = None self.captionHeight = 0 # work out later self.background = background self.border = border self.spaceBefore = spaceBefore self.spaceAfter = spaceAfter self.hAlign=hAlign self._getCaptionPara() #Larry Meyn's fix - otherwise they all get the number of the last chapter. def _getCaptionPara(self): caption = self.caption captionFont = self.captionFont captionSize = self.captionSize captionTextColor = self.captionTextColor captionBackColor = self.captionBackColor captionAlign = self.captionAlign captionPosition = self.captionPosition if self._captionData!=(caption,captionFont,captionSize,captionTextColor,captionBackColor,captionAlign,captionPosition): self._captionData = (caption,captionFont,captionSize,captionTextColor,captionBackColor,captionAlign,captionPosition) if isinstance(caption,Paragraph): self.captionPara = caption elif isinstance(caption,strTypes): self.captionStyle = ParagraphStyle( 'Caption', fontName=captionFont, fontSize=captionSize, leading=1.2*captionSize, textColor = captionTextColor, backColor = captionBackColor, #seems to be getting ignored spaceBefore=self.captionGap, alignment=TA_LEFT if captionAlign=='left' else TA_RIGHT if captionAlign=='right' else TA_CENTER, ) #must build paragraph now to get sequencing in synch with rest of story self.captionPara = Paragraph(self.caption, self.captionStyle) else: raise ValueError('Figure caption of type %r is not a string or Paragraph' % type(caption)) def wrap(self, availWidth, availHeight): # try to get the caption aligned if self.caption: self._getCaptionPara() w, h = self.captionPara.wrap(self.width, availHeight - self.figureHeight) self.captionHeight = h + self.captionGap self.height = self.captionHeight + self.figureHeight if w>self.width: self.width = w else: self.height = self.figureHeight if self.hAlign in ('CENTER','CENTRE',TA_CENTER): self.dx = 0.5 * (availWidth - self.width) elif self.hAlign in ('RIGHT',TA_RIGHT): self.dx = availWidth - self.width else: self.dx = 0 return (self.width, self.height) def draw(self): self.canv.translate(self.dx, 0) if self.caption and self.captionPosition=='bottom': self.canv.translate(0, self.captionHeight) if self.background: self.drawBackground() if self.border: self.drawBorder() self.canv.saveState() self.drawFigure() self.canv.restoreState() if self.caption: if self.captionPosition=='bottom': self.canv.translate(0, -self.captionHeight) else: self.canv.translate(0, self.figureHeight+self.captionGap) self._getCaptionPara() self.drawCaption() def drawBorder(self): canv = self.canv border = self.border bc = getattr(border,'color',None) bw = getattr(border,'width',None) bd = getattr(border,'dashArray',None) ss = bc or bw or bd if ss: canv.saveState() if bc: canv.setStrokeColor(bc) if bw: canv.setLineWidth(bw) if bd: canv.setDash(bd) canv.rect(0, 0, self.width, self.figureHeight,fill=0,stroke=1) if ss: canv.restoreState() def _doBackground(self, color): self.canv.saveState() self.canv.setFillColor(self.background) self.canv.rect(0, 0, self.width, self.figureHeight, fill=1) self.canv.restoreState() def drawBackground(self): """For use when using a figure on a differently coloured background. Allows you to specify a colour to be used as a background for the figure.""" if isColor(self.background): self._doBackground(self.background) else: try: c = toColor(self.background) self._doBackground(c) except: pass def drawCaption(self): self.captionPara.drawOn(self.canv, 0, 0) def drawFigure(self): pass def drawPage(canvas,x, y, width, height): #draws something which looks like a page pth = canvas.beginPath() corner = 0.05*width # shaded backdrop offset a little canvas.setFillColorRGB(0.5,0.5,0.5) canvas.rect(x + corner, y - corner, width, height, stroke=0, fill=1) #'sheet of paper' in light yellow canvas.setFillColorRGB(1,1,0.9) canvas.setLineWidth(0) canvas.rect(x, y, width, height, stroke=1, fill=1) #reset canvas.setFillColorRGB(0,0,0) canvas.setStrokeColorRGB(0,0,0) class PageFigure(Figure): """Shows a blank page in a frame, and draws on that. Used in illustrations of how PLATYPUS works.""" def __init__(self, background=None): Figure.__init__(self, 3*inch, 3*inch) self.caption = 'Figure 1 - a blank page' self.captionStyle = captionStyle self.background = background def drawVirtualPage(self): pass def drawFigure(self): drawPage(self.canv, 0.625*inch, 0.25*inch, 1.75*inch, 2.5*inch) self.canv.translate(0.625*inch, 0.25*inch) self.canv.scale(1.75/8.27, 2.5/11.69) self.drawVirtualPage() class PlatPropFigure1(PageFigure): """This shows a page with a frame on it""" def __init__(self): PageFigure.__init__(self) self.caption = "Figure 1 - a page with a simple frame" def drawVirtualPage(self): demo1(self.canv) class FlexFigure(Figure): """Base for a figure class with a caption. Can grow or shrink in proportion""" def __init__(self, width, height, caption, background=None, captionFont='Helvetica-Oblique',captionSize=8, captionTextColor=colors.black, shrinkToFit=1, growToFit=1, spaceBefore=12, spaceAfter=12, captionGap=9, captionAlign='centre', captionPosition='top', scaleFactor=None, hAlign='CENTER', border=1, ): Figure.__init__(self, width, height, caption, captionFont=captionFont, captionSize=captionSize, background=None, captionTextColor=captionTextColor, spaceBefore = spaceBefore, spaceAfter = spaceAfter, captionGap=captionGap, captionAlign=captionAlign, captionPosition=captionPosition, hAlign=hAlign, border=border, ) self.shrinkToFit = shrinkToFit #if set and wrap is too tight, shrinks self.growToFit = growToFit #if set and wrap is too small, grows self.scaleFactor = scaleFactor self._scaleFactor = None self.background = background def _scale(self,availWidth,availHeight): "Rescale to fit according to the rules, but only once" if self._scaleFactor is None or self.width>availWidth or self.height>availHeight: w, h = Figure.wrap(self, availWidth, availHeight) captionHeight = h - self.figureHeight if self.scaleFactor is None: #scale factor None means auto self._scaleFactor = min(availWidth/self.width,(availHeight-captionHeight)/self.figureHeight) else: #they provided a factor self._scaleFactor = self.scaleFactor if self._scaleFactor<1 and self.shrinkToFit: self.width = self.width * self._scaleFactor - 0.0001 self.figureHeight = self.figureHeight * self._scaleFactor elif self._scaleFactor>1 and self.growToFit: self.width = self.width*self._scaleFactor - 0.0001 self.figureHeight = self.figureHeight * self._scaleFactor def wrap(self, availWidth, availHeight): self._scale(availWidth,availHeight) return Figure.wrap(self, availWidth, availHeight) def split(self, availWidth, availHeight): self._scale(availWidth,availHeight) return Figure.split(self, availWidth, availHeight) class ImageFigure(FlexFigure): """Image with a caption below it""" def __init__(self, filename, caption, background=None,scaleFactor=None,hAlign='CENTER',border=None): assert os.path.isfile(filename), 'image file %s not found' % filename from reportlab.lib.utils import ImageReader w, h = ImageReader(filename).getSize() self.filename = filename FlexFigure.__init__(self, w, h, caption, background,scaleFactor=scaleFactor,hAlign=hAlign,border=border) def drawFigure(self): self.canv.drawImage(self.filename, 0, 0,self.width, self.figureHeight) class DrawingFigure(FlexFigure): """Drawing with a caption below it. Clunky, scaling fails.""" def __init__(self, modulename, classname, caption, baseDir=None, background=None): module = recursiveImport(modulename, baseDir) klass = getattr(module, classname) self.drawing = klass() FlexFigure.__init__(self, self.drawing.width, self.drawing.height, caption, background) self.growToFit = 1 def drawFigure(self): self.canv.scale(self._scaleFactor, self._scaleFactor) self.drawing.drawOn(self.canv, 0, 0) try: from rlextra.pageCatcher.pageCatcher import restoreForms, storeForms, storeFormsInMemory, restoreFormsInMemory _hasPageCatcher = 1 except ImportError: _hasPageCatcher = 0 if _hasPageCatcher: #################################################################### # # PageCatcher plugins # These let you use our PageCatcher product to add figures # to other documents easily. #################################################################### class PageCatcherCachingMixIn: "Helper functions to cache pages for figures" def getFormName(self, pdfFileName, pageNo): #naming scheme works within a directory only dirname, filename = os.path.split(pdfFileName) root, ext = os.path.splitext(filename) return '%s_page%d' % (root, pageNo) def needsProcessing(self, pdfFileName, pageNo): "returns 1 if no forms or form is older" formName = self.getFormName(pdfFileName, pageNo) if os.path.exists(formName + '.frm'): formModTime = os.stat(formName + '.frm')[8] pdfModTime = os.stat(pdfFileName)[8] return (pdfModTime > formModTime) else: return 1 def processPDF(self, pdfFileName, pageNo): formName = self.getFormName(pdfFileName, pageNo) storeForms(pdfFileName, formName + '.frm', prefix= formName + '_', pagenumbers=[pageNo]) #print 'stored %s.frm' % formName return formName + '.frm' class cachePageCatcherFigureNonA4(FlexFigure, PageCatcherCachingMixIn): """PageCatcher page with a caption below it. Size to be supplied.""" # This should merge with PageFigure into one class that reuses # form information to determine the page orientation... def __init__(self, filename, pageNo, caption, width, height, background=None): self.dirname, self.filename = os.path.split(filename) if self.dirname == '': self.dirname = os.curdir self.pageNo = pageNo self.formName = self.getFormName(self.filename, self.pageNo) + '_' + str(pageNo) FlexFigure.__init__(self, width, height, caption, background) def drawFigure(self): self.canv.saveState() if not self.canv.hasForm(self.formName): restorePath = self.dirname + os.sep + self.filename #does the form file exist? if not, generate it. formFileName = self.getFormName(restorePath, self.pageNo) + '.frm' if self.needsProcessing(restorePath, self.pageNo): #print 'preprocessing PDF %s page %s' % (restorePath, self.pageNo) self.processPDF(restorePath, self.pageNo) names = restoreForms(formFileName, self.canv) self.canv.scale(self._scaleFactor, self._scaleFactor) self.canv.doForm(self.formName) self.canv.restoreState() class cachePageCatcherFigure(cachePageCatcherFigureNonA4): """PageCatcher page with a caption below it. Presumes A4, Portrait. This needs our commercial PageCatcher product, or you'll get a blank.""" def __init__(self, filename, pageNo, caption, width=595, height=842, background=None): cachePageCatcherFigureNonA4.__init__(self, filename, pageNo, caption, width, height, background=background) class PageCatcherFigureNonA4(FlexFigure): """PageCatcher page with a caption below it. Size to be supplied.""" # This should merge with PageFigure into one class that reuses # form information to determine the page orientation... _cache = {} def __init__(self, filename, pageNo, caption, width, height, background=None, caching=None): fn = self.filename = filename self.pageNo = pageNo fn = fn.replace(os.sep,'_').replace('/','_').replace('\\','_').replace('-','_').replace(':','_') self.prefix = fn.replace('.','_')+'_'+str(pageNo)+'_' self.formName = self.prefix + str(pageNo) self.caching = caching FlexFigure.__init__(self, width, height, caption, background) def drawFigure(self): if not self.canv.hasForm(self.formName): if self.filename in self._cache: f,data = self._cache[self.filename] else: f = open(self.filename,'rb') pdf = f.read() f.close() f, data = storeFormsInMemory(pdf, pagenumbers=[self.pageNo], prefix=self.prefix) if self.caching=='memory': self._cache[self.filename] = f, data f = restoreFormsInMemory(data, self.canv) self.canv.saveState() self.canv.scale(self._scaleFactor, self._scaleFactor) self.canv.doForm(self.formName) self.canv.restoreState() class PageCatcherFigure(PageCatcherFigureNonA4): """PageCatcher page with a caption below it. Presumes A4, Portrait. This needs our commercial PageCatcher product, or you'll get a blank.""" def __init__(self, filename, pageNo, caption, width=595, height=842, background=None, caching=None): PageCatcherFigureNonA4.__init__(self, filename, pageNo, caption, width, height, background=background, caching=caching) def demo1(canvas): frame = Frame( 2*inch, # x 4*inch, # y at bottom 4*inch, # width 5*inch, # height showBoundary = 1 # helps us see what's going on ) bodyStyle = ParagraphStyle('Body', fontName=_baseFontName, fontSize=24, leading=28, spaceBefore=6) para1 = Paragraph('Spam spam spam spam. ' * 5, bodyStyle) para2 = Paragraph('Eggs eggs eggs. ' * 5, bodyStyle) mydata = [para1, para2] #this does the packing and drawing. The frame will consume #items from the front of the list as it prints them frame.addFromList(mydata,canvas) def test1(): c = Canvas('figures.pdf') f = Frame(inch, inch, 6*inch, 9*inch, showBoundary=1) v = PlatPropFigure1() v.captionTextColor = toColor('blue') v.captionBackColor = toColor('lightyellow') f.addFromList([v],c) c.save() if __name__ == '__main__': test1() reportlab-3.3.0/src/reportlab/platypus/doctemplate.py0000664000175000017500000014055312661063724022377 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/doctemplate.py __version__='3.3.0' __doc__=""" This module contains the core structure of platypus. rlatypus constructs documents. Document styles are determined by DocumentTemplates. Each DocumentTemplate contains one or more PageTemplates which defines the look of the pages of the document. Each PageTemplate has a procedure for drawing the "non-flowing" part of the page (for example the header, footer, page number, fixed logo graphic, watermark, etcetera) and a set of Frames which enclose the flowing part of the page (for example the paragraphs, tables, or non-fixed diagrams of the text). A document is built when a DocumentTemplate is fed a sequence of Flowables. The action of the build consumes the flowables in order and places them onto frames on pages as space allows. When a frame runs out of space the next frame of the page is used. If no frame remains a new page is created. A new page can also be created if a page break is forced. The special invisible flowable NextPageTemplate can be used to specify the page template for the next page (which by default is the one being used for the current frame). """ from reportlab.platypus.flowables import * from reportlab.lib.units import inch from reportlab.platypus.paragraph import Paragraph from reportlab.platypus.frames import Frame from reportlab.rl_config import defaultPageSize, verbose import reportlab.lib.sequencer from reportlab.pdfgen import canvas from reportlab.lib.utils import isSeq, encode_label, decode_label, annotateException, strTypes try: set except NameError: from sets import Set as set import sys import logging logger = logging.getLogger("reportlab.platypus") class LayoutError(Exception): pass def _fSizeString(f): #used to get size during error messages w=getattr(f,'width',None) if w is None: w=getattr(f,'_width',None) h=getattr(f,'height',None) if h is None: h=getattr(f,'_height',None) #tables in particular may have some nasty large culprit if hasattr(f, '_culprit'): c = ', %s, ' % f._culprit() else: c = '' if w is not None or h is not None: if w is None: w='???' if h is None: h='???' return '(%s x %s)%s' % (w,h,c) return '' def _doNothing(canvas, doc): "Dummy callback for onPage" pass class PTCycle(list): def __new__(cls,*args,**kwds): self = list.__new__(cls,*args,**kwds) self._restart = 0 self._idx = 0 return self @property def next_value(self): v = self[self._idx] self._idx += 1 if self._idx>=len(self): self._idx = self._restart return v @property def peek(self): return self[self._idx] class IndexingFlowable(Flowable): """Abstract interface definition for flowables which might hold references to other pages or themselves be targets of cross-references. XRefStart, XRefDest, Table of Contents, Indexes etc.""" def isIndexing(self): return 1 def isSatisfied(self): return 1 def notify(self, kind, stuff): """This will be called by the framework wherever 'stuff' happens. 'kind' will be a value that can be used to decide whether to pay attention or not.""" pass def beforeBuild(self): """Called by multiBuild before it starts; use this to clear old contents""" pass def afterBuild(self): """Called after build ends but before isSatisfied""" pass class ActionFlowable(Flowable): '''This Flowable is never drawn, it can be used for data driven controls For example to change a page template (from one column to two, for example) use NextPageTemplate which creates an ActionFlowable. ''' def __init__(self,action=()): #must call super init to ensure it has a width and height (of zero), #as in some cases the packer might get called on it... Flowable.__init__(self) if not isSeq(action): action = (action,) self.action = tuple(action) def apply(self,doc): ''' This is called by the doc.build processing to allow the instance to implement its behaviour ''' action = self.action[0] args = tuple(self.action[1:]) arn = 'handle_'+action if arn=="handle_nextPageTemplate" and args[0]=='main': pass try: getattr(doc,arn)(*args) except AttributeError as aerr: if aerr.args[0]==arn: raise NotImplementedError("Can't handle ActionFlowable(%s)" % action) else: raise except: annotateException("\nhandle_%s args=%s"%(action,ascii(args))) def __call__(self): return self def identity(self, maxLen=None): return "ActionFlowable: %s%s" % (str(self.action),self._frameName()) class LCActionFlowable(ActionFlowable): locChanger = 1 #we cause a frame or page change def wrap(self, availWidth, availHeight): '''Should never be called.''' raise NotImplementedError def draw(self): '''Should never be called.''' raise NotImplementedError class NextFrameFlowable(ActionFlowable): def __init__(self,ix,resume=0): ActionFlowable.__init__(self,('nextFrame',ix,resume)) class CurrentFrameFlowable(LCActionFlowable): def __init__(self,ix,resume=0): ActionFlowable.__init__(self,('currentFrame',ix,resume)) class NullActionFlowable(ActionFlowable): def apply(self,doc): pass class _FrameBreak(LCActionFlowable): ''' A special ActionFlowable that allows setting doc._nextFrameIndex eg story.append(FrameBreak('mySpecialFrame')) ''' def __call__(self,ix=None,resume=0): r = self.__class__(self.action+(resume,)) r._ix = ix return r def apply(self,doc): if getattr(self,'_ix',None): doc.handle_nextFrame(self._ix) ActionFlowable.apply(self,doc) FrameBreak = _FrameBreak('frameEnd') PageBegin = LCActionFlowable('pageBegin') def _evalMeasurement(n): if isinstance(n,str): from reportlab.platypus.paraparser import _num n = _num(n) if isSeq(n): n = n[1] return n class FrameActionFlowable(Flowable): _fixedWidth = _fixedHeight = 1 def __init__(self,*arg,**kw): raise NotImplementedError('Abstract Class') def frameAction(self,frame): raise NotImplementedError('Abstract Class') class Indenter(FrameActionFlowable): """Increases or decreases left and right margins of frame. This allows one to have a 'context-sensitive' indentation and makes nested lists way easier. """ _ZEROSIZE=True width=0 height=0 def __init__(self, left=0, right=0): self.left = _evalMeasurement(left) self.right = _evalMeasurement(right) def frameAction(self, frame): frame._leftExtraIndent += self.left frame._rightExtraIndent += self.right class NotAtTopPageBreak(FrameActionFlowable): def __init__(self,nextTemplate=None): self.nextTemplate = nextTemplate def frameAction(self,frame): if not frame._atTop: frame.add_generated_content(PageBreak(nextTemplate=self.nextTemplate)) class NextPageTemplate(ActionFlowable): """When you get to the next page, use the template specified (change to two column, for example) """ def __init__(self,pt): ActionFlowable.__init__(self,('nextPageTemplate',pt)) class PageTemplate: """ essentially a list of Frames and an onPage routine to call at the start of a page when this is selected. onPageEnd gets called at the end. derived classes can also implement beforeDrawPage and afterDrawPage if they want """ def __init__(self,id=None,frames=[],onPage=_doNothing, onPageEnd=_doNothing, pagesize=None, autoNextPageTemplate=None, cropBox=None, artBox=None, trimBox=None, bleedBox=None, ): frames = frames or [] if not isSeq(frames): frames = [frames] assert [x for x in frames if not isinstance(x,Frame)]==[], "frames argument error" self.id = id self.frames = frames self.onPage = onPage self.onPageEnd = onPageEnd self.pagesize = pagesize self.autoNextPageTemplate = autoNextPageTemplate self.cropBox = cropBox self.artBox = artBox self.trimBox = trimBox self.bleedBox = bleedBox def beforeDrawPage(self,canv,doc): """Override this if you want additional functionality or prefer a class based page routine. Called before any flowables for this page are processed.""" pass def checkPageSize(self,canv,doc): """This gets called by the template framework If canv size != template size then the canv size is set to the template size or if that's not available to the doc size. """ #### NEVER EVER EVER COMPARE FLOATS FOR EQUALITY #RGB converting pagesizes to ints means we are accurate to one point #RGB I suggest we should be aiming a little better cp = None dp = None sp = None if canv._pagesize: cp = list(map(int, canv._pagesize)) if self.pagesize: sp = list(map(int, self.pagesize)) if doc.pagesize: dp = list(map(int, doc.pagesize)) if cp!=sp: if sp: canv.setPageSize(self.pagesize) elif cp!=dp: canv.setPageSize(doc.pagesize) for box in 'crop','art','trim','bleed': size = getattr(self,box+'Box',None) if size: canv.setCropBox(size,name=box) def afterDrawPage(self, canv, doc): """This is called after the last flowable for the page has been processed. You might use this if the page header or footer needed knowledge of what flowables were drawn on this page.""" pass def _addGeneratedContent(flowables,frame): S = getattr(frame,'_generated_content',None) if S: flowables[0:0] = S del frame._generated_content class onDrawStr(str): def __new__(cls,value,onDraw,label,kind=None): self = str.__new__(cls,value) self.onDraw = onDraw self.kind = kind self.label = label return self class PageAccumulator: '''gadget to accumulate information in a page and then allow it to be interrogated at the end of the page''' _count = 0 def __init__(self,name=None): if name is None: name = self.__class__.__name__+str(self.__class__._count) self.__class__._count += 1 self.name = name self.data = [] def reset(self): self.data[:] = [] def add(self,*args): self.data.append(args) def onDrawText(self,*args): return '' % (self.name,encode_label(args)) def __call__(self,canv,kind,label): self.add(*decode_label(label)) def attachToPageTemplate(self,pt): if pt.onPage: def onPage(canv,doc,oop=pt.onPage): self.onPage(canv,doc) oop(canv,doc) else: def onPage(canv,doc): self.onPage(canv,doc) pt.onPage = onPage if pt.onPageEnd: def onPageEnd(canv,doc,oop=pt.onPageEnd): self.onPageEnd(canv,doc) oop(canv,doc) else: def onPageEnd(canv,doc): self.onPageEnd(canv,doc) pt.onPageEnd = onPageEnd def onPage(self,canv,doc): '''this will be called at the start of the page''' setattr(canv,self.name,self) #push ourselves onto the canvas self.reset() def onPageEnd(self,canv,doc): '''this will be called at the end of a page''' self.pageEndAction(canv,doc) try: delattr(canv,self.name) except: pass self.reset() def pageEndAction(self,canv,doc): '''this should be overridden to do something useful''' pass def onDrawStr(self,value,*args): return onDrawStr(value,self,encode_label(args)) class BaseDocTemplate: """ First attempt at defining a document template class. The basic idea is simple. 1) The document has a list of data associated with it this data should derive from flowables. We'll have special classes like PageBreak, FrameBreak to do things like forcing a page end etc. 2) The document has one or more page templates. 3) Each page template has one or more frames. 4) The document class provides base methods for handling the story events and some reasonable methods for getting the story flowables into the frames. 5) The document instances can override the base handler routines. Most of the methods for this class are not called directly by the user, but in some advanced usages they may need to be overridden via subclassing. EXCEPTION: doctemplate.build(...) must be called for most reasonable uses since it builds a document using the page template. Each document template builds exactly one document into a file specified by the filename argument on initialization. Possible keyword arguments for the initialization: - pageTemplates: A list of templates. Must be nonempty. Names assigned to the templates are used for referring to them so no two used templates should have the same name. For example you might want one template for a title page, one for a section first page, one for a first page of a chapter and two more for the interior of a chapter on odd and even pages. If this argument is omitted then at least one pageTemplate should be provided using the addPageTemplates method before the document is built. - pageSize: a 2-tuple or a size constant from reportlab/lib/pagesizes.pu. Used by the SimpleDocTemplate subclass which does NOT accept a list of pageTemplates but makes one for you; ignored when using pageTemplates. - showBoundary: if set draw a box around the frame boundaries. - leftMargin: - rightMargin: - topMargin: - bottomMargin: Margin sizes in points (default 1 inch). These margins may be overridden by the pageTemplates. They are primarily of interest for the SimpleDocumentTemplate subclass. - allowSplitting: If set flowables (eg, paragraphs) may be split across frames or pages (default: 1) - title: Internal title for document (does not automatically display on any page) - author: Internal author for document (does not automatically display on any page) """ _initArgs = { 'pagesize':defaultPageSize, 'pageTemplates':[], 'showBoundary':0, 'leftMargin':inch, 'rightMargin':inch, 'topMargin':inch, 'bottomMargin':inch, 'allowSplitting':1, 'title':None, 'author':None, 'subject':None, 'creator':None, 'keywords':[], 'invariant':None, 'pageCompression':None, '_pageBreakQuick':1, 'rotation':0, '_debug':0, 'encrypt': None, 'cropMarks': None, 'enforceColorSpace': None, 'displayDocTitle': None, 'lang': None, 'initialFontName': None, 'initialFontSize': None, 'initialLeading': None, 'cropBox': None, 'artBox': None, 'trimBox': None, 'bleedBox': None, } _invalidInitArgs = () _firstPageTemplateIndex = 0 def __init__(self, filename, **kw): """create a document template bound to a filename (see class documentation for keyword arguments)""" self.filename = filename self._nameSpace = dict(doc=self) self._lifetimes = {} for k in self._initArgs.keys(): if k not in kw: v = self._initArgs[k] else: if k in self._invalidInitArgs: raise ValueError("Invalid argument %s" % k) v = kw[k] setattr(self,k,v) p = self.pageTemplates self.pageTemplates = [] self.addPageTemplates(p) # facility to assist multi-build and cross-referencing. # various hooks can put things into here - key is what # you want, value is a page number. This can then be # passed to indexing flowables. self._pageRefs = {} self._indexingFlowables = [] #callback facility for progress monitoring self._onPage = None self._onProgress = None self._flowableCount = 0 # so we know how far to go #infinite loop detection if we start doing lots of empty pages self._curPageFlowableCount = 0 self._emptyPages = 0 self._emptyPagesAllowed = 10 #context sensitive margins - set by story, not from outside self._leftExtraIndent = 0.0 self._rightExtraIndent = 0.0 self._topFlowables = [] self._frameBGs = [] self._calc() self.afterInit() def _calc(self): self._rightMargin = self.pagesize[0] - self.rightMargin self._topMargin = self.pagesize[1] - self.topMargin self.width = self._rightMargin - self.leftMargin self.height = self._topMargin - self.bottomMargin def setPageCallBack(self, func): 'Simple progress monitor - func(pageNo) called on each new page' self._onPage = func def setProgressCallBack(self, func): '''Cleverer progress monitor - func(typ, value) called regularly''' self._onProgress = func def clean_hanging(self): 'handle internal postponed actions' while len(self._hanging): self.handle_flowable(self._hanging) def addPageTemplates(self,pageTemplates): 'add one or a sequence of pageTemplates' if not isSeq(pageTemplates): pageTemplates = [pageTemplates] #this test below fails due to inconsistent imports! #assert filter(lambda x: not isinstance(x,PageTemplate), pageTemplates)==[], "pageTemplates argument error" for t in pageTemplates: self.pageTemplates.append(t) def handle_documentBegin(self): '''implement actions at beginning of document''' self._hanging = [PageBegin] self.pageTemplate = self.pageTemplates[self._firstPageTemplateIndex] self.page = 0 self.beforeDocument() def handle_pageBegin(self): """Perform actions required at beginning of page. shouldn't normally be called directly""" self.page += 1 if self._debug: logger.debug("beginning page %d" % self.page) 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() #keep a count of flowables added to this page. zero indicates bad stuff self._curPageFlowableCount = 0 if hasattr(self,'_nextFrameIndex'): del self._nextFrameIndex self.frame = self.pageTemplate.frames[0] self.frame._debug = self._debug self.handle_frameBegin() def _setPageTemplate(self): if hasattr(self,'_nextPageTemplateCycle'): #they are cycling through pages'; we keep the index self.pageTemplate = self._nextPageTemplateCycle.next_value elif hasattr(self,'_nextPageTemplateIndex'): self.pageTemplate = self.pageTemplates[self._nextPageTemplateIndex] del self._nextPageTemplateIndex elif self.pageTemplate.autoNextPageTemplate: self.handle_nextPageTemplate(self.pageTemplate.autoNextPageTemplate) self.pageTemplate = self.pageTemplates[self._nextPageTemplateIndex] def _samePT(self,npt): if isSeq(npt): return getattr(self,'_nextPageTemplateCycle',[]) if isinstance(npt,strTypes): return npt == (self.pageTemplates[self._nextPageTemplateIndex].id if hasattr(self,'_nextPageTemplateIndex') else self.pageTemplate.id) if isinstance(npt,int) and 0<=npt= self._emptyPagesAllowed: if 1: ident = "More than %d pages generated without content - halting layout. Likely that a flowable is too large for any frame." % self._emptyPagesAllowed #leave to keep apart from the raise raise LayoutError(ident) else: pass #attempt to restore to good state else: if self._onProgress: self._onProgress('PAGE', self.canv.getPageNumber()) self.pageTemplate.afterDrawPage(self.canv, self) self.pageTemplate.onPageEnd(self.canv, self) self.afterPage() if self._debug: logger.debug("ending page %d" % self.page) self.canv.setPageRotation(getattr(self.pageTemplate,'rotation',self.rotation)) self.canv.showPage() self._setPageTemplate() if self._emptyPages==0: pass #store good state here self._hanging.append(PageBegin) def handle_pageBreak(self,slow=None): '''some might choose not to end all the frames''' if self._pageBreakQuick and not slow: self.handle_pageEnd() else: n = len(self._hanging) while len(self._hanging)==n: self.handle_frameEnd() def handle_frameBegin(self,resume=0): '''What to do at the beginning of a frame''' f = self.frame if f._atTop: if self.showBoundary or self.frame.showBoundary: self.frame.drawBoundary(self.canv) f._leftExtraIndent = self._leftExtraIndent f._rightExtraIndent = self._rightExtraIndent f._frameBGs = self._frameBGs if self._topFlowables: self._hanging.extend(self._topFlowables) def handle_frameEnd(self,resume=0): ''' Handles the semantics of the end of a frame. This includes the selection of the next frame or if this is the last frame then invoke pageEnd. ''' self._removeVars(('frame',)) self._leftExtraIndent = self.frame._leftExtraIndent self._rightExtraIndent = self.frame._rightExtraIndent self._frameBGs = self.frame._frameBGs f = self.frame if hasattr(self,'_nextFrameIndex'): self.frame = self.pageTemplate.frames[self._nextFrameIndex] self.frame._debug = self._debug del self._nextFrameIndex self.handle_frameBegin(resume) elif hasattr(f,'lastFrame') or f is self.pageTemplate.frames[-1]: self.handle_pageEnd() self.frame = None else: self.frame = self.pageTemplate.frames[self.pageTemplate.frames.index(f) + 1] self.frame._debug = self._debug self.handle_frameBegin() def handle_nextPageTemplate(self,pt): '''On endPage change to the page template with name or index pt''' if isinstance(pt,strTypes): if hasattr(self, '_nextPageTemplateCycle'): del self._nextPageTemplateCycle for t in self.pageTemplates: if t.id == pt: self._nextPageTemplateIndex = self.pageTemplates.index(t) return raise ValueError("can't find template('%s')"%pt) elif isinstance(pt,int): if hasattr(self, '_nextPageTemplateCycle'): del self._nextPageTemplateCycle self._nextPageTemplateIndex = pt elif isSeq(pt): #used for alternating left/right pages #collect the refs to the template objects, complain if any are bad c = PTCycle() for ptn in pt: found = 0 if ptn=='*': #special case name used to short circuit the iteration c._restart = len(c) continue for t in self.pageTemplates: if t.id == ptn: c.append(t) found = 1 if not found: raise ValueError("Cannot find page template called %s" % ptn) if not c: raise ValueError("No valid page templates in cycle") elif c._restart>len(c): raise ValueError("Invalid cycle restart position") #ensure we start on the first one self._nextPageTemplateCycle = c else: raise TypeError("argument pt should be string or integer or list") def handle_nextFrame(self,fx,resume=0): '''On endFrame change to the frame with name or index fx''' if isinstance(fx,strTypes): for f in self.pageTemplate.frames: if f.id == fx: self._nextFrameIndex = self.pageTemplate.frames.index(f) return raise ValueError("can't find frame('%s') in %r(%s) which has frames %r"%(fx,self.pageTemplate,self.pageTemplate.id,[(f,f.id) for f in self.pageTemplate.frames])) elif isinstance(fx,int): self._nextFrameIndex = fx else: raise TypeError("argument fx should be string or integer") def handle_currentFrame(self,fx,resume=0): '''change to the frame with name or index fx''' self.handle_nextFrame(fx,resume) self.handle_frameEnd(resume) def handle_breakBefore(self, flowables): '''preprocessing step to allow pageBreakBefore and frameBreakBefore attributes''' first = flowables[0] # if we insert a page break before, we'll process that, see it again, # and go in an infinite loop. So we need to set a flag on the object # saying 'skip me'. This should be unset on the next pass if hasattr(first, '_skipMeNextTime'): delattr(first, '_skipMeNextTime') return # this could all be made much quicker by putting the attributes # in to the flowables with a defult value of 0 if hasattr(first,'pageBreakBefore') and first.pageBreakBefore == 1: first._skipMeNextTime = 1 first.insert(0, PageBreak()) return if hasattr(first,'style') and hasattr(first.style, 'pageBreakBefore') and first.style.pageBreakBefore == 1: first._skipMeNextTime = 1 flowables.insert(0, PageBreak()) return if hasattr(first,'frameBreakBefore') and first.frameBreakBefore == 1: first._skipMeNextTime = 1 flowables.insert(0, FrameBreak()) return if hasattr(first,'style') and hasattr(first.style, 'frameBreakBefore') and first.style.frameBreakBefore == 1: first._skipMeNextTime = 1 flowables.insert(0, FrameBreak()) return def handle_keepWithNext(self, flowables): "implements keepWithNext" i = 0 n = len(flowables) while i maxPasses: raise IndexError("Index entries not resolved after %d passes" % maxPasses) #work through any edits while mbe: e = mbe.pop(0) e[0](*e[1:]) del self._multiBuildEdits if verbose: print('saved') return passes #these are pure virtuals override in derived classes #NB these get called at suitable places by the base class #so if you derive and override the handle_xxx methods #it's up to you to ensure that they maintain the needed consistency def afterInit(self): """This is called after initialisation of the base class.""" pass def beforeDocument(self): """This is called before any processing is done on the document.""" pass def beforePage(self): """This is called at the beginning of page processing, and immediately before the beforeDrawPage method of the current page template.""" pass def afterPage(self): """This is called after page processing, and immediately after the afterDrawPage method of the current page template.""" pass def filterFlowables(self,flowables): '''called to filter flowables at the start of the main handle_flowable method. Upon return if flowables[0] has been set to None it is discarded and the main method returns. ''' pass def afterFlowable(self, flowable): '''called after a flowable has been rendered''' pass _allowedLifetimes = 'page','frame','build','forever' def docAssign(self,var,expr,lifetime): if not isinstance(expr,strTypes): expr=str(expr) expr=expr.strip() var=var.strip() self.docExec('%s=(%s)'%(var.strip(),expr.strip()),lifetime) def docExec(self,stmt,lifetime): stmt=stmt.strip() NS=self._nameSpace K0=list(NS.keys()) try: if lifetime not in self._allowedLifetimes: raise ValueError('bad lifetime %r not in %r'%(lifetime,self._allowedLifetimes)) exec(stmt, {},NS) except: exc = sys.exc_info()[1] args = list(exc.args) msg = '\ndocExec %s lifetime=%r failed!' % (stmt,lifetime) args.append(msg) exc.args = tuple(args) for k in NS.keys(): if k not in K0: del NS[k] raise self._addVars([k for k in NS.keys() if k not in K0],lifetime) def _addVars(self,vars,lifetime): '''add namespace variables to lifetimes lists''' LT=self._lifetimes for var in vars: for v in LT.values(): if var in v: v.remove(var) LT.setdefault(lifetime,set([])).add(var) def _removeVars(self,lifetimes): '''remove namespace variables for with lifetime in lifetimes''' LT=self._lifetimes NS=self._nameSpace for lifetime in lifetimes: for k in LT.setdefault(lifetime,[]): try: del NS[k] except KeyError: pass del LT[lifetime] def docEval(self,expr): try: return eval(expr.strip(),{},self._nameSpace) except: exc = sys.exc_info()[1] args = list(exc.args) args[-1] += '\ndocEval %s failed!' % expr exc.args = tuple(args) raise class SimpleDocTemplate(BaseDocTemplate): """A special case document template that will handle many simple documents. See documentation for BaseDocTemplate. No pageTemplates are required for this special case. A page templates are inferred from the margin information and the onFirstPage, onLaterPages arguments to the build method. A document which has all pages with the same look except for the first page may can be built using this special approach. """ _invalidInitArgs = ('pageTemplates',) def handle_pageBegin(self): '''override base method to add a change of page template after the firstpage. ''' self._handle_pageBegin() self._handle_nextPageTemplate('Later') def build(self,flowables,onFirstPage=_doNothing, onLaterPages=_doNothing, canvasmaker=canvas.Canvas): """build the document using the flowables. Annotate the first page using the onFirstPage function and later pages using the onLaterPages function. The onXXX pages should follow the signature def myOnFirstPage(canvas, document): # do annotations and modify the document ... The functions can do things like draw logos, page numbers, footers, etcetera. They can use external variables to vary the look (for example providing page numbering or section names). """ self._calc() #in case we changed margins sizes etc frameT = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='normal') self.addPageTemplates([PageTemplate(id='First',frames=frameT, onPage=onFirstPage,pagesize=self.pagesize), PageTemplate(id='Later',frames=frameT, onPage=onLaterPages,pagesize=self.pagesize)]) if onFirstPage is _doNothing and hasattr(self,'onFirstPage'): self.pageTemplates[0].beforeDrawPage = self.onFirstPage if onLaterPages is _doNothing and hasattr(self,'onLaterPages'): self.pageTemplates[1].beforeDrawPage = self.onLaterPages BaseDocTemplate.build(self,flowables, canvasmaker=canvasmaker) def progressCB(typ, value): """Example prototype for progress monitoring. This aims to provide info about what is going on during a big job. It should enable, for example, a reasonably smooth progress bar to be drawn. We design the argument signature to be predictable and conducive to programming in other (type safe) languages. If set, this will be called repeatedly with pairs of values. The first is a string indicating the type of call; the second is a numeric value. typ 'STARTING', value = 0 typ 'SIZE_EST', value = numeric estimate of job size typ 'PASS', value = number of this rendering pass typ 'PROGRESS', value = number between 0 and SIZE_EST typ 'PAGE', value = page number of page type 'FINISHED', value = 0 The sequence is STARTING - always called once SIZE_EST - always called once PROGRESS - called often PAGE - called often when page is emitted FINISHED - called when really, really finished some juggling is needed to accurately estimate numbers of pages in pageDrawing mode. NOTE: the SIZE_EST is a guess. It is possible that the PROGRESS value may slightly exceed it, or may even step back a little on rare occasions. The only way to be really accurate would be to do two passes, and I don't want to take that performance hit. """ print('PROGRESS MONITOR: %-10s %d' % (typ, value)) if __name__ == '__main__': from reportlab.lib.styles import _baseFontName, _baseFontNameB def myFirstPage(canvas, doc): from reportlab.lib.colors import red PAGE_HEIGHT = canvas._pagesize[1] canvas.saveState() canvas.setStrokeColor(red) canvas.setLineWidth(5) canvas.line(66,72,66,PAGE_HEIGHT-72) canvas.setFont(_baseFontNameB,24) canvas.drawString(108, PAGE_HEIGHT-108, "TABLE OF CONTENTS DEMO") canvas.setFont(_baseFontName,12) canvas.drawString(4 * inch, 0.75 * inch, "First Page") canvas.restoreState() def myLaterPages(canvas, doc): from reportlab.lib.colors import red PAGE_HEIGHT = canvas._pagesize[1] canvas.saveState() canvas.setStrokeColor(red) canvas.setLineWidth(5) canvas.line(66,72,66,PAGE_HEIGHT-72) canvas.setFont(_baseFontName,12) canvas.drawString(4 * inch, 0.75 * inch, "Page %d" % doc.page) canvas.restoreState() def run(): objects_to_draw = [] from reportlab.lib.styles import ParagraphStyle #from paragraph import Paragraph from reportlab.platypus.doctemplate import SimpleDocTemplate #need a style normal = ParagraphStyle('normal') normal.firstLineIndent = 18 normal.spaceBefore = 6 from reportlab.lib.randomtext import randomText import random for i in range(15): height = 0.5 + (2*random.random()) box = XBox(6 * inch, height * inch, 'Box Number %d' % i) objects_to_draw.append(box) para = Paragraph(randomText(), normal) objects_to_draw.append(para) SimpleDocTemplate('doctemplate.pdf').build(objects_to_draw, onFirstPage=myFirstPage,onLaterPages=myLaterPages) run() reportlab-3.3.0/src/reportlab/platypus/__init__.py0000775000175000017500000000225612661063724021635 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/__init__.py __version__='3.3.0' __doc__='''Page Layout and Typography Using Scripts" - higher-level framework for flowing documents''' from reportlab.platypus.flowables import Flowable, Image, Macro, PageBreak, Preformatted, Spacer, XBox, \ CondPageBreak, KeepTogether, TraceInfo, FailOnWrap, FailOnDraw, PTOContainer, \ KeepInFrame, ParagraphAndImage, ImageAndFlowables, ListFlowable, ListItem, FrameBG, \ PageBreakIfNotEmpty from reportlab.platypus.paragraph import Paragraph, cleanBlockQuotedText, ParaLines from reportlab.platypus.paraparser import ParaFrag from reportlab.platypus.tables import Table, TableStyle, CellStyle, LongTable from reportlab.platypus.frames import Frame from reportlab.platypus.doctemplate import BaseDocTemplate, NextPageTemplate, PageTemplate, ActionFlowable, \ SimpleDocTemplate, FrameBreak, PageBegin, Indenter, NotAtTopPageBreak from reportlab.platypus.xpreformatted import XPreformatted reportlab-3.3.0/src/reportlab/platypus/paraparser.py0000664000175000017500000013552012661063724022234 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/paraparser.py __version__='3.3.0' __doc__='''The parser used to process markup within paragraphs''' import string import re import sys import os import copy import base64 from pprint import pprint as pp import unicodedata import reportlab.lib.sequencer from reportlab.lib.abag import ABag from reportlab.lib.utils import ImageReader, isPy3, annotateException, encode_label, asUnicode, asBytes, uniChr, isStr from reportlab.lib.colors import toColor, white, black, red, Color from reportlab.lib.fonts import tt2ps, ps2tt from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY from reportlab.lib.units import inch,mm,cm,pica if isPy3: from html.parser import HTMLParser from html.entities import name2codepoint else: from HTMLParser import HTMLParser from htmlentitydefs import name2codepoint _re_para = re.compile(r'^\s*<\s*para(?:\s+|>|/>)') sizeDelta = 2 # amount to reduce font size by for super and sub script subFraction = 0.5 # fraction of font size that a sub script should be lowered supFraction = 0.5 # fraction of font size that a super script should be raised DEFAULT_INDEX_NAME='_indexAdd' def _convnum(s, unit=1, allowRelative=True): if s[0] in ('+','-') and allowRelative: try: return ('relative',int(s)*unit) except ValueError: return ('relative',float(s)*unit) else: try: return int(s)*unit except ValueError: return float(s)*unit def _num(s, unit=1, allowRelative=True): """Convert a string like '10cm' to an int or float (in points). The default unit is point, but optionally you can use other default units like mm. """ if s.endswith('cm'): unit=cm s = s[:-2] if s.endswith('in'): unit=inch s = s[:-2] if s.endswith('pt'): unit=1 s = s[:-2] if s.endswith('i'): unit=inch s = s[:-1] if s.endswith('mm'): unit=mm s = s[:-2] if s.endswith('pica'): unit=pica s = s[:-4] return _convnum(s,unit,allowRelative) def _int(s): try: return int(s) except: raise ValueError('cannot convert %r to int' % s) def _bool(s): s = s.lower() if s in ('true','1','yes'): return True if s in ('false','0','no'): return False raise ValueError('cannot convert %r to bool value' % s) def _numpct(s,unit=1,allowRelative=False): if s.endswith('%'): return _PCT(_convnum(s[:-1],allowRelative=allowRelative)) else: return _num(s,unit,allowRelative) class _PCT: def __init__(self,v): self._value = v*0.01 def normalizedValue(self,normalizer): normalizer = normalizer or getattr(self,'_normalizer') return normalizer*self._value def _valignpc(s): s = s.lower() if s in ('baseline','sub','super','top','text-top','middle','bottom','text-bottom'): return s if s.endswith('%'): n = _convnum(s[:-1]) if isinstance(n,tuple): n = n[1] return _PCT(n) n = _num(s) if isinstance(n,tuple): n = n[1] return n def _autoLeading(x): x = x.lower() if x in ('','min','max','off'): return x raise ValueError('Invalid autoLeading=%r' % x ) def _align(s): s = s.lower() if s=='left': return TA_LEFT elif s=='right': return TA_RIGHT elif s=='justify': return TA_JUSTIFY elif s in ('centre','center'): return TA_CENTER else: raise ValueError('illegal alignment %r' % s) def _bAnchor(s): s = s.lower() if not s in ('start','middle','end','numeric'): raise ValueError('illegal bullet anchor %r' % s) return s def _wordWrapConv(s): s = s.upper().strip() if not s: return None if s not in ('CJK','RTL','LTR'): raise ValueError('cannot convert wordWrap=%r' % s) return s def _textTransformConv(s): s = s.lower().strip() if not s: return None if s not in ('uppercase','lowercase','capitalize','none'): raise ValueError('cannot convert wordWrap=%r' % s) return s _paraAttrMap = {'font': ('fontName', None), 'face': ('fontName', None), 'fontsize': ('fontSize', _num), 'size': ('fontSize', _num), 'leading': ('leading', _num), 'autoleading': ('autoLeading', _autoLeading), 'lindent': ('leftIndent', _num), 'rindent': ('rightIndent', _num), 'findent': ('firstLineIndent', _num), 'align': ('alignment', _align), 'spaceb': ('spaceBefore', _num), 'spacea': ('spaceAfter', _num), 'bfont': ('bulletFontName', None), 'bfontsize': ('bulletFontSize',_num), 'boffsety': ('bulletOffsetY',_num), 'bindent': ('bulletIndent',_num), 'bcolor': ('bulletColor',toColor), 'banchor': ('bulletAnchor',_bAnchor), 'color':('textColor',toColor), 'backcolor':('backColor',toColor), 'bgcolor':('backColor',toColor), 'bg':('backColor',toColor), 'fg': ('textColor',toColor), 'justifybreaks': ('justifyBreaks',_bool), 'justifylastline': ('justifyLastLine',_int), 'wordwrap': ('wordWrap',_wordWrapConv), 'allowwidows': ('allowWidows',_bool), 'alloworphans': ('allowOrphans',_bool), 'splitlongwords': ('splitLongWords',_bool), 'borderwidth': ('borderWidth',_num), 'borderpadding': ('borderpadding',_num), 'bordercolor': ('borderColor',toColor), 'borderradius': ('borderRadius',_num), 'texttransform':('textTransform',_textTransformConv), 'enddots':('endDots',None), 'underlineproportion':('underlineProportion',_num), } _bulletAttrMap = { 'font': ('bulletFontName', None), 'face': ('bulletFontName', None), 'size': ('bulletFontSize',_num), 'fontsize': ('bulletFontSize',_num), 'offsety': ('bulletOffsetY',_num), 'indent': ('bulletIndent',_num), 'color': ('bulletColor',toColor), 'fg': ('bulletColor',toColor), 'anchor': ('bulletAnchor',_bAnchor), } #things which are valid font attributes _fontAttrMap = {'size': ('fontSize', _num), 'face': ('fontName', None), 'name': ('fontName', None), 'fg': ('textColor', toColor), 'color':('textColor', toColor), 'backcolor':('backColor',toColor), 'bgcolor':('backColor',toColor), } #things which are valid span attributes _spanAttrMap = {'size': ('fontSize', _num), 'face': ('fontName', None), 'name': ('fontName', None), 'fg': ('textColor', toColor), 'color':('textColor', toColor), 'backcolor':('backColor',toColor), 'bgcolor':('backColor',toColor), 'style': ('style',None), } #things which are valid font attributes _linkAttrMap = {'size': ('fontSize', _num), 'face': ('fontName', None), 'name': ('fontName', None), 'fg': ('textColor', toColor), 'color':('textColor', toColor), 'backcolor':('backColor',toColor), 'bgcolor':('backColor',toColor), 'dest': ('link', None), 'destination': ('link', None), 'target': ('link', None), 'href': ('link', None), } _anchorAttrMap = {'fontSize': ('fontSize', _num), 'fontName': ('fontName', None), 'name': ('name', None), 'fg': ('textColor', toColor), 'color':('textColor', toColor), 'backcolor':('backColor',toColor), 'bgcolor':('backColor',toColor), 'href': ('href', None), } _imgAttrMap = { 'src': ('src', None), 'width': ('width',_numpct), 'height':('height',_numpct), 'valign':('valign',_valignpc), } _indexAttrMap = { 'name': ('name',None), 'item': ('item',None), 'offset': ('offset',None), 'format': ('format',None), } _supAttrMap = { 'rise': ('supr', _num), 'size': ('sups', _num), } def _addAttributeNames(m): K = list(m.keys()) for k in K: n = m[k][0] if n not in m: m[n] = m[k] n = n.lower() if n not in m: m[n] = m[k] _addAttributeNames(_paraAttrMap) _addAttributeNames(_fontAttrMap) _addAttributeNames(_spanAttrMap) _addAttributeNames(_bulletAttrMap) _addAttributeNames(_anchorAttrMap) _addAttributeNames(_linkAttrMap) def _applyAttributes(obj, attr): for k, v in attr.items(): if isinstance(v,(list,tuple)) and v[0]=='relative': if hasattr(obj, k): v = v[1]+getattr(obj,k) else: v = v[1] setattr(obj,k,v) #Named character entities intended to be supported from the special font #with additions suggested by Christoph Zwerschke who also suggested the #numeric entity names that follow. greeks = { 'Aacute': u'\xc1', 'aacute': u'\xe1', 'Acirc': u'\xc2', 'acirc': u'\xe2', 'acute': u'\xb4', 'AElig': u'\xc6', 'aelig': u'\xe6', 'Agrave': u'\xc0', 'agrave': u'\xe0', 'alefsym': u'\u2135', 'Alpha': u'\u0391', 'alpha': u'\u03b1', 'and': u'\u2227', 'ang': u'\u2220', 'Aring': u'\xc5', 'aring': u'\xe5', 'asymp': u'\u2248', 'Atilde': u'\xc3', 'atilde': u'\xe3', 'Auml': u'\xc4', 'auml': u'\xe4', 'bdquo': u'\u201e', 'Beta': u'\u0392', 'beta': u'\u03b2', 'brvbar': u'\xa6', 'bull': u'\u2022', 'cap': u'\u2229', 'Ccedil': u'\xc7', 'ccedil': u'\xe7', 'cedil': u'\xb8', 'cent': u'\xa2', 'Chi': u'\u03a7', 'chi': u'\u03c7', 'circ': u'\u02c6', 'clubs': u'\u2663', 'cong': u'\u2245', 'copy': u'\xa9', 'crarr': u'\u21b5', 'cup': u'\u222a', 'curren': u'\xa4', 'dagger': u'\u2020', 'Dagger': u'\u2021', 'darr': u'\u2193', 'dArr': u'\u21d3', 'deg': u'\xb0', 'delta': u'\u03b4', 'Delta': u'\u2206', 'diams': u'\u2666', 'divide': u'\xf7', 'Eacute': u'\xc9', 'eacute': u'\xe9', 'Ecirc': u'\xca', 'ecirc': u'\xea', 'Egrave': u'\xc8', 'egrave': u'\xe8', 'empty': u'\u2205', 'emsp': u'\u2003', 'ensp': u'\u2002', 'Epsilon': u'\u0395', 'epsilon': u'\u03b5', 'epsiv': u'\u03b5', 'equiv': u'\u2261', 'Eta': u'\u0397', 'eta': u'\u03b7', 'ETH': u'\xd0', 'eth': u'\xf0', 'Euml': u'\xcb', 'euml': u'\xeb', 'euro': u'\u20ac', 'exist': u'\u2203', 'fnof': u'\u0192', 'forall': u'\u2200', 'frac12': u'\xbd', 'frac14': u'\xbc', 'frac34': u'\xbe', 'frasl': u'\u2044', 'Gamma': u'\u0393', 'gamma': u'\u03b3', 'ge': u'\u2265', 'harr': u'\u2194', 'hArr': u'\u21d4', 'hearts': u'\u2665', 'hellip': u'\u2026', 'Iacute': u'\xcd', 'iacute': u'\xed', 'Icirc': u'\xce', 'icirc': u'\xee', 'iexcl': u'\xa1', 'Igrave': u'\xcc', 'igrave': u'\xec', 'image': u'\u2111', 'infin': u'\u221e', 'int': u'\u222b', 'Iota': u'\u0399', 'iota': u'\u03b9', 'iquest': u'\xbf', 'isin': u'\u2208', 'Iuml': u'\xcf', 'iuml': u'\xef', 'Kappa': u'\u039a', 'kappa': u'\u03ba', 'Lambda': u'\u039b', 'lambda': u'\u03bb', 'lang': u'\u2329', 'laquo': u'\xab', 'larr': u'\u2190', 'lArr': u'\u21d0', 'lceil': u'\uf8ee', 'ldquo': u'\u201c', 'le': u'\u2264', 'lfloor': u'\uf8f0', 'lowast': u'\u2217', 'loz': u'\u25ca', 'lrm': u'\u200e', 'lsaquo': u'\u2039', 'lsquo': u'\u2018', 'macr': u'\xaf', 'mdash': u'\u2014', 'micro': u'\xb5', 'middot': u'\xb7', 'minus': u'\u2212', 'mu': u'\xb5', 'Mu': u'\u039c', 'nabla': u'\u2207', 'nbsp': u'\xa0', 'ndash': u'\u2013', 'ne': u'\u2260', 'ni': u'\u220b', 'notin': u'\u2209', 'not': u'\xac', 'nsub': u'\u2284', 'Ntilde': u'\xd1', 'ntilde': u'\xf1', 'Nu': u'\u039d', 'nu': u'\u03bd', 'Oacute': u'\xd3', 'oacute': u'\xf3', 'Ocirc': u'\xd4', 'ocirc': u'\xf4', 'OElig': u'\u0152', 'oelig': u'\u0153', 'Ograve': u'\xd2', 'ograve': u'\xf2', 'oline': u'\uf8e5', 'omega': u'\u03c9', 'Omega': u'\u2126', 'Omicron': u'\u039f', 'omicron': u'\u03bf', 'oplus': u'\u2295', 'ordf': u'\xaa', 'ordm': u'\xba', 'or': u'\u2228', 'Oslash': u'\xd8', 'oslash': u'\xf8', 'Otilde': u'\xd5', 'otilde': u'\xf5', 'otimes': u'\u2297', 'Ouml': u'\xd6', 'ouml': u'\xf6', 'para': u'\xb6', 'part': u'\u2202', 'permil': u'\u2030', 'perp': u'\u22a5', 'phis': u'\u03c6', 'Phi': u'\u03a6', 'phi': u'\u03d5', 'piv': u'\u03d6', 'Pi': u'\u03a0', 'pi': u'\u03c0', 'plusmn': u'\xb1', 'pound': u'\xa3', 'prime': u'\u2032', 'Prime': u'\u2033', 'prod': u'\u220f', 'prop': u'\u221d', 'Psi': u'\u03a8', 'psi': u'\u03c8', 'radic': u'\u221a', 'rang': u'\u232a', 'raquo': u'\xbb', 'rarr': u'\u2192', 'rArr': u'\u21d2', 'rceil': u'\uf8f9', 'rdquo': u'\u201d', 'real': u'\u211c', 'reg': u'\xae', 'rfloor': u'\uf8fb', 'Rho': u'\u03a1', 'rho': u'\u03c1', 'rlm': u'\u200f', 'rsaquo': u'\u203a', 'rsquo': u'\u2019', 'sbquo': u'\u201a', 'Scaron': u'\u0160', 'scaron': u'\u0161', 'sdot': u'\u22c5', 'sect': u'\xa7', 'shy': u'\xad', 'sigmaf': u'\u03c2', 'sigmav': u'\u03c2', 'Sigma': u'\u03a3', 'sigma': u'\u03c3', 'sim': u'\u223c', 'spades': u'\u2660', 'sube': u'\u2286', 'sub': u'\u2282', 'sum': u'\u2211', 'sup1': u'\xb9', 'sup2': u'\xb2', 'sup3': u'\xb3', 'supe': u'\u2287', 'sup': u'\u2283', 'szlig': u'\xdf', 'Tau': u'\u03a4', 'tau': u'\u03c4', 'there4': u'\u2234', 'thetasym': u'\u03d1', 'thetav': u'\u03d1', 'Theta': u'\u0398', 'theta': u'\u03b8', 'thinsp': u'\u2009', 'THORN': u'\xde', 'thorn': u'\xfe', 'tilde': u'\u02dc', 'times': u'\xd7', 'trade': u'\uf8ea', 'Uacute': u'\xda', 'uacute': u'\xfa', 'uarr': u'\u2191', 'uArr': u'\u21d1', 'Ucirc': u'\xdb', 'ucirc': u'\xfb', 'Ugrave': u'\xd9', 'ugrave': u'\xf9', 'uml': u'\xa8', 'upsih': u'\u03d2', 'Upsilon': u'\u03a5', 'upsilon': u'\u03c5', 'Uuml': u'\xdc', 'uuml': u'\xfc', 'weierp': u'\u2118', 'Xi': u'\u039e', 'xi': u'\u03be', 'Yacute': u'\xdd', 'yacute': u'\xfd', 'yen': u'\xa5', 'yuml': u'\xff', 'Yuml': u'\u0178', 'Zeta': u'\u0396', 'zeta': u'\u03b6', 'zwj': u'\u200d', 'zwnj': u'\u200c', } known_entities = dict([(k,uniChr(v)) for k,v in name2codepoint.items()]) for k in greeks: if k not in known_entities: known_entities[k] = greeks[k] f = isPy3 and asBytes or asUnicode K = list(known_entities.keys()) for k in K: known_entities[f(k)] = known_entities[k] del k, f, K #------------------------------------------------------------------------ class ParaFrag(ABag): """class ParaFrag contains the intermediate representation of string segments as they are being parsed by the ParaParser. fontname, fontSize, rise, textColor, cbDefn """ _greek2Utf8=None def _greekConvert(data): global _greek2Utf8 if not _greek2Utf8: from reportlab.pdfbase.rl_codecs import RL_Codecs import codecs #our decoding map dm = codecs.make_identity_dict(range(32,256)) for k in range(0,32): dm[k] = None dm.update(RL_Codecs._RL_Codecs__rl_codecs_data['symbol'][0]) _greek2Utf8 = {} for k,v in dm.items(): if not v: u = '\0' else: if isPy3: u = chr(v) else: u = unichr(v).encode('utf8') _greek2Utf8[chr(k)] = u return ''.join(map(_greek2Utf8.__getitem__,data)) #------------------------------------------------------------------ # !!! NOTE !!! THIS TEXT IS NOW REPLICATED IN PARAGRAPH.PY !!! # The ParaFormatter will be able to format the following # tags: # < /b > - bold # < /i > - italics # < u > < /u > - underline # < strike > < /strike > - strike through # < super [size="pts"] [rise="pts"]> < /super > - superscript # < sup ="pts"] [rise="pts"]> < /sup > - superscript # < sub ="pts"] [rise="pts"]> < /sub > - subscript # # # < bullet > - bullet text (at head of para only) # # # link text # attributes of links # size/fontSize=num # name/face/fontName=name # fg/textColor/color=color # backcolor/backColor/bgcolor=color # dest/destination/target/href/link=target # anchor text # attributes of anchors # fontSize=num # fontName=name # fg/textColor/color=color # backcolor/backColor/bgcolor=color # href=href # # # # # width="w%" --> fontSize*w/100 idea from Roberto Alsina # height="h%" --> linewidth*h/100 # - # # The whole may be surrounded by tags # # It will also be able to handle any MathML specified Greek characters. #------------------------------------------------------------------ class ParaParser(HTMLParser): #---------------------------------------------------------- # First we will define all of the xml tag handler functions. # # start_(attributes) # end_() # # While parsing the xml ParaFormatter will call these # functions to handle the string formatting tags. # At the start of each tag the corresponding field will # be set to 1 and at the end tag the corresponding field will # be set to 0. Then when handle_data is called the options # for that data will be aparent by the current settings. #---------------------------------------------------------- def __getattr__( self, attrName ): """This way we can handle the same way as (ignoring case).""" if attrName!=attrName.lower() and attrName!="caseSensitive" and not self.caseSensitive and \ (attrName.startswith("start_") or attrName.startswith("end_")): return getattr(self,attrName.lower()) raise AttributeError(attrName) #### bold def start_b( self, attributes ): self._push('b',bold=1) def end_b( self ): self._pop('b') def start_strong( self, attributes ): self._push('strong',bold=1) def end_strong( self ): self._pop('strong') #### italics def start_i( self, attributes ): self._push('i',italic=1) def end_i( self ): self._pop('i') def start_em( self, attributes ): self._push('em', italic=1) def end_em( self ): self._pop('em') #### underline def start_u( self, attributes ): self._push('u',underline=1) def end_u( self ): self._pop('u') #### strike def start_strike( self, attributes ): self._push('strike',strike=1) def end_strike( self ): self._pop('strike') #### link def start_link(self, attributes): self._push('link',**self.getAttributes(attributes,_linkAttrMap)) def end_link(self): if self._pop('link').link is None: raise ValueError(' has no target or href') #### anchor def start_a(self, attributes): A = self.getAttributes(attributes,_anchorAttrMap) name = A.get('name',None) if name is not None: name = name.strip() if not name: self._syntax_error(' anchor variant requires non-blank name') if len(A)>1: self._syntax_error(' anchor variant only allows name attribute') A = dict(name=A['name']) A['_selfClosingTag'] = 'anchor' else: href = A.get('href','').strip() A['link'] = href #convert to our link form A.pop('href',None) self._push('a',**A) def end_a(self): frag = self._stack[-1] sct = getattr(frag,'_selfClosingTag','') if sct: if not (sct=='anchor' and frag.name): raise ValueError('Parser failure in ') defn = frag.cbDefn = ABag() defn.label = defn.kind = 'anchor' defn.name = frag.name del frag.name, frag._selfClosingTag self.handle_data('') self._pop('a') else: if self._pop('a').link is None: raise ValueError(' has no href') def start_img(self,attributes): A = self.getAttributes(attributes,_imgAttrMap) if not A.get('src'): self._syntax_error(' needs src attribute') A['_selfClosingTag'] = 'img' self._push('img',**A) def end_img(self): frag = self._stack[-1] if not getattr(frag,'_selfClosingTag',''): raise ValueError('Parser failure in ') defn = frag.cbDefn = ABag() defn.kind = 'img' defn.src = getattr(frag,'src',None) defn.image = ImageReader(defn.src) size = defn.image.getSize() defn.width = getattr(frag,'width',size[0]) defn.height = getattr(frag,'height',size[1]) defn.valign = getattr(frag,'valign','bottom') del frag._selfClosingTag self.handle_data('') self._pop('img') #### super script def start_super( self, attributes ): A = self.getAttributes(attributes,_supAttrMap) A['sup']=1 self._push('super',**A) def end_super( self ): self._pop('super') def start_sup( self, attributes ): A = self.getAttributes(attributes,_supAttrMap) A['sup']=1 self._push('sup',**A) def end_sup( self ): self._pop('sup') #### sub script def start_sub( self, attributes ): A = self.getAttributes(attributes,_supAttrMap) A['sub']=1 self._push('sub',**A) def end_sub( self ): self._pop('sub') #### greek script #### add symbol encoding def handle_charref(self, name): try: if name[0]=='x': n = int(name[1:],16) else: n = int(name) except ValueError: self.unknown_charref(name) return self.handle_data(uniChr(n)) #.encode('utf8')) def syntax_error(self,lineno,message): self._syntax_error(message) def _syntax_error(self,message): if message[:10]=="attribute " and message[-17:]==" value not quoted": return self.errors.append(message) def start_greek(self, attr): self._push('greek',greek=1) def end_greek(self): self._pop('greek') def start_unichar(self, attr): if 'name' in attr: if 'code' in attr: self._syntax_error(' invalid with both name and code attributes') try: v = unicodedata.lookup(attr['name']) except KeyError: self._syntax_error(' invalid name attribute\n"%s"' % ascii(attr['name'])) v = '\0' elif 'code' in attr: try: v = int(eval(attr['code'])) v = chr(v) if isPy3 else unichr(v) except: self._syntax_error(' invalid code attribute %s' % ascii(attr['code'])) v = '\0' else: v = None if attr: self._syntax_error(' invalid attribute %s' % list(attr.keys())[0]) if v is not None: self.handle_data(v) self._push('unichar',_selfClosingTag='unichar') def end_unichar(self): self._pop('unichar') def start_font(self,attr): A = self.getAttributes(attr,_spanAttrMap) if 'fontName' in A: A['fontName'], A['bold'], A['italic'] = ps2tt(A['fontName']) self._push('font',**A) def end_font(self): self._pop('font') def start_span(self,attr): A = self.getAttributes(attr,_spanAttrMap) if 'style' in A: style = self.findSpanStyle(A.pop('style')) D = {} for k in 'fontName fontSize textColor backColor'.split(): v = getattr(style,k,self) if v is self: continue D[k] = v D.update(A) A = D if 'fontName' in A: A['fontName'], A['bold'], A['italic'] = ps2tt(A['fontName']) self._push('span',**A) def end_span(self): self._pop('span') def start_br(self, attr): self._push('br',_selfClosingTag='br',lineBreak=True,text='') def end_br(self): #print('\nend_br called, %d frags in list' % len(self.fragList)) frag = self._stack[-1] if not (frag._selfClosingTag=='br' and frag.lineBreak): raise ValueError('Parser failure in
    ') del frag._selfClosingTag self.handle_data('') self._pop('br') def _initial_frag(self,attr,attrMap,bullet=0): style = self._style if attr!={}: style = copy.deepcopy(style) _applyAttributes(style,self.getAttributes(attr,attrMap)) self._style = style # initialize semantic values frag = ParaFrag() frag.sub = 0 frag.sup = 0 frag.rise = 0 frag.underline = 0 frag.strike = 0 frag.greek = 0 frag.link = None if bullet: frag.fontName, frag.bold, frag.italic = ps2tt(style.bulletFontName) frag.fontSize = style.bulletFontSize frag.textColor = hasattr(style,'bulletColor') and style.bulletColor or style.textColor else: frag.fontName, frag.bold, frag.italic = ps2tt(style.fontName) frag.fontSize = style.fontSize frag.textColor = style.textColor return frag def start_para(self,attr): frag = self._initial_frag(attr,_paraAttrMap) frag.__tag__ = 'para' self._stack = [frag] def end_para(self): self._pop('para') def start_bullet(self,attr): if hasattr(self,'bFragList'): self._syntax_error('only one tag allowed') self.bFragList = [] frag = self._initial_frag(attr,_bulletAttrMap,1) frag.isBullet = 1 frag.__tag__ = 'bullet' self._stack.append(frag) def end_bullet(self): self._pop('bullet') #--------------------------------------------------------------- def start_seqdefault(self, attr): try: default = attr['id'] except KeyError: default = None self._seq.setDefaultCounter(default) def end_seqdefault(self): pass def start_seqreset(self, attr): try: id = attr['id'] except KeyError: id = None try: base = int(attr['base']) except: base=0 self._seq.reset(id, base) def end_seqreset(self): pass def start_seqchain(self, attr): try: order = attr['order'] except KeyError: order = '' order = order.split() seq = self._seq for p,c in zip(order[:-1],order[1:]): seq.chain(p, c) end_seqchain = end_seqreset def start_seqformat(self, attr): try: id = attr['id'] except KeyError: id = None try: value = attr['value'] except KeyError: value = '1' self._seq.setFormat(id,value) end_seqformat = end_seqreset # AR hacking in aliases to allow the proper casing for RML. # the above ones should be deprecated over time. 2001-03-22 start_seqDefault = start_seqdefault end_seqDefault = end_seqdefault start_seqReset = start_seqreset end_seqReset = end_seqreset start_seqChain = start_seqchain end_seqChain = end_seqchain start_seqFormat = start_seqformat end_seqFormat = end_seqformat def start_seq(self, attr): #if it has a template, use that; otherwise try for id; #otherwise take default sequence if 'template' in attr: templ = attr['template'] self.handle_data(templ % self._seq) return elif 'id' in attr: id = attr['id'] else: id = None increment = attr.get('inc', None) if not increment: output = self._seq.nextf(id) else: #accepts "no" for do not increment, or an integer. #thus, 0 and 1 increment by the right amounts. if increment.lower() == 'no': output = self._seq.thisf(id) else: incr = int(increment) output = self._seq.thisf(id) self._seq.reset(id, self._seq._this() + incr) self.handle_data(output) def end_seq(self): pass def start_ondraw(self,attr): defn = ABag() if 'name' in attr: defn.name = attr['name'] else: self._syntax_error(' needs at least a name attribute') if 'label' in attr: defn.label = attr['label'] defn.kind='onDraw' self._push('ondraw',cbDefn=defn) self.handle_data('') self._pop('ondraw') start_onDraw=start_ondraw end_onDraw=end_ondraw=end_seq def start_index(self,attr): attr=self.getAttributes(attr,_indexAttrMap) defn = ABag() if 'item' in attr: label = attr['item'] else: self._syntax_error(' needs at least an item attribute') if 'name' in attr: name = attr['name'] else: name = DEFAULT_INDEX_NAME format = attr.get('format',None) if format is not None and format not in ('123','I','i','ABC','abc'): raise ValueError('index tag format is %r not valid 123 I i ABC or abc' % offset) offset = attr.get('offset',None) if offset is not None: try: offset = int(offset) except: raise ValueError('index tag offset is %r not an int' % offset) defn.label = encode_label((label,format,offset)) defn.name = name defn.kind='index' self._push('index',cbDefn=defn) self.handle_data('') self._pop('index',) end_index=end_seq def start_unknown(self,attr): pass end_unknown=end_seq #--------------------------------------------------------------- def _push(self,tag,**attr): frag = copy.copy(self._stack[-1]) frag.__tag__ = tag _applyAttributes(frag,attr) self._stack.append(frag) def _pop(self,tag): frag = self._stack.pop() if tag==frag.__tag__: return frag raise ValueError('Parse error: saw instead of expected ' % (tag,frag.__tag__)) def getAttributes(self,attr,attrMap): A = {} for k, v in attr.items(): if not self.caseSensitive: k = k.lower() if k in attrMap: j = attrMap[k] func = j[1] try: A[j[0]] = v if func is None else func(v) except: self._syntax_error('%s: invalid value %s'%(k,v)) else: raise ValueError('invalid attribute name %s attrMap=%r'% (k,list(sorted(attrMap.keys())))) self._syntax_error('invalid attribute name %s'%k) return A #---------------------------------------------------------------- def __init__(self,verbose=0, caseSensitive=0, ignoreUnknownTags=1): HTMLParser.__init__(self, **(dict(convert_charrefs=False) if sys.version_info>=(3,4) else {})) self.verbose = verbose #HTMLParser is case insenstive anyway, but the rml interface still needs this #all start/end_ methods should have a lower case version for HMTMParser self.caseSensitive = caseSensitive self.ignoreUnknownTags = ignoreUnknownTags def _iReset(self): self.fragList = [] if hasattr(self, 'bFragList'): delattr(self,'bFragList') def _reset(self, style): '''reset the parser''' HTMLParser.reset(self) # initialize list of string segments to empty self.errors = [] self._style = style self._iReset() #---------------------------------------------------------------- def handle_data(self,data): "Creates an intermediate representation of string segments." #The old parser would only 'see' a string after all entities had #been processed. Thus, 'Hello ™ World' would emerge as one #fragment. HTMLParser processes these separately. We want to ensure #that successive calls like this are concatenated, to prevent too many #fragments being created. frag = copy.copy(self._stack[-1]) if hasattr(frag,'cbDefn'): kind = frag.cbDefn.kind if data: self._syntax_error('Only empty <%s> tag allowed' % kind) elif hasattr(frag,'_selfClosingTag'): if data!='': self._syntax_error('No content allowed in %s tag' % frag._selfClosingTag) return else: # if sub and sup are both on they will cancel each other out if frag.sub == 1 and frag.sup == 1: frag.sub = 0 frag.sup = 0 if frag.sub: frag.rise = -getattr(frag,'supr',frag.fontSize*subFraction) frag.fontSize = getattr(frag,'sups',frag.fontSize-min(sizeDelta,0.2*frag.fontSize)) elif frag.sup: frag.rise = getattr(frag,'supr',frag.fontSize*supFraction) frag.fontSize = getattr(frag,'sups',frag.fontSize-min(sizeDelta,0.2*frag.fontSize)) if frag.greek: frag.fontName = 'symbol' data = _greekConvert(data) # bold, italic, and underline frag.fontName = tt2ps(frag.fontName,frag.bold,frag.italic) #save our data frag.text = data if hasattr(frag,'isBullet'): delattr(frag,'isBullet') self.bFragList.append(frag) else: self.fragList.append(frag) def handle_cdata(self,data): self.handle_data(data) def _setup_for_parse(self,style): self._seq = reportlab.lib.sequencer.getSequencer() self._reset(style) # reinitialise the parser def _complete_parse(self): "Reset after parsing, to be ready for next paragraph" del self._seq style = self._style del self._style if len(self.errors)==0: fragList = self.fragList bFragList = hasattr(self,'bFragList') and self.bFragList or None self._iReset() else: fragList = bFragList = None return style, fragList, bFragList def _tt_handle(self,tt): "Iterate through a pre-parsed tuple tree (e.g. from pyRXP)" #import pprint #pprint.pprint(tt) #find the corresponding start_tagname and end_tagname methods. #These must be defined. tag = tt[0] try: start = getattr(self,'start_'+tag) end = getattr(self,'end_'+tag) except AttributeError: if not self.ignoreUnknownTags: raise ValueError('Invalid tag "%s"' % tag) start = self.start_unknown end = self.end_unknown #call the start_tagname method start(tt[1] or {}) #if tree node has any children, they will either be further nodes, #or text. Accordingly, call either this function, or handle_data. C = tt[2] if C: M = self._tt_handlers for c in C: M[isinstance(c,(list,tuple))](c) #call the end_tagname method end() def _tt_start(self,tt): self._tt_handlers = self.handle_data,self._tt_handle self._tt_handle(tt) def tt_parse(self,tt,style): '''parse from tupletree form''' self._setup_for_parse(style) self._tt_start(tt) return self._complete_parse() def findSpanStyle(self,style): raise ValueError('findSpanStyle not implemented in this parser') #HTMLParser interface def parse(self, text, style): "attempt replacement for parse" self._setup_for_parse(style) text = asUnicode(text) if not(len(text)>=6 and text[0]=='<' and _re_para.match(text)): text = u""+text+u"" try: self.feed(text) except: annotateException('\nparagraph text %s caused exception' % ascii(text)) return self._complete_parse() def handle_starttag(self, tag, attrs): "Called by HTMLParser when a tag starts" #tuple tree parser used to expect a dict. HTML parser #gives list of two-element tuples if isinstance(attrs, list): d = {} for (k, v) in attrs: d[k] = v attrs = d if not self.caseSensitive: tag = tag.lower() try: start = getattr(self,'start_'+tag) except AttributeError: if not self.ignoreUnknownTags: raise ValueError('Invalid tag "%s"' % tag) start = self.start_unknown #call it start(attrs or {}) def handle_endtag(self, tag): "Called by HTMLParser when a tag ends" #find the existing end_tagname method if not self.caseSensitive: tag = tag.lower() try: end = getattr(self,'end_'+tag) except AttributeError: if not self.ignoreUnknownTags: raise ValueError('Invalid tag "%s"' % tag) end = self.end_unknown #call it end() def handle_entityref(self, name): "Handles a named entity. " try: v = known_entities[name] except: v = u'&%s;' % name self.handle_data(v) if __name__=='__main__': from reportlab.platypus import cleanBlockQuotedText from reportlab.lib.styles import _baseFontName _parser=ParaParser() def check_text(text,p=_parser): print('##########') text = cleanBlockQuotedText(text) l,rv,bv = p.parse(text,style) if rv is None: for l in _parser.errors: print(l) else: print('ParaStyle', l.fontName,l.fontSize,l.textColor) for l in rv: sys.stdout.write(l.fontName,l.fontSize,l.textColor,l.bold, l.rise, '|%s|'%l.text[:25]) if hasattr(l,'cbDefn'): print('cbDefn',getattr(l.cbDefn,'name',''),getattr(l.cbDefn,'label',''),l.cbDefn.kind) else: print() style=ParaFrag() style.fontName=_baseFontName style.fontSize = 12 style.textColor = black style.bulletFontName = black style.bulletFontName=_baseFontName style.bulletFontSize=12 text=''' aDβ Tell me, O muse, of that ingenious hero who travelled far and wide after he had sacked the famous town of Troy. Many cities did he visit, and many were the nations with whose manners and customs he was acquainted; moreover he suffered much by sea while trying to save his own life and bring his men safely home; but do what he might he could not save his men, for they perished through their own sheer folly in eating the cattle of the Sun-god Hyperion; so the god prevented them from ever reaching home. Tell me, too, about all these things, O daughter of Jove, from whatsoever source you1 may know them. ''' check_text(text) check_text(' ') check_text('ReportLab -- Reporting for the Internet Age'%_baseFontName) check_text(''' τTell me, O muse, of that ingenious hero who travelled far and wide after he had sacked the famous town of Troy. Many cities did he visit, and many were the nations with whose manners and customs he was acquainted; moreover he suffered much by sea while trying to save his own life and bring his men safely home; but do what he might he could not save his men, for they perished through their own sheer folly in eating the cattle of the Sun-god Hyperion; so the god prevented them from ever reaching home. Tell me, too, about all these things, O daughter of Jove, from whatsoever source you may know them.''') check_text(''' Telemachus took this speech as of good omen and rose at once, for he was bursting with what he had to say. He stood in the middle of the assembly and the good herald Pisenor brought him his staff. Then, turning to Aegyptius, "Sir," said he, "it is I, as you will shortly learn, who have convened you, for it is I who am the most aggrieved. I have not got wind of any host approaching about which I would warn you, nor is there any matter of public moment on which I would speak. My grieveance is purely personal, and turns on two great misfortunes which have fallen upon my house. The first of these is the loss of my excellent father, who was chief among all you here present, and was like a father to every one of you; the second is much more serious, and ere long will be the utter ruin of my estate. The sons of all the chief men among you are pestering my mother to marry them against her will. They are afraid to go to her father Icarius, asking him to choose the one he likes best, and to provide marriage gifts for his daughter, but day by day they keep hanging about my father's house, sacrificing our oxen, sheep, and fat goats for their banquets, and never giving so much as a thought to the quantity of wine they drink. No estate can stand such recklessness; we have now no Ulysses to ward off harm from our doors, and I cannot hold my own against them. I shall never all my days be as good a man as he was, still I would indeed defend myself if I had power to do so, for I cannot stand such treatment any longer; my house is being disgraced and ruined. Have respect, therefore, to your own consciences and to public opinion. Fear, too, the wrath of heaven, lest the gods should be displeased and turn upon you. I pray you by Jove and Themis, who is the beginning and the end of councils, [do not] hold back, my friends, and leave me singlehanded- unless it be that my brave father Ulysses did some wrong to the Achaeans which you would now avenge on me, by aiding and abetting these suitors. Moreover, if I am to be eaten out of house and home at all, I had rather you did the eating yourselves, for I could then take action against you to some purpose, and serve you with notices from house to house till I got paid in full, whereas now I have no remedy."''') check_text(''' But as the sun was rising from the fair sea into the firmament of heaven to shed light on mortals and immortals, they reached Pylos the city of Neleus. Now the people of Pylos were gathered on the sea shore to offer sacrifice of black bulls to Neptune lord of the Earthquake. There were nine guilds with five hundred men in each, and there were nine bulls to each guild. As they were eating the inward meats and burning the thigh bones [on the embers] in the name of Neptune, Telemachus and his crew arrived, furled their sails, brought their ship to anchor, and went ashore. ''') check_text(''' So the neighbours and kinsmen of Menelaus were feasting and making merry in his house. There was a bard also to sing to them and play his lyre, while two tumblers went about performing in the midst of them when the man struck up with his tune.]''') check_text(''' "When we had passed the [Wandering] rocks, with Scylla and terrible Charybdis, we reached the noble island of the sun-god, where were the goodly cattle and sheep belonging to the sun Hyperion. While still at sea in my ship I could bear the cattle lowing as they came home to the yards, and the sheep bleating. Then I remembered what the blind Theban prophet Teiresias had told me, and how carefully Aeaean Circe had warned me to shun the island of the blessed sun-god. So being much troubled I said to the men, 'My men, I know you are hard pressed, but listen while I tell you the prophecy that Teiresias made me, and how carefully Aeaean Circe warned me to shun the island of the blessed sun-god, for it was here, she said, that our worst danger would lie. Head the ship, therefore, away from the island.''') check_text('''A<B>C&D"E'F''') check_text('''A< B> C& D" E' F''') check_text('''&'"]]>''') check_text('''+ There was a bard also to sing to them and play his lyre, while two tumblers went about performing in the midst of them when the man struck up with his tune.]''') check_text('''A paragraph''') check_text('''B paragraph''') # HVB, 30.05.2003: Test for new features _parser.caseSensitive=0 check_text('''Here comes Helvetica 14 with strong emphasis.''') check_text('''Here comes Helvetica 14 with strong emphasis.''') check_text('''Here comes Courier 3cm and normal again.''') check_text('''Before the break
    the middle line
    and the last line.''') check_text('''This should be an inline image !''') check_text('''aaa bbbb underline cccc''') reportlab-3.3.0/src/reportlab/platypus/tables.py0000775000175000017500000020520512661063724021347 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/tables.py __version__='3.3.0' __doc__=""" Tables are created by passing the constructor a tuple of column widths, a tuple of row heights and the data in row order. Drawing of the table can be controlled by using a TableStyle instance. This allows control of the color and weight of the lines (if any), and the font, alignment and padding of the text. None values in the sequence of row heights or column widths, mean that the corresponding rows or columns should be automatically sized. All the cell values should be convertible to strings; embedded newline '\\n' characters cause the value to wrap (ie are like a traditional linefeed). See the test output from running this module as a script for a discussion of the method for constructing tables and table styles. """ from reportlab.platypus.flowables import Flowable, Preformatted, Spacer from reportlab import rl_config from reportlab.lib.styles import PropertySet, ParagraphStyle, _baseFontName from reportlab.lib import colors from reportlab.lib.utils import annotateException, IdentStr, flatten, isStr, asNative, strTypes from reportlab.lib.rl_accel import fp_str from reportlab.lib.abag import ABag as CellFrame from reportlab.pdfbase.pdfmetrics import stringWidth from reportlab.platypus.doctemplate import Indenter from reportlab.platypus.flowables import LIIndenter LINECAPS={None: None, 'butt':0,'round':1,'projecting':2,'squared':2} LINEJOINS={None: None, 'miter':0, 'mitre':0, 'round':1,'bevel':2} class CellStyle(PropertySet): fontname = _baseFontName fontsize = 10 leading = 12 leftPadding = 6 rightPadding = 6 topPadding = 3 bottomPadding = 3 firstLineIndent = 0 color = 'black' alignment = 'LEFT' background = 'white' valign = "BOTTOM" href = None destination = None def __init__(self, name, parent=None): self.name = name if parent is not None: parent.copy(self) def copy(self, result=None): if result is None: result = CellStyle() for name in dir(self): setattr(result, name, getattr(self, name)) return result class TableStyle: def __init__(self, cmds=None, parent=None, **kw): #handle inheritance from parent first. commands = [] if parent: # copy the parents list at construction time commands = commands + parent.getCommands() self._opts = parent._opts for a in ('spaceBefore','spaceAfter'): if hasattr(parent,a): setattr(self,a,getattr(parent,a)) if cmds: commands = commands + list(cmds) self._cmds = commands self._opts={} self._opts.update(kw) def add(self, *cmd): self._cmds.append(cmd) def __repr__(self): return "TableStyle(\n%s\n) # end TableStyle" % " \n".join(map(repr, self._cmds)) def getCommands(self): return self._cmds def _rowLen(x): return not isinstance(x,(tuple,list)) and 1 or len(x) def _calc_pc(V,avail): '''check list V for percentage or * values 1) absolute values go through unchanged 2) percentages are used as weights for unconsumed space 3) if no None values were seen '*' weights are set equally with unclaimed space otherwise * weights are assigned as None''' R = [] r = R.append I = [] i = I.append J = [] j = J.append s = avail w = n = 0. for v in V: if isinstance(v,strTypes): v = str(v).strip() if not v: v = None n += 1 elif v.endswith('%'): v = float(v[:-1]) w += v i(len(R)) elif v=='*': j(len(R)) else: v = float(v) s -= v elif v is None: n += 1 else: s -= v r(v) s = max(0.,s) f = s/max(100.,w) for i in I: R[i] *= f s -= R[i] s = max(0.,s) m = len(J) if m: v = n==0 and s/m or None for j in J: R[j] = v return R def _hLine(canvLine, scp, ecp, y, hBlocks, FUZZ=rl_config._FUZZ): ''' Draw horizontal lines; do not draw through regions specified in hBlocks This also serves for vertical lines with a suitable canvLine ''' if hBlocks: hBlocks = hBlocks.get(y,None) if not hBlocks or scp>=hBlocks[-1][1]-FUZZ or ecp<=hBlocks[0][0]+FUZZ: canvLine(scp,y,ecp,y) else: i = 0 n = len(hBlocks) while scp=ecp-FUZZ: i += 1 continue i0 = max(scp,x0) i1 = min(ecp,x1) if i0>scp: canvLine(scp,y,i0,y) scp = i1 if scp=lim: continue x1 += 1 t = sum([V[x]+M.get(x,0) for x in xrange(x0,x1)]) if t>=v-FUZZ: continue #already good enough X = [x for x in xrange(x0,x1) if V0[x] is None] #variable candidates if not X: continue #something wrong here mate v -= t v /= float(len(X)) for x in X: M[x] = M.get(x,0)+v for x,v in M.items(): V[x] += v class _ExpandedCellTuple(tuple): pass class Table(Flowable): def __init__(self, data, colWidths=None, rowHeights=None, style=None, repeatRows=0, repeatCols=0, splitByRow=1, emptyTableAction=None, ident=None, hAlign=None,vAlign=None, normalizedData=0, cellStyles=None, rowSplitRange=None, spaceBefore=None,spaceAfter=None): self.ident = ident self.hAlign = hAlign or 'CENTER' self.vAlign = vAlign or 'MIDDLE' if not isinstance(data,(tuple,list)): raise ValueError("%s invalid data type" % self.identity()) self._nrows = nrows = len(data) self._cellvalues = [] _seqCW = isinstance(colWidths,(tuple,list)) _seqRH = isinstance(rowHeights,(tuple,list)) if nrows: self._ncols = ncols = max(list(map(_rowLen,data))) elif colWidths and _seqCW: ncols = len(colWidths) else: ncols = 0 if not emptyTableAction: emptyTableAction = rl_config.emptyTableAction self._longTableOptimize = getattr(self,'_longTableOptimize',rl_config.longTableOptimize) if not (nrows and ncols): if emptyTableAction=='error': raise ValueError("%s must have at least a row and column" % self.identity()) elif emptyTableAction=='indicate': self.__class__ = Preformatted global _emptyTableStyle if '_emptyTableStyle' not in list(globals().keys()): _emptyTableStyle = ParagraphStyle('_emptyTableStyle') _emptyTableStyle.textColor = colors.red _emptyTableStyle.backColor = colors.yellow Preformatted.__init__(self,'%s(%d,%d)' % (self.__class__.__name__,nrows,ncols), _emptyTableStyle) elif emptyTableAction=='ignore': self.__class__ = Spacer Spacer.__init__(self,0,0) else: raise ValueError('%s bad emptyTableAction: "%s"' % (self.identity(),emptyTableAction)) return # we need a cleanup pass to ensure data is strings - non-unicode and non-null if normalizedData: self._cellvalues = data else: self._cellvalues = data = self.normalizeData(data) if not _seqCW: colWidths = ncols*[colWidths] elif len(colWidths)!=ncols: if rl_config.allowShortTableRows and isinstance(colWidths,list): n = len(colWidths) if n%s" % (self.__class__.__name__, id(self), nr, nc, tallest, vx) def _cellListIter(self,C,aW,aH): canv = getattr(self,'canv',None) for c in C: if getattr(c,'__split_only__',None): for d in c.splitOn(canv,aW,aH): yield d else: yield c def _cellListProcess(self,C,aW,aH): if not isinstance(C,_ExpandedCellTuple): frame = None R = [].append for c in self._cellListIter(C,aW,aH): if isinstance(c,Indenter): if not frame: frame = CellFrame(_leftExtraIndent=0,_rightExtraIndent=0) c.frameAction(frame) if frame._leftExtraIndent<1e-8 and frame._rightExtraIndent<1e-8: frame = None continue if frame: R(LIIndenter(c,leftIndent=frame._leftExtraIndent,rightIndent=frame._rightExtraIndent)) else: R(c) C = _ExpandedCellTuple(R.__self__) return C def _listCellGeom(self, V,w,s,W=None,H=None,aH=72000): if not V: return 0,0 aW = w - s.leftPadding - s.rightPadding aH = aH - s.topPadding - s.bottomPadding t = 0 w = 0 canv = getattr(self,'canv',None) sb0 = None for v in V: vw, vh = v.wrapOn(canv, aW, aH) sb = v.getSpaceBefore() sa = v.getSpaceAfter() if W is not None: W.append(vw) if H is not None: H.append(vh) w = max(w,vw) t += vh + sa + sb if sb0 is None: sb0 = sb return w, t - sb0 - sa def _listValueWidth(self,V,aH=72000,aW=72000): if not V: return 0,0 t = 0 w = 0 canv = getattr(self,'canv',None) return max([v.wrapOn(canv,aW,aH)[0] for v in V]) def _calc_width(self,availWidth,W=None): if getattr(self,'_width_calculated_once',None): return #comments added by Andy to Robin's slightly terse variable names if not W: W = _calc_pc(self._argW,availWidth) #widths array if None in W: #some column widths are not given canv = getattr(self,'canv',None) saved = None if self._spanCmds: colSpanCells = self._colSpanCells spanRanges = self._spanRanges else: colSpanCells = () spanRanges = {} spanCons = {} if W is self._argW: W0 = W W = W[:] else: W0 = W[:] V = self._cellvalues S = self._cellStyles while None in W: j = W.index(None) #find first unspecified column w = 0 for i,Vi in enumerate(V): v = Vi[j] s = S[i][j] ji = j,i span = spanRanges.get(ji,None) if ji in colSpanCells and not span: #if the current cell is part of a spanned region, t = 0.0 #assume a zero size. else:#work out size t = self._elementWidth(v,s) if t is None: raise ValueError("Flowable %s in cell(%d,%d) can't have auto width\n%s" % (v.identity(30),i,j,self.identity(30))) t += s.leftPadding+s.rightPadding if span: c0 = span[0] c1 = span[2] if c0!=c1: x = c0,c1 spanCons[x] = max(spanCons.get(x,t),t) t = 0 if t>w: w = t #record a new maximum W[j] = w if spanCons: try: spanFixDim(W0,W,spanCons) except: annotateException('\nspanning problem in %s\nW0=%r W=%r\nspanCons=%r' % (self.identity(),W0,W,spanCons)) self._colWidths = W width = 0 self._colpositions = [0] #index -1 is right side boundary; we skip when processing cells for w in W: width = width + w self._colpositions.append(width) self._width = width self._width_calculated_once = 1 def _elementWidth(self,v,s): if isinstance(v,(list,tuple)): w = 0 for e in v: ew = self._elementWidth(e,s) if ew is None: return None w = max(w,ew) return w elif isinstance(v,Flowable) and v._fixedWidth: if hasattr(v, 'width') and isinstance(v.width,(int,float)): return v.width if hasattr(v, 'drawWidth') and isinstance(v.drawWidth,(int,float)): return v.drawWidth # Even if something is fixedWidth, the attribute to check is not # necessarily consistent (cf. Image.drawWidth). Therefore, we'll # be extra-careful and fall through to this code if necessary. if hasattr(v, 'minWidth'): try: w = v.minWidth() # should be all flowables if isinstance(w,(float,int)): return w except AttributeError: pass if v is None: return 0 else: try: v = str(v).split("\n") except: return 0 fontName = s.fontname fontSize = s.fontsize return max([stringWidth(x,fontName,fontSize) for x in v]) def _calc_height(self, availHeight, availWidth, H=None, W=None): H = self._argH if not W: W = _calc_pc(self._argW,availWidth) #widths array hmax = lim = len(H) longTable = self._longTableOptimize if None in H: canv = getattr(self,'canv',None) saved = None #get a handy list of any cells which span rows. should be ignored for sizing if self._spanCmds: rowSpanCells = self._rowSpanCells colSpanCells = self._colSpanCells spanRanges = self._spanRanges colpositions = self._colpositions else: rowSpanCells = colSpanCells = () spanRanges = {} if canv: saved = canv._fontname, canv._fontsize, canv._leading H0 = H H = H[:] #make a copy as we'll change it self._rowHeights = H spanCons = {} FUZZ = rl_config._FUZZ while None in H: i = H.index(None) V = self._cellvalues[i] # values for row i S = self._cellStyles[i] # styles for row i h = 0 j = 0 for j,(v, s, w) in enumerate(list(zip(V, S, W))): # value, style, width (lengths must match) ji = j,i span = spanRanges.get(ji,None) if ji in rowSpanCells and not span: continue # don't count it, it's either occluded or unreliable else: if isinstance(v,(tuple,list,Flowable)): if isinstance(v,Flowable): v = (v,) else: v = flatten(v) v = V[j] = self._cellListProcess(v,w,None) if w is None and not self._canGetWidth(v): raise ValueError("Flowable %s in cell(%d,%d) can't have auto width in\n%s" % (v[0].identity(30),i,j,self.identity(30))) if canv: canv._fontname, canv._fontsize, canv._leading = s.fontname, s.fontsize, s.leading or 1.2*s.fontsize if ji in colSpanCells: if not span: continue w = max(colpositions[span[2]+1]-colpositions[span[0]],w) dW,t = self._listCellGeom(v,w or self._listValueWidth(v),s) if canv: canv._fontname, canv._fontsize, canv._leading = saved dW = dW + s.leftPadding + s.rightPadding if not rl_config.allowTableBoundsErrors and dW>w: from reportlab.platypus.doctemplate import LayoutError raise LayoutError("Flowable %s (%sx%s points) too wide for cell(%d,%d) (%sx* points) in\n%s" % (v[0].identity(30),fp_str(dW),fp_str(t),i,j, fp_str(w), self.identity(30))) else: v = (v is not None and str(v) or '').split("\n") t = (s.leading or 1.2*s.fontsize)*len(v) t += s.bottomPadding+s.topPadding if span: r0 = span[1] r1 = span[3] if r0!=r1: x = r0,r1 spanCons[x] = max(spanCons.get(x,t),t) t = 0 if t>h: h = t #record a new maximum H[i] = h # we can stop if we have filled up all available room if longTable: hmax = i height = sum(H[:i]) if height > availHeight: #we can terminate if all spans are complete in H[:i] if spanCons: msr = max([x[1] for x in spanCons.keys()]) #RS=[endrowspan,.....] if hmax>=msr: break if None not in H: hmax = lim if spanCons: try: spanFixDim(H0,H,spanCons,lim=hmax) except: annotateException('\nspanning problem in %s hmax=%s lim=%s avail=%s x %s\nH0=%r H=%r\nspanCons=%r' % (self.identity(),hmax,lim,availWidth,availHeight,H0,H,spanCons)) height = self._height = sum(H[:hmax]) self._rowpositions = [height] # index 0 is actually topline; we skip when processing cells for h in H[:hmax]: height -= h self._rowpositions.append(height) assert abs(height)<1e-8, '!!!!!%s\ninternal height error height=%r hmax=%d Sum(H[:%d])=%r\nH=%r\nrowPositions=%r' % (self.identity(),height,hmax,hmax,self._height,H[:hmax],self._rowpositions) self._hmax = hmax def _calc(self, availWidth, availHeight): #if hasattr(self,'_width'): return #in some cases there are unsizable things in #cells. If so, apply a different algorithm #and assign some withs in a less (thanks to Gary Poster) dumb way. #this CHANGES the widths array. if (None in self._colWidths or '*' in self._colWidths) and self._hasVariWidthElements(): W = self._calcPreliminaryWidths(availWidth) #widths else: W = None # need to know which cells are part of spanned # ranges, so _calc_height and _calc_width can ignore them # in sizing if self._spanCmds: self._calcSpanRanges() if None in self._argH: self._calc_width(availWidth,W=W) if self._nosplitCmds: self._calcNoSplitRanges() # calculate the full table height self._calc_height(availHeight,availWidth,W=W) # calculate the full table width self._calc_width(availWidth,W=W) if self._spanCmds: #now work out the actual rect for each spanned cell from the underlying grid self._calcSpanRects() def _culprit(self): """Return a string describing the tallest element. Usually this is what causes tables to fail to split. Currently tables are the only items to have a '_culprit' method. Doctemplate checks for it. """ rh = self._rowHeights tallest = max(rh) rowNum = rh.index(tallest) #rowNum of limited interest as usually it's a split one #and we see row #1. Text might be a nice addition. return 'tallest cell %0.1f points' % tallest def _hasVariWidthElements(self, upToRow=None): """Check for flowables in table cells and warn up front. Allow a couple which we know are fixed size such as images and graphics.""" if upToRow is None: upToRow = self._nrows for row in xrange(min(self._nrows, upToRow)): for col in xrange(self._ncols): value = self._cellvalues[row][col] if not self._canGetWidth(value): return 1 return 0 def _canGetWidth(self, thing): "Can we work out the width quickly?" if isinstance(thing,(list, tuple)): for elem in thing: if not self._canGetWidth(elem): return 0 return 1 elif isinstance(thing, Flowable): return thing._fixedWidth # must loosen this up else: #str, number, None etc. #anything else gets passed to str(...) # so should be sizable return 1 def _calcPreliminaryWidths(self, availWidth): """Fallback algorithm for when main one fails. Where exact width info not given but things like paragraphs might be present, do a preliminary scan and assign some best-guess values.""" W = list(self._argW) # _calc_pc(self._argW,availWidth) verbose = 0 totalDefined = 0.0 percentDefined = 0 percentTotal = 0 numberUndefined = 0 numberGreedyUndefined = 0 for w in W: if w is None: numberUndefined += 1 elif w == '*': numberUndefined += 1 numberGreedyUndefined += 1 elif _endswith(w,'%'): percentDefined += 1 percentTotal += float(w[:-1]) else: assert isinstance(w,(int,float)) totalDefined = totalDefined + w if verbose: print('prelim width calculation. %d columns, %d undefined width, %0.2f units remain' % ( self._ncols, numberUndefined, availWidth - totalDefined)) #check columnwise in each None column to see if they are sizable. given = [] sizeable = [] unsizeable = [] minimums = {} totalMinimum = 0 elementWidth = self._elementWidth for colNo in xrange(self._ncols): w = W[colNo] if w is None or w=='*' or _endswith(w,'%'): siz = 1 final = 0 for rowNo in xrange(self._nrows): value = self._cellvalues[rowNo][colNo] style = self._cellStyles[rowNo][colNo] pad = style.leftPadding+style.rightPadding new = elementWidth(value,style) if new: new += pad else: new = pad new += style.leftPadding+style.rightPadding final = max(final, new) siz = siz and self._canGetWidth(value) # irrelevant now? if siz: sizeable.append(colNo) else: unsizeable.append(colNo) minimums[colNo] = final totalMinimum += final else: given.append(colNo) if len(given) == self._ncols: return if verbose: print('predefined width: ',given) if verbose: print('uncomputable width: ',unsizeable) if verbose: print('computable width: ',sizeable) # how much width is left: remaining = availWidth - (totalMinimum + totalDefined) if remaining > 0: # we have some room left; fill it. definedPercentage = (totalDefined/availWidth)*100 percentTotal += definedPercentage if numberUndefined and percentTotal < 100: undefined = numberGreedyUndefined or numberUndefined defaultWeight = (100-percentTotal)/undefined percentTotal = 100 defaultDesired = (defaultWeight/percentTotal)*availWidth else: defaultWeight = defaultDesired = 1 # we now calculate how wide each column wanted to be, and then # proportionately shrink that down to fit the remaining available # space. A column may not shrink less than its minimum width, # however, which makes this a bit more complicated. desiredWidths = [] totalDesired = 0 effectiveRemaining = remaining for colNo, minimum in minimums.items(): w = W[colNo] if _endswith(w,'%'): desired = (float(w[:-1])/percentTotal)*availWidth elif w == '*': desired = defaultDesired else: desired = not numberGreedyUndefined and defaultDesired or 1 if desired <= minimum: W[colNo] = minimum else: desiredWidths.append( (desired-minimum, minimum, desired, colNo)) totalDesired += desired effectiveRemaining += minimum if desiredWidths: # else we're done # let's say we have two variable columns. One wanted # 88 points, and one wanted 264 points. The first has a # minWidth of 66, and the second of 55. We have 71 points # to divide up in addition to the totalMinimum (i.e., # remaining==71). Our algorithm tries to keep the proportion # of these variable columns. # # To do this, we add up the minimum widths of the variable # columns and the remaining width. That's 192. We add up the # totalDesired width. That's 352. That means we'll try to # shrink the widths by a proportion of 192/352--.545454. # That would make the first column 48 points, and the second # 144 points--adding up to the desired 192. # # Unfortunately, that's too small for the first column. It # must be 66 points. Therefore, we go ahead and save that # column width as 88 points. That leaves (192-88==) 104 # points remaining. The proportion to shrink the remaining # column is (104/264), which, multiplied by the desired # width of 264, is 104: the amount assigned to the remaining # column. proportion = effectiveRemaining/totalDesired # we sort the desired widths by difference between desired and # and minimum values, a value called "disappointment" in the # code. This means that the columns with a bigger # disappointment will have a better chance of getting more of # the available space. desiredWidths.sort() finalSet = [] for disappointment, minimum, desired, colNo in desiredWidths: adjusted = proportion * desired if adjusted < minimum: W[colNo] = minimum totalDesired -= desired effectiveRemaining -= minimum if totalDesired: proportion = effectiveRemaining/totalDesired else: finalSet.append((minimum, desired, colNo)) for minimum, desired, colNo in finalSet: adjusted = proportion * desired assert adjusted >= minimum W[colNo] = adjusted else: for colNo, minimum in minimums.items(): W[colNo] = minimum if verbose: print('new widths are:', W) self._argW = self._colWidths = W return W def minWidth(self): W = list(self._argW) width = 0 elementWidth = self._elementWidth rowNos = xrange(self._nrows) values = self._cellvalues styles = self._cellStyles for colNo in xrange(len(W)): w = W[colNo] if w is None or w=='*' or _endswith(w,'%'): final = 0 for rowNo in rowNos: value = values[rowNo][colNo] style = styles[rowNo][colNo] new = (elementWidth(value,style)+ style.leftPadding+style.rightPadding) final = max(final, new) width += final else: width += float(w) return width # XXX + 1/2*(left and right border widths) def _calcSpanRanges(self): """Work out rects for tables which do row and column spanning. This creates some mappings to let the later code determine if a cell is part of a "spanned" range. self._spanRanges shows the 'coords' in integers of each 'cell range', or None if it was clobbered: (col, row) -> (col0, row0, col1, row1) Any cell not in the key is not part of a spanned region """ self._spanRanges = spanRanges = {} for x in xrange(self._ncols): for y in xrange(self._nrows): spanRanges[x,y] = (x, y, x, y) self._colSpanCells = [] self._rowSpanCells = [] csa = self._colSpanCells.append rsa = self._rowSpanCells.append for (cmd, start, stop) in self._spanCmds: x0, y0 = start x1, y1 = stop #normalize if x0 < 0: x0 = x0 + self._ncols if x1 < 0: x1 = x1 + self._ncols if y0 < 0: y0 = y0 + self._nrows if y1 < 0: y1 = y1 + self._nrows if x0 > x1: x0, x1 = x1, x0 if y0 > y1: y0, y1 = y1, y0 if x0!=x1 or y0!=y1: if x0!=x1: #column span for y in xrange(y0, y1+1): for x in xrange(x0,x1+1): csa((x,y)) if y0!=y1: #row span for y in xrange(y0, y1+1): for x in xrange(x0,x1+1): rsa((x,y)) for y in xrange(y0, y1+1): for x in xrange(x0,x1+1): spanRanges[x,y] = None # set the main entry spanRanges[x0,y0] = (x0, y0, x1, y1) def _calcNoSplitRanges(self): """ This creates some mappings to let the later code determine if a cell is part of a "nosplit" range. self._nosplitRanges shows the 'coords' in integers of each 'cell range', or None if it was clobbered: (col, row) -> (col0, row0, col1, row1) Any cell not in the key is not part of a spanned region """ self._nosplitRanges = nosplitRanges = {} for x in xrange(self._ncols): for y in xrange(self._nrows): nosplitRanges[x,y] = (x, y, x, y) self._colNoSplitCells = [] self._rowNoSplitCells = [] csa = self._colNoSplitCells.append rsa = self._rowNoSplitCells.append for (cmd, start, stop) in self._nosplitCmds: x0, y0 = start x1, y1 = stop #normalize if x0 < 0: x0 = x0 + self._ncols if x1 < 0: x1 = x1 + self._ncols if y0 < 0: y0 = y0 + self._nrows if y1 < 0: y1 = y1 + self._nrows if x0 > x1: x0, x1 = x1, x0 if y0 > y1: y0, y1 = y1, y0 if x0!=x1 or y0!=y1: #column span if x0!=x1: for y in xrange(y0, y1+1): for x in xrange(x0,x1+1): csa((x,y)) #row span if y0!=y1: for y in xrange(y0, y1+1): for x in xrange(x0,x1+1): rsa((x,y)) for y in xrange(y0, y1+1): for x in xrange(x0,x1+1): nosplitRanges[x,y] = None # set the main entry nosplitRanges[x0,y0] = (x0, y0, x1, y1) def _calcSpanRects(self): """Work out rects for tables which do row and column spanning. Based on self._spanRanges, which is already known, and the widths which were given or previously calculated, self._spanRects shows the real coords for drawing: (col, row) -> (x, y, width, height) for each cell. Any cell which 'does not exist' as another has spanned over it will get a None entry on the right """ spanRects = getattr(self,'_spanRects',{}) hmax = getattr(self,'_hmax',None) longTable = self._longTableOptimize if spanRects and (longTable and hmax==self._hmax_spanRects or not longTable): return colpositions = self._colpositions rowpositions = self._rowpositions vBlocks = {} hBlocks = {} rlim = len(rowpositions)-1 for (coord, value) in self._spanRanges.items(): if value is None: spanRects[coord] = None else: col0, row0, col1, row1 = value if row1>=rlim: continue col,row = coord if col1-col0>0: for _ in xrange(col0+1,col1+1): vBlocks.setdefault(colpositions[_],[]).append((rowpositions[row1+1],rowpositions[row0])) if row1-row0>0: for _ in xrange(row0+1,row1+1): hBlocks.setdefault(rowpositions[_],[]).append((colpositions[col0],colpositions[col1+1])) x = colpositions[col0] y = rowpositions[row1+1] width = colpositions[col1+1] - x height = rowpositions[row0] - y spanRects[coord] = (x, y, width, height) for _ in hBlocks, vBlocks: for value in _.values(): value.sort() self._spanRects = spanRects self._vBlocks = vBlocks self._hBlocks = hBlocks self._hmax_spanRects = hmax def setStyle(self, tblstyle): if not isinstance(tblstyle,TableStyle): tblstyle = TableStyle(tblstyle) for cmd in tblstyle.getCommands(): self._addCommand(cmd) for k,v in tblstyle._opts.items(): setattr(self,k,v) for a in ('spaceBefore','spaceAfter'): if not hasattr(self,a) and hasattr(tblstyle,a): setattr(self,a,getattr(tblstyle,a)) def _addCommand(self,cmd): if cmd[0] in ('BACKGROUND','ROWBACKGROUNDS','COLBACKGROUNDS'): self._bkgrndcmds.append(cmd) elif cmd[0] == 'SPAN': self._spanCmds.append(cmd) elif cmd[0] == 'NOSPLIT': # we expect op, start, stop self._nosplitCmds.append(cmd) elif _isLineCommand(cmd): # we expect op, start, stop, weight, colour, cap, dashes, join cmd = list(cmd) if len(cmd)<5: raise ValueError('bad line command '+ascii(cmd)) #determine line cap value at position 5. This can be str or numeric. if len(cmd)<6: cmd.append(1) else: cap = _convert2int(cmd[5], LINECAPS, 0, 2, 'cap', cmd) cmd[5] = cap #dashes at index 6 - this is a dash array: if len(cmd)<7: cmd.append(None) #join mode at index 7 - can be str or numeric, look up as for caps if len(cmd)<8: cmd.append(1) else: join = _convert2int(cmd[7], LINEJOINS, 0, 2, 'join', cmd) cmd[7] = join #linecount at index 8. Default is 1, set to 2 for double line. if len(cmd)<9: cmd.append(1) else: lineCount = cmd[8] if lineCount is None: lineCount = 1 cmd[8] = lineCount assert lineCount >= 1 #linespacing at index 9. Not applicable unless 2+ lines, defaults to line #width so you get a visible gap between centres if len(cmd)<10: cmd.append(cmd[3]) else: space = cmd[9] if space is None: space = cmd[3] cmd[9] = space assert len(cmd) == 10 self._linecmds.append(tuple(cmd)) else: (op, (sc, sr), (ec, er)), values = cmd[:3] , cmd[3:] if sc < 0: sc = sc + self._ncols if ec < 0: ec = ec + self._ncols if sr < 0: sr = sr + self._nrows if er < 0: er = er + self._nrows for i in xrange(sr, er+1): for j in xrange(sc, ec+1): _setCellStyle(self._cellStyles, i, j, op, values) def _drawLines(self): ccap, cdash, cjoin = None, None, None self.canv.saveState() for op, (sc,sr), (ec,er), weight, color, cap, dash, join, count, space in self._linecmds: if isinstance(sr,strTypes) and sr.startswith('split'): continue if sc < 0: sc = sc + self._ncols if ec < 0: ec = ec + self._ncols if sr < 0: sr = sr + self._nrows if er < 0: er = er + self._nrows if cap!=None and ccap!=cap: self.canv.setLineCap(cap) ccap = cap if dash is None or dash == []: if cdash is not None: self.canv.setDash() cdash = None elif dash != cdash: self.canv.setDash(dash) cdash = dash if join is not None and cjoin!=join: self.canv.setLineJoin(join) cjoin = join getattr(self,_LineOpMap.get(op, '_drawUnknown' ))( (sc, sr), (ec, er), weight, color, count, space) self.canv.restoreState() self._curcolor = None def _drawUnknown(self, start, end, weight, color, count, space): #we are only called from _drawLines which is one level up import sys op = sys._getframe(1).f_locals['op'] raise ValueError("Unknown line command '%s'" % op) def _drawGrid(self, start, end, weight, color, count, space): self._drawBox( start, end, weight, color, count, space) self._drawInnerGrid( start, end, weight, color, count, space) def _drawBox(self, start, end, weight, color, count, space): sc,sr = start ec,er = end self._drawHLines((sc, sr), (ec, sr), weight, color, count, space) self._drawHLines((sc, er+1), (ec, er+1), weight, color, count, space) self._drawVLines((sc, sr), (sc, er), weight, color, count, space) self._drawVLines((ec+1, sr), (ec+1, er), weight, color, count, space) def _drawInnerGrid(self, start, end, weight, color, count, space): sc,sr = start ec,er = end self._drawHLines((sc, sr+1), (ec, er), weight, color, count, space) self._drawVLines((sc+1, sr), (ec, er), weight, color, count, space) def _prepLine(self, weight, color): if color and color!=self._curcolor: self.canv.setStrokeColor(color) self._curcolor = color if weight and weight!=self._curweight: self.canv.setLineWidth(weight) self._curweight = weight def _drawHLines(self, start, end, weight, color, count, space): sc,sr = start ec,er = end ecp = self._colpositions[sc:ec+2] rp = self._rowpositions[sr:er+1] if len(ecp)<=1 or len(rp)<1: return self._prepLine(weight, color) scp = ecp[0] ecp = ecp[-1] hBlocks = getattr(self,'_hBlocks',{}) canvLine = self.canv.line if count == 1: for y in rp: _hLine(canvLine, scp, ecp, y, hBlocks) else: lf = lambda x0,y0,x1,y1,canvLine=canvLine, ws=weight+space, count=count: _multiLine(x0,x1,y0,canvLine,ws,count) for y in rp: _hLine(lf, scp, ecp, y, hBlocks) def _drawHLinesB(self, start, end, weight, color, count, space): sc,sr = start ec,er = end self._drawHLines((sc, sr+1), (ec, er+1), weight, color, count, space) def _drawVLines(self, start, end, weight, color, count, space): sc,sr = start ec,er = end erp = self._rowpositions[sr:er+2] cp = self._colpositions[sc:ec+1] if len(erp)<=1 or len(cp)<1: return self._prepLine(weight, color) srp = erp[0] erp = erp[-1] vBlocks = getattr(self,'_vBlocks',{}) canvLine = lambda y0, x0, y1, x1, _line=self.canv.line: _line(x0,y0,x1,y1) if count == 1: for x in cp: _hLine(canvLine, erp, srp, x, vBlocks) else: lf = lambda x0,y0,x1,y1,canvLine=canvLine, ws=weight+space, count=count: _multiLine(x0,x1,y0,canvLine,ws,count) for x in cp: _hLine(lf, erp, srp, x, vBlocks) def _drawVLinesA(self, start, end, weight, color, count, space): sc,sr = start ec,er = end self._drawVLines((sc+1, sr), (ec+1, er), weight, color, count, space) def wrap(self, availWidth, availHeight): self._calc(availWidth, availHeight) self.availWidth = availWidth return (self._width, self._height) def onSplit(self,T,byRow=1): ''' This method will be called when the Table is split. Special purpose tables can override to do special stuff. ''' pass def _cr_0(self,n,cmds,nr0): for c in cmds: c = tuple(c) (sc,sr), (ec,er) = c[1:3] if isinstance(sr,strTypes): continue if sr<0: sr += nr0 if sr>=n: continue if er>=n: er = n-1 self._addCommand((c[0],)+((sc, sr), (ec, er))+c[3:]) def _cr_1_1(self,n,repeatRows, cmds): for c in cmds: c = tuple(c) (sc,sr), (ec,er) = c[1:3] if sr in ('splitfirst','splitlast'): self._addCommand(c) else: if sr>=0 and sr>=repeatRows and sr=0 and er=repeatRows and sr=repeatRows and sr>=n: sr=sr+repeatRows-n if er>=repeatRows and er=repeatRows and er>=n: er=er+repeatRows-n self._addCommand((c[0],)+((sc, sr), (ec, er))+c[3:]) sr = self._rowSplitRange if sr: sr, er = sr if sr>=0 and sr>=repeatRows and sr=0 and er=repeatRows and sr=repeatRows and sr>=n: sr=sr+repeatRows-n if er>=repeatRows and er=repeatRows and er>=n: er=er+repeatRows-n self._rowSplitRange = sr,er def _cr_1_0(self,n,cmds): for c in cmds: c = tuple(c) (sc,sr), (ec,er) = c[1:3] if sr in ('splitfirst','splitlast'): self._addCommand(c) else: if er>=0 and er=0 and sr=n: sr -= n if er>=n: er -= n self._addCommand((c[0],)+((sc, sr), (ec, er))+c[3:]) def _splitRows(self,availHeight): n=self._getFirstPossibleSplitRowPosition(availHeight) repeatRows = self.repeatRows if n<= (repeatRows if isinstance(repeatRows,int) else (max(repeatRows)+1)): return [] lim = len(self._rowHeights) if n==lim: return [self] lo = self._rowSplitRange if lo: lo, hi = lo if lo<0: lo += lim if hi<0: hi += lim if n>hi: return self._splitRows(availHeight - sum(self._rowHeights[hi:n])) elif n=n: # we have to split the BOX A.append(('LINEABOVE',(sc,sr), (ec,sr), weight, color, cap, dash, join, count, space)) A.append(('LINEBEFORE',(sc,sr), (sc,er), weight, color, cap, dash, join, count, space)) A.append(('LINEAFTER',(ec,sr), (ec,er), weight, color, cap, dash, join, count, space)) A.append(('LINEBELOW',(sc,er), (ec,er), weight, color, cap, dash, join, count, space)) if op=='GRID': A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space)) A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space)) A.append(('INNERGRID',(sc,sr), (ec,er), weight, color, cap, dash, join, count, space)) else: A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space)) elif op in ('INNERGRID','LINEABOVE'): if sr=n: A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space)) A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space)) A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space)) elif op == 'LINEBELOW': if sr=(n-1): A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space)) A.append((op,(sc,sr), (ec,er), weight, color)) elif op == 'LINEABOVE': if sr<=n and er>=n: A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space)) A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space)) else: A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space)) R0._cr_0(n,A,nrows) R0._cr_0(n,self._bkgrndcmds,nrows) R0._cr_0(n,self._spanCmds,nrows) R0._cr_0(n,self._nosplitCmds,nrows) if ident: ident = IdentStr(ident) if repeatRows: if isinstance(repeatRows,int): iRows = data[:repeatRows] nRepeatRows = repeatRows iRowH = self._argH[:repeatRows] iCS = self._cellStyles[:repeatRows] else: #we have a list of repeated rows eg (1,3) repeatRows = list(sorted(repeatRows)) iRows = [data[i] for i in repeatRows] nRepeatRows = len(repeatRows) iRowH = [self._argH[i] for i in repeatRows] iCS = [self._cellStyles[i] for i in repeatRows] R1 = self.__class__(iRows+data[n:],colWidths=self._colWidths, rowHeights=iRowH+self._argH[n:], repeatRows=nRepeatRows, repeatCols=repeatCols, splitByRow=splitByRow, normalizedData=1, cellStyles=iCS+self._cellStyles[n:], ident=ident, spaceAfter=getattr(self,'spaceAfter',None), ) R1._cr_1_1(n,nRepeatRows,A) R1._cr_1_1(n,nRepeatRows,self._bkgrndcmds) R1._cr_1_1(n,nRepeatRows,self._spanCmds) R1._cr_1_1(n,nRepeatRows,self._nosplitCmds) else: #R1 = slelf.__class__(data[n:], self._argW, self._argH[n:], R1 = self.__class__(data[n:], colWidths=self._colWidths, rowHeights=self._argH[n:], repeatRows=repeatRows, repeatCols=repeatCols, splitByRow=splitByRow, normalizedData=1, cellStyles=self._cellStyles[n:], ident=ident, spaceAfter=getattr(self,'spaceAfter',None), ) R1._cr_1_0(n,A) R1._cr_1_0(n,self._bkgrndcmds) R1._cr_1_0(n,self._spanCmds) R1._cr_1_0(n,self._nosplitCmds) R0.hAlign = R1.hAlign = self.hAlign R0.vAlign = R1.vAlign = self.vAlign self.onSplit(R0) self.onSplit(R1) return [R0,R1] def _getRowImpossible(impossible,cells,ranges): for xy in cells: r=ranges[xy] if r!=None: y1,y2=r[1],r[3] if y1!=y2: ymin=min(y1,y2) #normalize ymax=max(y1,y2) #normalize y=ymin+1 while 1: if y>ymax: break impossible[y]=None #split at position y is impossible because of overlapping rowspan y+=1 _getRowImpossible=staticmethod(_getRowImpossible) def _getFirstPossibleSplitRowPosition(self,availHeight): impossible={} if self._spanCmds: self._getRowImpossible(impossible,self._rowSpanCells,self._spanRanges) if self._nosplitCmds: self._getRowImpossible(impossible,self._rowNoSplitCells,self._nosplitRanges) h = 0 n = 1 split_at = 0 # from this point of view 0 is the first position where the table may *always* be splitted for rh in self._rowHeights: if h+rh>availHeight: break if n not in impossible: split_at=n h=h+rh n=n+1 return split_at def split(self, availWidth, availHeight): self._calc(availWidth, availHeight) if self.splitByRow: if not rl_config.allowTableBoundsErrors and self._width>availWidth: return [] return self._splitRows(availHeight) else: raise NotImplementedError def draw(self): self._curweight = self._curcolor = self._curcellstyle = None self._drawBkgrnd() if not self._spanCmds: # old fashioned case, no spanning, steam on and do each cell for row, rowstyle, rowpos, rowheight in zip(self._cellvalues, self._cellStyles, self._rowpositions[1:], self._rowHeights): for cellval, cellstyle, colpos, colwidth in zip(row, rowstyle, self._colpositions[:-1], self._colWidths): self._drawCell(cellval, cellstyle, (colpos, rowpos), (colwidth, rowheight)) else: # we have some row or col spans, need a more complex algorithm # to find the rect for each for rowNo in xrange(self._nrows): for colNo in xrange(self._ncols): cellRect = self._spanRects[colNo, rowNo] if cellRect is not None: (x, y, width, height) = cellRect cellval = self._cellvalues[rowNo][colNo] cellstyle = self._cellStyles[rowNo][colNo] self._drawCell(cellval, cellstyle, (x, y), (width, height)) self._drawLines() def _drawBkgrnd(self): nrows = self._nrows ncols = self._ncols canv = self.canv colpositions = self._colpositions rowpositions = self._rowpositions rowHeights = self._rowHeights colWidths = self._colWidths spanRects = getattr(self,'_spanRects',None) for cmd, (sc, sr), (ec, er), arg in self._bkgrndcmds: if sc < 0: sc = sc + ncols if ec < 0: ec = ec + ncols if sr < 0: sr = sr + nrows if er < 0: er = er + nrows x0 = colpositions[sc] y0 = rowpositions[sr] x1 = colpositions[min(ec+1,ncols)] y1 = rowpositions[min(er+1,nrows)] w, h = x1-x0, y1-y0 if hasattr(arg,'__call__'): arg(self,canv, x0, y0, w, h) elif cmd == 'ROWBACKGROUNDS': #Need a list of colors to cycle through. The arguments #might be already colours, or convertible to colors, or # None, or the str 'None'. #It's very common to alternate a pale shade with None. colorCycle = list(map(colors.toColorOrNone, arg)) count = len(colorCycle) rowCount = er - sr + 1 for i in xrange(rowCount): color = colorCycle[i%count] h = rowHeights[sr + i] if color: canv.setFillColor(color) canv.rect(x0, y0, w, -h, stroke=0,fill=1) y0 = y0 - h elif cmd == 'COLBACKGROUNDS': #cycle through colours columnwise colorCycle = list(map(colors.toColorOrNone, arg)) count = len(colorCycle) colCount = ec - sc + 1 for i in xrange(colCount): color = colorCycle[i%count] w = colWidths[sc + i] if color: canv.setFillColor(color) canv.rect(x0, y0, w, h, stroke=0,fill=1) x0 = x0 +w else: #cmd=='BACKGROUND' color = colors.toColorOrNone(arg) if color: if ec==sc and er==sr and spanRects: xywh = spanRects.get((sc,sr)) if xywh: #it's a single cell x0, y0, w, h = xywh canv.setFillColor(color) canv.rect(x0, y0, w, h, stroke=0,fill=1) def _drawCell(self, cellval, cellstyle, pos, size): colpos, rowpos = pos colwidth, rowheight = size if self._curcellstyle is not cellstyle: cur = self._curcellstyle if cur is None or cellstyle.color != cur.color: self.canv.setFillColor(cellstyle.color) if cur is None or cellstyle.leading != cur.leading or cellstyle.fontname != cur.fontname or cellstyle.fontsize != cur.fontsize: self.canv.setFont(cellstyle.fontname, cellstyle.fontsize, cellstyle.leading) self._curcellstyle = cellstyle just = cellstyle.alignment valign = cellstyle.valign if isinstance(cellval,(tuple,list,Flowable)): if not isinstance(cellval,(tuple,list)): cellval = (cellval,) # we assume it's a list of Flowables W = [] H = [] w, h = self._listCellGeom(cellval,colwidth,cellstyle,W=W, H=H,aH=rowheight) if valign=='TOP': y = rowpos + rowheight - cellstyle.topPadding elif valign=='BOTTOM': y = rowpos+cellstyle.bottomPadding + h else: y = rowpos+(rowheight+cellstyle.bottomPadding-cellstyle.topPadding+h)/2.0 if cellval: y += cellval[0].getSpaceBefore() for v, w, h in zip(cellval,W,H): if just=='LEFT': x = colpos+cellstyle.leftPadding elif just=='RIGHT': x = colpos+colwidth-cellstyle.rightPadding - w elif just in ('CENTRE', 'CENTER'): x = colpos+(colwidth+cellstyle.leftPadding-cellstyle.rightPadding-w)/2.0 else: raise ValueError('Invalid justification %s' % just) y -= v.getSpaceBefore() y -= h v.drawOn(self.canv,x,y) y -= v.getSpaceAfter() else: if just == 'LEFT': draw = self.canv.drawString x = colpos + cellstyle.leftPadding elif just in ('CENTRE', 'CENTER'): draw = self.canv.drawCentredString x = colpos+(colwidth+cellstyle.leftPadding-cellstyle.rightPadding)*0.5 elif just == 'RIGHT': draw = self.canv.drawRightString x = colpos + colwidth - cellstyle.rightPadding elif just == 'DECIMAL': draw = self.canv.drawAlignedString x = colpos + colwidth - cellstyle.rightPadding else: raise ValueError('Invalid justification %s' % just) vals = str(cellval).split("\n") n = len(vals) leading = cellstyle.leading fontsize = cellstyle.fontsize if valign=='BOTTOM': y = rowpos + cellstyle.bottomPadding+n*leading-fontsize elif valign=='TOP': y = rowpos + rowheight - cellstyle.topPadding - fontsize elif valign=='MIDDLE': #tim roberts pointed out missing fontsize correction 2004-10-04 y = rowpos + (cellstyle.bottomPadding + rowheight-cellstyle.topPadding+n*leading)/2.0 - fontsize else: raise ValueError("Bad valign: '%s'" % str(valign)) for v in vals: draw(x, y, v) y -= leading onDraw = getattr(cellval,'onDraw',None) if onDraw: onDraw(self.canv,cellval.kind,cellval.label) if cellstyle.href: #external hyperlink self.canv.linkURL(cellstyle.href, (colpos, rowpos, colpos + colwidth, rowpos + rowheight), relative=1) if cellstyle.destination: #external hyperlink self.canv.linkRect("", cellstyle.destination, Rect=(colpos, rowpos, colpos + colwidth, rowpos + rowheight), relative=1) _LineOpMap = { 'GRID':'_drawGrid', 'BOX':'_drawBox', 'OUTLINE':'_drawBox', 'INNERGRID':'_drawInnerGrid', 'LINEBELOW':'_drawHLinesB', 'LINEABOVE':'_drawHLines', 'LINEBEFORE':'_drawVLines', 'LINEAFTER':'_drawVLinesA', } class LongTable(Table): '''Henning von Bargen's changes will be active''' _longTableOptimize = 1 LINECOMMANDS = list(_LineOpMap.keys()) def _isLineCommand(cmd): return cmd[0] in LINECOMMANDS def _setCellStyle(cellStyles, i, j, op, values): #new = CellStyle('<%d, %d>' % (i,j), cellStyles[i][j]) #cellStyles[i][j] = new ## modify in place!!! new = cellStyles[i][j] if op == 'FONT': n = len(values) new.fontname = values[0] if n>1: new.fontsize = values[1] if n>2: new.leading = values[2] else: new.leading = new.fontsize*1.2 elif op in ('FONTNAME', 'FACE'): new.fontname = values[0] elif op in ('SIZE', 'FONTSIZE'): new.fontsize = values[0] elif op == 'LEADING': new.leading = values[0] elif op == 'TEXTCOLOR': new.color = colors.toColor(values[0], colors.Color(0,0,0)) elif op in ('ALIGN', 'ALIGNMENT'): new.alignment = values[0] elif op == 'VALIGN': new.valign = values[0] elif op == 'LEFTPADDING': new.leftPadding = values[0] elif op == 'RIGHTPADDING': new.rightPadding = values[0] elif op == 'TOPPADDING': new.topPadding = values[0] elif op == 'BOTTOMPADDING': new.bottomPadding = values[0] elif op == 'HREF': new.href = values[0] elif op == 'DESTINATION': new.destination = values[0] GRID_STYLE = TableStyle( [('GRID', (0,0), (-1,-1), 0.25, colors.black), ('ALIGN', (1,1), (-1,-1), 'RIGHT')] ) BOX_STYLE = TableStyle( [('BOX', (0,0), (-1,-1), 0.50, colors.black), ('ALIGN', (1,1), (-1,-1), 'RIGHT')] ) LABELED_GRID_STYLE = TableStyle( [('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 2, colors.black), ('LINEBELOW', (0,0), (-1,0), 2, colors.black), ('LINEAFTER', (0,0), (0,-1), 2, colors.black), ('ALIGN', (1,1), (-1,-1), 'RIGHT')] ) COLORED_GRID_STYLE = TableStyle( [('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 2, colors.red), ('LINEBELOW', (0,0), (-1,0), 2, colors.black), ('LINEAFTER', (0,0), (0,-1), 2, colors.black), ('ALIGN', (1,1), (-1,-1), 'RIGHT')] ) LIST_STYLE = TableStyle( [('LINEABOVE', (0,0), (-1,0), 2, colors.green), ('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), ('LINEBELOW', (0,-1), (-1,-1), 2, colors.green), ('ALIGN', (1,1), (-1,-1), 'RIGHT')] ) # experimental iterator which can apply a sequence # of colors e.g. Blue, None, Blue, None as you move # down. if __name__ == '__main__': from tests.test_platypus_tables import old_tables_test old_tables_test() reportlab-3.3.0/src/reportlab/platypus/tableofcontents.py0000664000175000017500000005146212661063724023270 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/tableofcontents.py __version__='3.3.0' __doc__="""Experimental class to generate Tables of Contents easily This module defines a single TableOfContents() class that can be used to create automatically a table of tontents for Platypus documents like this: story = [] toc = TableOfContents() story.append(toc) # some heading paragraphs here... doc = MyTemplate(path) doc.multiBuild(story) The data needed to create the table is a list of (level, text, pageNum) triplets, plus some paragraph styles for each level of the table itself. The triplets will usually be created in a document template's method like afterFlowable(), making notification calls using the notify() method with appropriate data like this: (level, text, pageNum) = ... self.notify('TOCEntry', (level, text, pageNum)) Optionally the list can contain four items in which case the last item is a destination key which the entry should point to. A bookmark with this key needs to be created first like this: key = 'ch%s' % self.seq.nextf('chapter') self.canv.bookmarkPage(key) self.notify('TOCEntry', (level, text, pageNum, key)) As the table of contents need at least two passes over the Platypus story which is why the multiBuild() method must be called. The levelParaStyle variables are the paragraph styles used to format the entries in the table of contents. Their indentation is calculated like this: each entry starts at a multiple of some constant named delta. If one entry spans more than one line, all lines after the first are indented by the same constant named epsilon. """ from reportlab.lib import enums from reportlab.lib.units import cm from reportlab.lib.utils import commasplit, escapeOnce, encode_label, decode_label, strTypes, asUnicode from reportlab.lib.styles import ParagraphStyle, _baseFontName from reportlab.platypus.paragraph import Paragraph from reportlab.platypus.doctemplate import IndexingFlowable from reportlab.platypus.tables import TableStyle, Table from reportlab.platypus.flowables import Spacer, Flowable from reportlab.pdfbase.pdfmetrics import stringWidth from reportlab.pdfgen import canvas import unicodedata def unquote(txt): from xml.sax.saxutils import unescape return unescape(txt, {"'": "'", """: '"'}) try: set except: class set(list): def add(self,x): if x not in self: list.append(self,x) def drawPageNumbers(canvas, style, pages, availWidth, availHeight, dot=' . ', formatter=None): ''' Draws pagestr on the canvas using the given style. If dot is None, pagestr is drawn at the current position in the canvas. If dot is a string, pagestr is drawn right-aligned. If the string is not empty, the gap is filled with it. ''' pagestr = ', '.join([str(p) for p, _ in pages]) x, y = canvas._curr_tx_info['cur_x'], canvas._curr_tx_info['cur_y'] fontSize = style.fontSize pagestrw = stringWidth(pagestr, style.fontName, fontSize) #if it's too long to fit, we need to shrink to fit in 10% increments. #it would be very hard to output multiline entries. #however, we impose a minimum size of 1 point as we don't want an #infinite loop. Ultimately we should allow a TOC entry to spill #over onto a second line if needed. freeWidth = availWidth-x while pagestrw > freeWidth and fontSize >= 1.0: fontSize = 0.9 * fontSize pagestrw = stringWidth(pagestr, style.fontName, fontSize) if isinstance(dot, strTypes): if dot: dotw = stringWidth(dot, style.fontName, fontSize) dotsn = int((availWidth-x-pagestrw)/dotw) else: dotsn = dotw = 0 text = '%s%s' % (dotsn * dot, pagestr) newx = availWidth - dotsn*dotw - pagestrw pagex = availWidth - pagestrw elif dot is None: text = ', ' + pagestr newx = x pagex = newx else: raise TypeError('Argument dot should either be None or an instance of basestring.') tx = canvas.beginText(newx, y) tx.setFont(style.fontName, fontSize) tx.setFillColor(style.textColor) tx.textLine(text) canvas.drawText(tx) commaw = stringWidth(', ', style.fontName, fontSize) for p, key in pages: if not key: continue w = stringWidth(str(p), style.fontName, fontSize) canvas.linkRect('', key, (pagex, y, pagex+w, y+style.leading), relative=1) pagex += w + commaw # Default paragraph styles for tables of contents. # (This could also be generated automatically or even # on-demand if it is not known how many levels the # TOC will finally need to display...) delta = 1*cm epsilon = 0.5*cm defaultLevelStyles = [ ParagraphStyle( name='Level 0', fontName=_baseFontName, fontSize=10, leading=11, firstLineIndent = 0, leftIndent = epsilon)] defaultTableStyle = \ TableStyle([ ('VALIGN', (0,0), (-1,-1), 'TOP'), ('RIGHTPADDING', (0,0), (-1,-1), 0), ('LEFTPADDING', (0,0), (-1,-1), 0), ]) class TableOfContents(IndexingFlowable): """This creates a formatted table of contents. It presumes a correct block of data is passed in. The data block contains a list of (level, text, pageNumber) triplets. You can supply a paragraph style for each level (starting at zero). Set dotsMinLevel to determine from which level on a line of dots should be drawn between the text and the page number. If dotsMinLevel is set to a negative value, no dotted lines are drawn. """ def __init__(self,**kwds): self.rightColumnWidth = kwds.get('rightColumnWidth',72) self.levelStyles = kwds.get('levelStyles',defaultLevelStyles) self.tableStyle = kwds.get('tableStyle',defaultTableStyle) self.dotsMinLevel = kwds.get('dotsMinLevel',1) self.formatter = kwds.get('formatter',None) if kwds: raise ValueError('unexpected keyword arguments %s' % ', '.join(kwds.keys())) self._table = None self._entries = [] self._lastEntries = [] def beforeBuild(self): # keep track of the last run self._lastEntries = self._entries[:] self.clearEntries() def isIndexing(self): return 1 def isSatisfied(self): return (self._entries == self._lastEntries) def notify(self, kind, stuff): """The notification hook called to register all kinds of events. Here we are interested in 'TOCEntry' events only. """ if kind == 'TOCEntry': self.addEntry(*stuff) def clearEntries(self): self._entries = [] def getLevelStyle(self, n): '''Returns the style for level n, generating and caching styles on demand if not present.''' try: return self.levelStyles[n] except IndexError: prevstyle = self.getLevelStyle(n-1) self.levelStyles.append(ParagraphStyle( name='%s-%d-indented' % (prevstyle.name, n), parent=prevstyle, firstLineIndent = prevstyle.firstLineIndent+delta, leftIndent = prevstyle.leftIndent+delta)) return self.levelStyles[n] def addEntry(self, level, text, pageNum, key=None): """Adds one entry to the table of contents. This allows incremental buildup by a doctemplate. Requires that enough styles are defined.""" assert type(level) == type(1), "Level must be an integer" self._entries.append((level, text, pageNum, key)) def addEntries(self, listOfEntries): """Bulk creation of entries in the table of contents. If you knew the titles but not the page numbers, you could supply them to get sensible output on the first run.""" for entryargs in listOfEntries: self.addEntry(*entryargs) def wrap(self, availWidth, availHeight): "All table properties should be known by now." # makes an internal table which does all the work. # we draw the LAST RUN's entries! If there are # none, we make some dummy data to keep the table # from complaining if len(self._lastEntries) == 0: _tempEntries = [(0,'Placeholder for table of contents',0,None)] else: _tempEntries = self._lastEntries def drawTOCEntryEnd(canvas, kind, label): '''Callback to draw dots and page numbers after each entry.''' label = label.split(',') page, level, key = int(label[0]), int(label[1]), eval(label[2],{}) style = self.getLevelStyle(level) if self.dotsMinLevel >= 0 and level >= self.dotsMinLevel: dot = ' . ' else: dot = '' if self.formatter: page = self.formatter(page) drawPageNumbers(canvas, style, [(page, key)], availWidth, availHeight, dot) self.canv.drawTOCEntryEnd = drawTOCEntryEnd tableData = [] for (level, text, pageNum, key) in _tempEntries: style = self.getLevelStyle(level) if key: text = '
    %s' % (key, text) keyVal = repr(key).replace(',','\\x2c').replace('"','\\x2c') else: keyVal = None para = Paragraph('%s' % (text, pageNum, level, keyVal), style) if style.spaceBefore: tableData.append([Spacer(1, style.spaceBefore),]) tableData.append([para,]) self._table = Table(tableData, colWidths=(availWidth,), style=self.tableStyle) self.width, self.height = self._table.wrapOn(self.canv,availWidth, availHeight) return (self.width, self.height) def split(self, availWidth, availHeight): """At this stage we do not care about splitting the entries, we will just return a list of platypus tables. Presumably the calling app has a pointer to the original TableOfContents object; Platypus just sees tables. """ return self._table.splitOn(self.canv,availWidth, availHeight) def drawOn(self, canvas, x, y, _sW=0): """Don't do this at home! The standard calls for implementing draw(); we are hooking this in order to delegate ALL the drawing work to the embedded table object. """ self._table.drawOn(canvas, x, y, _sW) def makeTuple(x): if hasattr(x, '__iter__'): return tuple(x) return (x,) class SimpleIndex(IndexingFlowable): """Creates multi level indexes. The styling can be cutomized and alphabetic headers turned on and off. """ def __init__(self, **kwargs): """ Constructor of SimpleIndex. Accepts the same arguments as the setup method. """ #keep stuff in a dictionary while building self._entries = {} self._lastEntries = {} self._flowable = None self.setup(**kwargs) def getFormatFunc(self,format): try: D = {} exec('from reportlab.lib.sequencer import _format_%s as formatFunc' % format, D) return D['formatFunc'] except ImportError: raise ValueError('Unknown format %r' % format) def setup(self, style=None, dot=None, tableStyle=None, headers=True, name=None, format='123', offset=0): """ This method makes it possible to change styling and other parameters on an existing object. style is the paragraph style to use for index entries. dot can either be None or a string. If it's None, entries are immediatly followed by their corresponding page numbers. If it's a string, page numbers are aligned on the right side of the document and the gap filled with a repeating sequence of the string. tableStyle is the style used by the table which the index uses to draw itself. Use this to change properties like spacing between elements. headers is a boolean. If it is True, alphabetic headers are displayed in the Index when the first letter changes. If False, we just output some extra space before the next item name makes it possible to use several indexes in one document. If you want this use this parameter to give each index a unique name. You can then index a term by refering to the name of the index which it should appear in: format can be 'I', 'i', '123', 'ABC', 'abc' """ if style is None: style = ParagraphStyle(name='index', fontName=_baseFontName, fontSize=11) self.textStyle = style self.tableStyle = tableStyle or defaultTableStyle self.dot = dot self.headers = headers if name is None: from reportlab.platypus.paraparser import DEFAULT_INDEX_NAME as name self.name = name self.formatFunc = self.getFormatFunc(format) self.offset = offset def __call__(self,canv,kind,label): try: terms, format, offset = decode_label(label) except: terms = label format = offset = None if format is None: formatFunc = self.formatFunc else: formatFunc = self.getFormatFunc(format) if offset is None: offset = self.offset terms = commasplit(terms) cPN = canv.getPageNumber() pns = formatFunc(cPN-offset) key = 'ix_%s_%s_p_%s' % (self.name, label, pns) info = canv._curr_tx_info canv.bookmarkHorizontal(key, info['cur_x'], info['cur_y'] + info['leading']) self.addEntry(terms, (cPN,pns), key) def getCanvasMaker(self, canvasmaker=canvas.Canvas): def newcanvasmaker(*args, **kwargs): from reportlab.pdfgen import canvas c = canvasmaker(*args, **kwargs) setattr(c,self.name,self) return c return newcanvasmaker def isIndexing(self): return 1 def isSatisfied(self): return (self._entries == self._lastEntries) def beforeBuild(self): # keep track of the last run self._lastEntries = self._entries.copy() self.clearEntries() def clearEntries(self): self._entries = {} def notify(self, kind, stuff): """The notification hook called to register all kinds of events. Here we are interested in 'IndexEntry' events only. """ if kind == 'IndexEntry': text, pageNum = stuff self.addEntry(text, (self._canv.getPageNumber(),pageNum)) def addEntry(self, text, pageNum, key=None): """Allows incremental buildup""" self._entries.setdefault(makeTuple(text),set([])).add((pageNum, key)) def split(self, availWidth, availHeight): """At this stage we do not care about splitting the entries, we will just return a list of platypus tables. Presumably the calling app has a pointer to the original TableOfContents object; Platypus just sees tables. """ return self._flowable.splitOn(self.canv,availWidth, availHeight) def _getlastEntries(self, dummy=[(['Placeholder for index'],enumerate((None,)*3))]): '''Return the last run's entries! If there are none, returns dummy.''' if not self._lastEntries: if self._entries: return list(self._entries.items()) return dummy return list(self._lastEntries.items()) def _build(self,availWidth,availHeight): _tempEntries = [(tuple(asUnicode(t) for t in texts),pageNumbers) for texts, pageNumbers in self._getlastEntries()] def getkey(seq): return [''.join((c for c in unicodedata.normalize('NFD', x.upper()) if unicodedata.category(c) != 'Mn')) for x in seq[0]] _tempEntries.sort(key=getkey) leveloffset = self.headers and 1 or 0 def drawIndexEntryEnd(canvas, kind, label): '''Callback to draw dots and page numbers after each entry.''' style = self.getLevelStyle(leveloffset) pages = [(p[1],k) for p,k in sorted(decode_label(label))] drawPageNumbers(canvas, style, pages, availWidth, availHeight, self.dot) self.canv.drawIndexEntryEnd = drawIndexEntryEnd alpha = '' tableData = [] lastTexts = [] alphaStyle = self.getLevelStyle(0) for texts, pageNumbers in _tempEntries: texts = list(texts) #track when the first character changes; either output some extra #space, or the first letter on a row of its own. We cannot do #widow/orphan control, sadly. nalpha = ''.join((c for c in unicodedata.normalize('NFD', texts[0][0].upper()) if unicodedata.category(c) != 'Mn')) if alpha != nalpha: alpha = nalpha if self.headers: header = alpha else: header = ' ' tableData.append([Spacer(1, alphaStyle.spaceBefore),]) tableData.append([Paragraph(header, alphaStyle),]) tableData.append([Spacer(1, alphaStyle.spaceAfter),]) i, diff = listdiff(lastTexts, texts) if diff: lastTexts = texts texts = texts[i:] label = encode_label(list(pageNumbers)) texts[-1] = '%s' % (texts[-1], label) for text in texts: #Platypus and RML differ on how parsed XML attributes are escaped. #e.g. . The only place this seems to bite us is in #the index entries so work around it here. text = escapeOnce(text) style = self.getLevelStyle(i+leveloffset) para = Paragraph(text, style) if style.spaceBefore: tableData.append([Spacer(1, style.spaceBefore),]) tableData.append([para,]) i += 1 self._flowable = Table(tableData, colWidths=[availWidth], style=self.tableStyle) def wrap(self, availWidth, availHeight): "All table properties should be known by now." self._build(availWidth,availHeight) self.width, self.height = self._flowable.wrapOn(self.canv,availWidth, availHeight) return self.width, self.height def drawOn(self, canvas, x, y, _sW=0): """Don't do this at home! The standard calls for implementing draw(); we are hooking this in order to delegate ALL the drawing work to the embedded table object. """ self._flowable.drawOn(canvas, x, y, _sW) def draw(self): t = self._flowable ocanv = getattr(t,'canv',None) if not ocanv: t.canv = self.canv try: t.draw() finally: if not ocanv: del t.canv def getLevelStyle(self, n): '''Returns the style for level n, generating and caching styles on demand if not present.''' if not hasattr(self.textStyle, '__iter__'): self.textStyle = [self.textStyle] try: return self.textStyle[n] except IndexError: self.textStyle = list(self.textStyle) prevstyle = self.getLevelStyle(n-1) self.textStyle.append(ParagraphStyle( name='%s-%d-indented' % (prevstyle.name, n), parent=prevstyle, firstLineIndent = prevstyle.firstLineIndent+.2*cm, leftIndent = prevstyle.leftIndent+.2*cm)) return self.textStyle[n] AlphabeticIndex = SimpleIndex def listdiff(l1, l2): m = min(len(l1), len(l2)) for i in range(m): if l1[i] != l2[i]: return i, l2[i:] return m, l2[m:] class ReferenceText(IndexingFlowable): """Fakery to illustrate how a reference would work if we could put it in a paragraph.""" def __init__(self, textPattern, targetKey): self.textPattern = textPattern self.target = targetKey self.paraStyle = ParagraphStyle('tmp') self._lastPageNum = None self._pageNum = -999 self._para = None def beforeBuild(self): self._lastPageNum = self._pageNum def notify(self, kind, stuff): if kind == 'Target': (key, pageNum) = stuff if key == self.target: self._pageNum = pageNum def wrap(self, availWidth, availHeight): text = self.textPattern % self._lastPageNum self._para = Paragraph(text, self.paraStyle) return self._para.wrap(availWidth, availHeight) def drawOn(self, canvas, x, y, _sW=0): self._para.drawOn(canvas, x, y, _sW) reportlab-3.3.0/src/reportlab/platypus/frames.py0000664000175000017500000002412312661063724021345 0ustar rptlabrptlab#Copyright ReportLab Europe Ltd. 2000-2016 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/frames.py __version__='3.3.0' __doc__="""A frame is a container for content on a page. """ import logging logger = logging.getLogger('reportlab.platypus') _geomAttr=('x1', 'y1', 'width', 'height', 'leftPadding', 'bottomPadding', 'rightPadding', 'topPadding') from reportlab import rl_config, isPy3 _FUZZ=rl_config._FUZZ class ShowBoundaryValue: def __init__(self,color=(0,0,0),width=0.1): self.color = color self.width = width if isPy3: def __bool__(self): return self.color is not None and self.width>=0 else: def __nonzero__(self): return self.color is not None and self.width>=0 class Frame: ''' A Frame is a piece of space in a document that is filled by the "flowables" in the story. For example in a book like document most pages have the text paragraphs in one or two frames. For generality a page might have several frames (for example for 3 column text or for text that wraps around a graphic). After creation a Frame is not usually manipulated directly by the applications program -- it is used internally by the platypus modules. Here is a diagramatid abstraction for the definitional part of a Frame:: width x2,y2 +---------------------------------+ | l top padding r | h | e +-------------------------+ i | e | f | | g | i | t | | h | g | | | t | h | p | | | t | a | | p | | d | | a | | | | d | | +-------------------------+ | | bottom padding | +---------------------------------+ (x1,y1) <-- lower left corner NOTE!! Frames are stateful objects. No single frame should be used in two documents at the same time (especially in the presence of multithreading. ''' def __init__(self, x1, y1, width,height, leftPadding=6, bottomPadding=6, rightPadding=6, topPadding=6, id=None, showBoundary=0, overlapAttachedSpace=None,_debug=None): self.id = id self._debug = _debug #these say where it goes on the page self.__dict__['_x1'] = x1 self.__dict__['_y1'] = y1 self.__dict__['_width'] = width self.__dict__['_height'] = height #these create some padding. self.__dict__['_leftPadding'] = leftPadding self.__dict__['_bottomPadding'] = bottomPadding self.__dict__['_rightPadding'] = rightPadding self.__dict__['_topPadding'] = topPadding # if we want a boundary to be shown self.showBoundary = showBoundary if overlapAttachedSpace is None: overlapAttachedSpace = rl_config.overlapAttachedSpace self._oASpace = overlapAttachedSpace self._geom() self._reset() def __getattr__(self,a): if a in _geomAttr: return self.__dict__['_'+a] raise AttributeError(a) def __setattr__(self,a,v): if a in _geomAttr: self.__dict__['_'+a] = v self._geom() else: self.__dict__[a] = v def _saveGeom(self, **kwds): if not self.__dict__.setdefault('_savedGeom',{}): for ga in _geomAttr: ga = '_'+ga self.__dict__['_savedGeom'][ga] = self.__dict__[ga] for k,v in kwds.items(): setattr(self,k,v) def _restoreGeom(self): if self.__dict__.get('_savedGeom',None): for ga in _geomAttr: ga = '_'+ga self.__dict__[ga] = self.__dict__[ga]['_savedGeom'] del self.__dict__['_savedGeom'] self._geom() def _geom(self): self._x2 = self._x1 + self._width self._y2 = self._y1 + self._height #efficiency self._y1p = self._y1 + self._bottomPadding #work out the available space self._aW = self._x2 - self._x1 - self._leftPadding - self._rightPadding self._aH = self._y2 - self._y1p - self._topPadding def _reset(self): self._restoreGeom() #drawing starts at top left self._x = self._x1 + self._leftPadding self._y = self._y2 - self._topPadding self._atTop = 1 self._prevASpace = 0 # these two should NOT be set on a frame. # they are used when Indenter flowables want # to adjust edges e.g. to do nested lists self._leftExtraIndent = 0.0 self._rightExtraIndent = 0.0 def _getAvailableWidth(self): return self._aW - self._leftExtraIndent - self._rightExtraIndent def _add(self, flowable, canv, trySplit=0): """ Draws the flowable at the current position. Returns 1 if successful, 0 if it would not fit. Raises a LayoutError if the object is too wide, or if it is too high for a totally empty frame, to avoid infinite loops""" flowable._frame = self flowable.canv = canv #so they can use stringWidth etc try: if getattr(flowable,'frameAction',None): flowable.frameAction(self) return 1 y = self._y p = self._y1p s = 0 aW = self._getAvailableWidth() zeroSize = getattr(flowable,'_ZEROSIZE',False) if not self._atTop: s =flowable.getSpaceBefore() if self._oASpace: if getattr(flowable,'_SPACETRANSFER',False) or zeroSize: s = self._prevASpace s = max(s-self._prevASpace,0) h = y - p - s if h>0 or zeroSize: w, h = flowable.wrap(aW, h) else: return 0 h += s y -= h if y < p-_FUZZ: if not rl_config.allowTableBoundsErrors and ((h>self._aH or w>aW) and not trySplit): from reportlab.platypus.doctemplate import LayoutError raise LayoutError("Flowable %s (%sx%s points) too large for frame (%sx%s points)." % ( flowable.__class__, w,h, aW,self._aH)) return 0 else: #now we can draw it, and update the current point. s = flowable.getSpaceAfter() fbg = getattr(self,'_frameBGs',None) if fbg: fbgl, fbgr, fbgc = fbg[-1] fbw = self._width-fbgl-fbgr fbh = y + h + s fby = max(p,y-s) fbh = max(0,fbh-fby) if abs(fbw)>_FUZZ and abs(fbh)>_FUZZ: canv.saveState() canv.setFillColor(fbgc) canv.rect(self._x1+fbgl,fby,fbw,fbh,stroke=0,fill=1) canv.restoreState() flowable.drawOn(canv, self._x + self._leftExtraIndent, y, _sW=aW-w) flowable.canv=canv if self._debug: logger.debug('drew %s' % flowable.identity()) y -= s if self._oASpace: if getattr(flowable,'_SPACETRANSFER',False): s = self._prevASpace self._prevASpace = s if y!=self._y: self._atTop = 0 self._y = y return 1 finally: #sometimes canv/_frame aren't still on the flowable for a in ('canv', '_frame'): if hasattr(flowable,a): delattr(flowable,a) add = _add def split(self,flowable,canv): '''Ask the flowable to split using up the available space.''' y = self._y p = self._y1p s = 0 if not self._atTop: s = flowable.getSpaceBefore() if self._oASpace: s = max(s-self._prevASpace,0) flowable._frame = self #some flowables might need these flowable.canv = canv try: r = flowable.split(self._aW, y-p-s) finally: #sometimes canv/_frame aren't still on the flowable for a in ('canv', '_frame'): if hasattr(flowable,a): delattr(flowable,a) return r def drawBoundary(self,canv): "draw the frame boundary as a rectangle (primarily for debugging)." from reportlab.lib.colors import Color, toColor sb = self.showBoundary ss = isinstance(sb,(str,tuple,list)) or isinstance(sb,Color) w = -1 if ss: c = toColor(sb,self) ss = c is not self elif isinstance(sb,ShowBoundaryValue) and sb: c = toColor(sb.color,self) w = sb.width ss = c is not self if ss: canv.saveState() canv.setStrokeColor(c) if w>=0: canv.setLineWidth(w) canv.rect( self._x1, self._y1, self._x2 - self._x1, self._y2 - self._y1 ) if ss: canv.restoreState() def addFromList(self, drawlist, canv): """Consumes objects from the front of the list until the frame is full. If it cannot fit one object, raises an exception.""" if self._debug: logger.debug("enter Frame.addFromlist() for frame %s" % self.id) if self.showBoundary: self.drawBoundary(canv) while len(drawlist) > 0: head = drawlist[0] if self.add(head,canv,trySplit=0): del drawlist[0] else: #leave it in the list for later break def add_generated_content(self,*C): self.__dict__.setdefault('_generated_content',[]).extend(C) def _aSpaceString(self): return '(%s x %s%s)' % (self._getAvailableWidth(),self._aH,self._atTop and '*' or '') reportlab-3.3.0/src/reportlab/platypus/para.py0000664000175000017500000026600412351054366021017 0ustar rptlabrptlab"""new experimental paragraph implementation Intended to allow support for paragraphs in paragraphs, hotlinks, embedded flowables, and underlining. The main entry point is the function def Paragraph(text, style, bulletText=None, frags=None) Which is intended to be plug compatible with the "usual" platypus paragraph except that it supports more functionality. In this implementation you may embed paragraphs inside paragraphs to create hierarchically organized documents. This implementation adds the following paragraph-like tags (which support the same attributes as paragraphs, for font specification, etc). - Unnumberred lists (ala html)::
    • first one
    • second one
    Also