Removed reportlab -> 2.0
Better report on timesheets bzr revid: pinky-f7e72504718a6a358300c76ac783f3eb37406dbd
This commit is contained in:
parent
44d01beae7
commit
23387d6a5c
|
@ -1244,6 +1244,7 @@ class orm(object):
|
|||
'relate': resrelate
|
||||
}
|
||||
|
||||
print result
|
||||
return result
|
||||
|
||||
# TODO: ameliorer avec NULL
|
||||
|
|
|
@ -37,7 +37,7 @@ import os
|
|||
# Change this to UTF-8 if you plan tu use Reportlab's UTF-8 support
|
||||
#
|
||||
# reportlab use "code page 1252" encoding by default. cfr reportlab user guide p.46
|
||||
encoding = 'cp1252'
|
||||
encoding = 'utf-8'
|
||||
|
||||
def str2xml(s):
|
||||
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/__init__.py
|
||||
__version__=''' $Id$ '''
|
||||
__doc__="""The Reportlab PDF generation library."""
|
||||
Version = "1.20"
|
||||
|
||||
def getStory(context):
|
||||
if context.target == 'UserGuide':
|
||||
# parse some local file
|
||||
import os
|
||||
myDir = os.path.split(__file__)[0]
|
||||
import yaml
|
||||
return yaml.parseFile(myDir + os.sep + 'mydocs.yaml')
|
||||
else:
|
||||
# this signals that it should revert to default processing
|
||||
return None
|
||||
|
||||
|
||||
def getMonitor():
|
||||
import reportlab.monitor
|
||||
mon = reportlab.monitor.ReportLabToolkitMonitor()
|
||||
return mon
|
|
@ -1,25 +0,0 @@
|
|||
This directory is intended to act as a placeholder for extending ReportLab
|
||||
with extra extensions. It has been packagised with an empty __init__.py.
|
||||
|
||||
So a typical extender should add his package extension as
|
||||
|
||||
reportlab/extensions/great_extension/__init__.py
|
||||
/dingo.py
|
||||
/etc etc
|
||||
|
||||
and single modules as
|
||||
|
||||
reportlab/extensions/my_module.py
|
||||
|
||||
Then client code can do
|
||||
|
||||
from reportlab.extensions.great_extension import dingo
|
||||
|
||||
if you extend with just a single module it might be simpler to add that
|
||||
into extensions so that you could do
|
||||
|
||||
from reportlab.extensions import my_module.
|
||||
|
||||
|
||||
ReportLab can take no responsibility for name clashes and problems caused by
|
||||
modules and packages in reportlab/extensions.
|
|
@ -1,7 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/extensions/__init__.py
|
||||
__version__=''' $Id$ '''
|
||||
__doc__="""
|
||||
"""
|
||||
# No usable content. Do not add to this file!
|
|
@ -1,8 +0,0 @@
|
|||
This directory is a convenient place to put
|
||||
fonts where Reportlab will find them. If running
|
||||
in a server environment, this is a good place to
|
||||
put Type 1 fonts needed by your application. If
|
||||
in a desktop environment, you might prefer to add
|
||||
your font directories to the T1SearchPath in
|
||||
reportlab/rl_config.py instead.
|
||||
|
Binary file not shown.
Binary file not shown.
|
@ -1,471 +0,0 @@
|
|||
StartFontMetrics 2.0
|
||||
Comment Generated by pfaedit
|
||||
Comment Creation Date: Thu Jun 26 18:26:51 2003
|
||||
FontName Wargames
|
||||
FullName Wargames
|
||||
FamilyName Wargames
|
||||
Weight Regular
|
||||
Notice (Copyright (c) Dustin Norlander, 2002.
|
||||
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version.)
|
||||
ItalicAngle 0
|
||||
IsFixedPitch false
|
||||
UnderlinePosition -133
|
||||
UnderlineThickness 20
|
||||
Version Version 1.0; 2001; initial release
|
||||
EncodingScheme ISO10646-1
|
||||
FontBBox 0 -280 860 800
|
||||
CapHeight 799
|
||||
XHeight 521
|
||||
Ascender 799
|
||||
Descender -279
|
||||
StartCharMetrics 87
|
||||
C 0 ; WX 500 ; N .notdef ; B 62 0 438 800 ;
|
||||
C 32 ; WX 500 ; N space ; B 0 0 0 0 ;
|
||||
C 33 ; WX 208 ; N exclam ; B 40 0 145 800 ;
|
||||
C 34 ; WX 390 ; N quotedbl ; B 60 556 338 800 ;
|
||||
C 39 ; WX 229 ; N quotesingle ; B 49 556 154 800 ;
|
||||
C 40 ; WX 365 ; N parenleft ; B 40 0 284 800 ;
|
||||
C 41 ; WX 361 ; N parenright ; B 49 0 293 800 ;
|
||||
C 43 ; WX 490 ; N plus ; B 29 277 414 662 ;
|
||||
C 44 ; WX 329 ; N comma ; B 0 -140 244 244 ;
|
||||
C 45 ; WX 484 ; N hyphen ; B 29 277 414 383 ;
|
||||
C 46 ; WX 338 ; N period ; B 34 -4 277 239 ;
|
||||
C 47 ; WX 507 ; N slash ; B 49 0 434 800 ;
|
||||
C 48 ; WX 604 ; N zero ; B 0 0 522 800 ;
|
||||
C 49 ; WX 330 ; N one ; B 0 0 244 800 ;
|
||||
C 50 ; WX 600 ; N two ; B 0 0 522 800 ;
|
||||
C 51 ; WX 597 ; N three ; B 0 0 522 800 ;
|
||||
C 52 ; WX 597 ; N four ; B 0 0 522 800 ;
|
||||
C 53 ; WX 594 ; N five ; B 0 0 522 800 ;
|
||||
C 54 ; WX 600 ; N six ; B 0 0 522 800 ;
|
||||
C 55 ; WX 594 ; N seven ; B 0 0 522 800 ;
|
||||
C 56 ; WX 597 ; N eight ; B 0 0 522 800 ;
|
||||
C 57 ; WX 603 ; N nine ; B 0 0 522 800 ;
|
||||
C 58 ; WX 233 ; N colon ; B 60 167 165 551 ;
|
||||
C 59 ; WX 228 ; N semicolon ; B 40 -14 146 512 ;
|
||||
C 61 ; WX 547 ; N equal ; B 80 277 463 575 ;
|
||||
C 63 ; WX 501 ; N question ; B 40 0 423 730 ;
|
||||
C 64 ; WX 945 ; N at ; B 60 0 860 662 ;
|
||||
C 65 ; WX 734 ; N A ; B 0 0 662 800 ;
|
||||
C 66 ; WX 730 ; N B ; B 0 0 662 800 ;
|
||||
C 67 ; WX 705 ; N C ; B 0 0 662 800 ;
|
||||
C 68 ; WX 732 ; N D ; B 0 0 662 800 ;
|
||||
C 69 ; WX 576 ; N E ; B 0 0 522 800 ;
|
||||
C 70 ; WX 568 ; N F ; B 0 0 522 800 ;
|
||||
C 71 ; WX 716 ; N G ; B 0 0 662 800 ;
|
||||
C 72 ; WX 734 ; N H ; B 0 0 662 800 ;
|
||||
C 73 ; WX 326 ; N I ; B 0 0 244 800 ;
|
||||
C 74 ; WX 598 ; N J ; B 0 0 522 800 ;
|
||||
C 75 ; WX 733 ; N K ; B 0 0 662 800 ;
|
||||
C 76 ; WX 692 ; N L ; B 0 0 662 800 ;
|
||||
C 77 ; WX 872 ; N M ; B 0 0 800 800 ;
|
||||
C 78 ; WX 734 ; N N ; B 0 0 662 800 ;
|
||||
C 79 ; WX 732 ; N O ; B 0 0 662 800 ;
|
||||
C 80 ; WX 701 ; N P ; B 0 0 662 800 ;
|
||||
C 81 ; WX 734 ; N Q ; B 0 0 662 800 ;
|
||||
C 82 ; WX 726 ; N R ; B 0 0 662 800 ;
|
||||
C 83 ; WX 591 ; N S ; B 0 0 522 800 ;
|
||||
C 84 ; WX 632 ; N T ; B 0 0 662 800 ;
|
||||
C 85 ; WX 733 ; N U ; B 0 0 662 800 ;
|
||||
C 86 ; WX 725 ; N V ; B 0 0 662 800 ;
|
||||
C 87 ; WX 872 ; N W ; B 0 0 800 800 ;
|
||||
C 88 ; WX 732 ; N X ; B 0 0 662 800 ;
|
||||
C 89 ; WX 617 ; N Y ; B 0 0 662 800 ;
|
||||
C 90 ; WX 575 ; N Z ; B 0 0 522 800 ;
|
||||
C 91 ; WX 309 ; N bracketleft ; B 0 0 244 800 ;
|
||||
C 92 ; WX 522 ; N backslash ; B 49 0 434 800 ;
|
||||
C 93 ; WX 373 ; N bracketright ; B 49 0 293 800 ;
|
||||
C 97 ; WX 605 ; N a ; B 0 0 522 522 ;
|
||||
C 98 ; WX 589 ; N b ; B 0 0 522 800 ;
|
||||
C 99 ; WX 588 ; N c ; B 0 0 522 522 ;
|
||||
C 100 ; WX 599 ; N d ; B 0 0 522 800 ;
|
||||
C 101 ; WX 590 ; N e ; B 0 0 522 522 ;
|
||||
C 102 ; WX 445 ; N f ; B 0 0 383 800 ;
|
||||
C 103 ; WX 599 ; N g ; B 0 -280 522 521 ;
|
||||
C 104 ; WX 590 ; N h ; B 0 0 522 800 ;
|
||||
C 105 ; WX 327 ; N i ; B 0 0 244 713 ;
|
||||
C 106 ; WX 326 ; N j ; B 0 -280 244 521 ;
|
||||
C 107 ; WX 590 ; N k ; B 0 0 522 800 ;
|
||||
C 108 ; WX 326 ; N l ; B 0 0 244 800 ;
|
||||
C 109 ; WX 743 ; N m ; B 0 0 662 522 ;
|
||||
C 110 ; WX 604 ; N n ; B 0 0 522 522 ;
|
||||
C 111 ; WX 602 ; N o ; B 0 0 522 522 ;
|
||||
C 112 ; WX 594 ; N p ; B 0 -280 522 521 ;
|
||||
C 113 ; WX 617 ; N q ; B 0 -280 662 521 ;
|
||||
C 114 ; WX 575 ; N r ; B 0 0 522 522 ;
|
||||
C 115 ; WX 458 ; N s ; B 0 0 383 522 ;
|
||||
C 116 ; WX 448 ; N t ; B 0 0 383 662 ;
|
||||
C 117 ; WX 605 ; N u ; B 0 0 522 522 ;
|
||||
C 118 ; WX 596 ; N v ; B 0 0 522 522 ;
|
||||
C 119 ; WX 743 ; N w ; B 0 0 662 522 ;
|
||||
C 120 ; WX 588 ; N x ; B 0 0 522 522 ;
|
||||
C 121 ; WX 599 ; N y ; B 0 -280 522 521 ;
|
||||
C 122 ; WX 584 ; N z ; B 0 0 522 522 ;
|
||||
C 124 ; WX 230 ; N bar ; B 49 139 154 662 ;
|
||||
C 160 ; WX 500 ; N uni00A0 ; B 0 0 0 0 ;
|
||||
C -1 ; WX 484 ; N uni2010 ; B 29 277 414 383 ;
|
||||
C -1 ; WX 0 ; N .null ; B 0 0 0 0 ;
|
||||
C -1 ; WX 500 ; N nonmarkingreturn ; B 0 0 0 0 ;
|
||||
EndCharMetrics
|
||||
StartKernData
|
||||
StartKernPairs 357
|
||||
KPX A Y -104
|
||||
KPX A U 25
|
||||
KPX A T -104
|
||||
KPX A Q 23
|
||||
KPX A O 23
|
||||
KPX A I 24
|
||||
KPX A G 23
|
||||
KPX A C 23
|
||||
KPX B Z 21
|
||||
KPX B U 28
|
||||
KPX B Q 27
|
||||
KPX B O 27
|
||||
KPX B N 20
|
||||
KPX B L 20
|
||||
KPX B K 20
|
||||
KPX B I 27
|
||||
KPX B H 20
|
||||
KPX B G 27
|
||||
KPX B F 21
|
||||
KPX B E 21
|
||||
KPX B C 27
|
||||
KPX C Z 21
|
||||
KPX C U 28
|
||||
KPX C Q 27
|
||||
KPX C O 27
|
||||
KPX C N 20
|
||||
KPX C L 20
|
||||
KPX C K 20
|
||||
KPX C I 27
|
||||
KPX C H 20
|
||||
KPX C G 27
|
||||
KPX C F 21
|
||||
KPX C E 21
|
||||
KPX C C 27
|
||||
KPX D Z 21
|
||||
KPX D U 28
|
||||
KPX D Q 27
|
||||
KPX D O 27
|
||||
KPX D N 20
|
||||
KPX D L 20
|
||||
KPX D K 20
|
||||
KPX D I 27
|
||||
KPX D H 20
|
||||
KPX D G 27
|
||||
KPX D F 21
|
||||
KPX D E 21
|
||||
KPX D C 27
|
||||
KPX E Z 21
|
||||
KPX E U 28
|
||||
KPX E Q 27
|
||||
KPX E O 27
|
||||
KPX E N 20
|
||||
KPX E L 20
|
||||
KPX E K 20
|
||||
KPX E I 27
|
||||
KPX E H 20
|
||||
KPX E G 27
|
||||
KPX E F 21
|
||||
KPX E E 21
|
||||
KPX E C 27
|
||||
KPX F Z 21
|
||||
KPX F U 28
|
||||
KPX F Q 27
|
||||
KPX F O 27
|
||||
KPX F N 20
|
||||
KPX F L 20
|
||||
KPX F K 20
|
||||
KPX F J -234
|
||||
KPX F I 27
|
||||
KPX F H 20
|
||||
KPX F G 27
|
||||
KPX F F 21
|
||||
KPX F E 21
|
||||
KPX F C 27
|
||||
KPX G Z 21
|
||||
KPX G U 28
|
||||
KPX G Q 27
|
||||
KPX G O 27
|
||||
KPX G N 20
|
||||
KPX G L 20
|
||||
KPX G K 20
|
||||
KPX G I 27
|
||||
KPX G H 20
|
||||
KPX G G 27
|
||||
KPX G F 21
|
||||
KPX G E 21
|
||||
KPX G C 27
|
||||
KPX H Z 21
|
||||
KPX H U 28
|
||||
KPX H Q 27
|
||||
KPX H O 27
|
||||
KPX H N 20
|
||||
KPX H L 20
|
||||
KPX H K 20
|
||||
KPX H I 27
|
||||
KPX H H 20
|
||||
KPX H G 27
|
||||
KPX H F 21
|
||||
KPX H E 21
|
||||
KPX H C 27
|
||||
KPX I Z 21
|
||||
KPX I U 28
|
||||
KPX I Q 27
|
||||
KPX I O 27
|
||||
KPX I N 20
|
||||
KPX I L 20
|
||||
KPX I K 20
|
||||
KPX I I 27
|
||||
KPX I H 20
|
||||
KPX I G 27
|
||||
KPX I F 21
|
||||
KPX I E 21
|
||||
KPX I C 27
|
||||
KPX J Z 21
|
||||
KPX J U 28
|
||||
KPX J Q 27
|
||||
KPX J O 27
|
||||
KPX J N 20
|
||||
KPX J L 20
|
||||
KPX J K 20
|
||||
KPX J I 27
|
||||
KPX J H 20
|
||||
KPX J G 27
|
||||
KPX J F 21
|
||||
KPX J E 21
|
||||
KPX J C 27
|
||||
KPX K Z 21
|
||||
KPX K U 28
|
||||
KPX K Q 27
|
||||
KPX K O 27
|
||||
KPX K N 20
|
||||
KPX K L 20
|
||||
KPX K K 20
|
||||
KPX K I 27
|
||||
KPX K H 20
|
||||
KPX K G 27
|
||||
KPX K F 21
|
||||
KPX K E 21
|
||||
KPX K C 27
|
||||
KPX L Y -104
|
||||
KPX L V -95
|
||||
KPX L U 25
|
||||
KPX L T -104
|
||||
KPX L Q 23
|
||||
KPX L O 23
|
||||
KPX L I 24
|
||||
KPX L G 23
|
||||
KPX L C 23
|
||||
KPX M Z 21
|
||||
KPX M U 28
|
||||
KPX M Q 27
|
||||
KPX M O 27
|
||||
KPX M N 20
|
||||
KPX M L 20
|
||||
KPX M K 20
|
||||
KPX M I 27
|
||||
KPX M H 20
|
||||
KPX M G 27
|
||||
KPX M F 21
|
||||
KPX M E 21
|
||||
KPX M C 27
|
||||
KPX N Z 21
|
||||
KPX N U 28
|
||||
KPX N Q 27
|
||||
KPX N O 27
|
||||
KPX N N 20
|
||||
KPX N L 20
|
||||
KPX N K 20
|
||||
KPX N I 27
|
||||
KPX N H 20
|
||||
KPX N G 27
|
||||
KPX N F 21
|
||||
KPX N E 21
|
||||
KPX N C 27
|
||||
KPX O Z 21
|
||||
KPX O U 28
|
||||
KPX O Q 27
|
||||
KPX O O 27
|
||||
KPX O N 20
|
||||
KPX O L 20
|
||||
KPX O K 20
|
||||
KPX O I 27
|
||||
KPX O H 20
|
||||
KPX O G 27
|
||||
KPX O F 21
|
||||
KPX O E 21
|
||||
KPX O C 27
|
||||
KPX P Z 75
|
||||
KPX P Y 63
|
||||
KPX P X 71
|
||||
KPX P V 73
|
||||
KPX P U 82
|
||||
KPX P T 63
|
||||
KPX P S 71
|
||||
KPX P Q 81
|
||||
KPX P O 81
|
||||
KPX P J -252
|
||||
KPX P G 81
|
||||
KPX P C 81
|
||||
KPX Q Z 21
|
||||
KPX Q U 28
|
||||
KPX Q Q 27
|
||||
KPX Q O 27
|
||||
KPX Q N 20
|
||||
KPX Q L 20
|
||||
KPX Q K 20
|
||||
KPX Q I 27
|
||||
KPX Q H 20
|
||||
KPX Q G 27
|
||||
KPX Q F 21
|
||||
KPX Q E 21
|
||||
KPX Q C 27
|
||||
KPX R Z 21
|
||||
KPX R U 28
|
||||
KPX R Q 27
|
||||
KPX R O 27
|
||||
KPX R N 20
|
||||
KPX R L 20
|
||||
KPX R K 20
|
||||
KPX R J -83
|
||||
KPX R I 27
|
||||
KPX R H 20
|
||||
KPX R G 27
|
||||
KPX R F 21
|
||||
KPX R E 21
|
||||
KPX R C 27
|
||||
KPX S Z 21
|
||||
KPX S U 28
|
||||
KPX S Q 27
|
||||
KPX S O 27
|
||||
KPX S N 20
|
||||
KPX S L 20
|
||||
KPX S K 20
|
||||
KPX S I 27
|
||||
KPX S H 20
|
||||
KPX S G 27
|
||||
KPX S F 21
|
||||
KPX S E 21
|
||||
KPX S C 27
|
||||
KPX T Z 75
|
||||
KPX T Y 64
|
||||
KPX T X 73
|
||||
KPX T W -40
|
||||
KPX T V 73
|
||||
KPX T U 83
|
||||
KPX T T 64
|
||||
KPX T S 73
|
||||
KPX T R -39
|
||||
KPX T Q 81
|
||||
KPX T P -39
|
||||
KPX T O 81
|
||||
KPX T N -37
|
||||
KPX T M -40
|
||||
KPX T L -37
|
||||
KPX T K -37
|
||||
KPX T J -181
|
||||
KPX T I -28
|
||||
KPX T H -37
|
||||
KPX T G 81
|
||||
KPX T F -36
|
||||
KPX T E -36
|
||||
KPX T D -39
|
||||
KPX T C 81
|
||||
KPX T B -39
|
||||
KPX T A -39
|
||||
KPX U Z 21
|
||||
KPX U U 28
|
||||
KPX U Q 27
|
||||
KPX U O 27
|
||||
KPX U N 20
|
||||
KPX U L 20
|
||||
KPX U K 20
|
||||
KPX U I 27
|
||||
KPX U H 20
|
||||
KPX U G 27
|
||||
KPX U F 21
|
||||
KPX U E 21
|
||||
KPX U C 27
|
||||
KPX V Z 21
|
||||
KPX V U 28
|
||||
KPX V Q 27
|
||||
KPX V O 27
|
||||
KPX V N 20
|
||||
KPX V L 20
|
||||
KPX V K 20
|
||||
KPX V J -124
|
||||
KPX V I 27
|
||||
KPX V H 20
|
||||
KPX V G 27
|
||||
KPX V F 21
|
||||
KPX V E 21
|
||||
KPX V C 27
|
||||
KPX W Z 21
|
||||
KPX W U 28
|
||||
KPX W Q 27
|
||||
KPX W O 27
|
||||
KPX W N 20
|
||||
KPX W L 20
|
||||
KPX W K 20
|
||||
KPX W I 27
|
||||
KPX W H 20
|
||||
KPX W G 27
|
||||
KPX W F 21
|
||||
KPX W E 21
|
||||
KPX W C 27
|
||||
KPX X Z 21
|
||||
KPX X U 28
|
||||
KPX X Q 27
|
||||
KPX X O 27
|
||||
KPX X N 20
|
||||
KPX X L 20
|
||||
KPX X K 20
|
||||
KPX X I 27
|
||||
KPX X H 20
|
||||
KPX X G 27
|
||||
KPX X F 21
|
||||
KPX X E 21
|
||||
KPX X C 27
|
||||
KPX Y Z 80
|
||||
KPX Y Y 68
|
||||
KPX Y X 77
|
||||
KPX Y W -36
|
||||
KPX Y V 78
|
||||
KPX Y U 86
|
||||
KPX Y T 68
|
||||
KPX Y S 77
|
||||
KPX Y R -34
|
||||
KPX Y Q 85
|
||||
KPX Y P -34
|
||||
KPX Y O 85
|
||||
KPX Y N -33
|
||||
KPX Y M -36
|
||||
KPX Y L -33
|
||||
KPX Y K -33
|
||||
KPX Y J -176
|
||||
KPX Y I -24
|
||||
KPX Y H -33
|
||||
KPX Y G 85
|
||||
KPX Y F -32
|
||||
KPX Y E -32
|
||||
KPX Y D -34
|
||||
KPX Y C 85
|
||||
KPX Y B -34
|
||||
KPX Y A -34
|
||||
KPX Z Z 21
|
||||
KPX Z U 28
|
||||
KPX Z Q 27
|
||||
KPX Z O 27
|
||||
KPX Z N 20
|
||||
KPX Z L 20
|
||||
KPX Z K 20
|
||||
KPX Z I 27
|
||||
KPX Z H 20
|
||||
KPX Z G 27
|
||||
KPX Z F 21
|
||||
KPX Z E 21
|
||||
KPX Z C 27
|
||||
EndKernPairs
|
||||
EndKernData
|
||||
EndFontMetrics
|
Binary file not shown.
|
@ -1,4 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/__init__.py
|
||||
__version__=''' $Id$ '''
|
|
@ -1,4 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/__init__.py
|
||||
__version__=''' $Id$ '''
|
|
@ -1,92 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/areas.py
|
||||
"""This module defines a Area mixin classes
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
from reportlab.lib.validators import isNumber, isColor, isColorOrNone, isNoneOrShape
|
||||
from reportlab.graphics.widgetbase import Widget
|
||||
from reportlab.graphics.shapes import Rect, Group, Line, Polygon
|
||||
from reportlab.lib.attrmap import AttrMap, AttrMapValue
|
||||
|
||||
class PlotArea(Widget):
|
||||
"Abstract base class representing a chart's plot area, pretty unusable by itself."
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber, desc='X position of the lower-left corner of the chart.'),
|
||||
y = AttrMapValue(isNumber, desc='Y position of the lower-left corner of the chart.'),
|
||||
width = AttrMapValue(isNumber, desc='Width of the chart.'),
|
||||
height = AttrMapValue(isNumber, desc='Height of the chart.'),
|
||||
strokeColor = AttrMapValue(isColorOrNone, desc='Color of the plot area border.'),
|
||||
strokeWidth = AttrMapValue(isNumber, desc='Width plot area border.'),
|
||||
fillColor = AttrMapValue(isColorOrNone, desc='Color of the plot area interior.'),
|
||||
background = AttrMapValue(isNoneOrShape, desc='Handle to background object.'),
|
||||
debug = AttrMapValue(isNumber, desc='Used only for debugging.'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.x = 20
|
||||
self.y = 10
|
||||
self.height = 85
|
||||
self.width = 180
|
||||
self.strokeColor = None
|
||||
self.strokeWidth = 1
|
||||
self.fillColor = None
|
||||
self.background = None
|
||||
self.debug = 0
|
||||
|
||||
def makeBackground(self):
|
||||
if self.background is not None:
|
||||
BG = self.background
|
||||
if isinstance(BG,Group):
|
||||
g = BG
|
||||
for bg in g.contents:
|
||||
bg.x = self.x
|
||||
bg.y = self.y
|
||||
bg.width = self.width
|
||||
bg.height = self.height
|
||||
else:
|
||||
g = Group()
|
||||
if type(BG) not in (type(()),type([])): BG=(BG,)
|
||||
for bg in BG:
|
||||
bg.x = self.x
|
||||
bg.y = self.y
|
||||
bg.width = self.width
|
||||
bg.height = self.height
|
||||
g.add(bg)
|
||||
return g
|
||||
else:
|
||||
strokeColor,strokeWidth,fillColor=self.strokeColor, self.strokeWidth, self.fillColor
|
||||
if (strokeWidth and strokeColor) or fillColor:
|
||||
g = Group()
|
||||
_3d_dy = getattr(self,'_3d_dy',None)
|
||||
x = self.x
|
||||
y = self.y
|
||||
h = self.height
|
||||
w = self.width
|
||||
if _3d_dy is not None:
|
||||
_3d_dx = self._3d_dx
|
||||
if fillColor and not strokeColor:
|
||||
from reportlab.lib.colors import Blacker
|
||||
c = Blacker(fillColor, getattr(self,'_3d_blacken',0.7))
|
||||
else:
|
||||
c = strokeColor
|
||||
if not strokeWidth: strokeWidth = 0.5
|
||||
if fillColor or strokeColor or c:
|
||||
bg = Polygon([x,y,x,y+h,x+_3d_dx,y+h+_3d_dy,x+w+_3d_dx,y+h+_3d_dy,x+w+_3d_dx,y+_3d_dy,x+w,y],
|
||||
strokeColor=strokeColor or c or grey, strokeWidth=strokeWidth, fillColor=fillColor)
|
||||
g.add(bg)
|
||||
g.add(Line(x,y,x+_3d_dx,y+_3d_dy, strokeWidth=0.5, strokeColor=c))
|
||||
g.add(Line(x+_3d_dx,y+_3d_dy, x+_3d_dx,y+h+_3d_dy,strokeWidth=0.5, strokeColor=c))
|
||||
fc = Blacker(c, getattr(self,'_3d_blacken',0.8))
|
||||
g.add(Polygon([x,y,x+_3d_dx,y+_3d_dy,x+w+_3d_dx,y+_3d_dy,x+w,y],
|
||||
strokeColor=strokeColor or c or grey, strokeWidth=strokeWidth, fillColor=fc))
|
||||
bg = Line(x+_3d_dx,y+_3d_dy, x+w+_3d_dx,y+_3d_dy,strokeWidth=0.5, strokeColor=c)
|
||||
else:
|
||||
bg = None
|
||||
else:
|
||||
bg = Rect(x, y, w, h,
|
||||
strokeColor=strokeColor, strokeWidth=strokeWidth, fillColor=fillColor)
|
||||
if bg: g.add(bg)
|
||||
return g
|
||||
else:
|
||||
return None
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,165 +0,0 @@
|
|||
from reportlab.lib.colors import blue, _PCMYK_black
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from reportlab.graphics.shapes import Circle, Drawing, Group, Line, Rect, String
|
||||
from reportlab.graphics.widgetbase import Widget
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.lib.validators import *
|
||||
from reportlab.lib.units import cm
|
||||
from reportlab.pdfbase.pdfmetrics import getFont
|
||||
from reportlab.graphics.charts.lineplots import _maxWidth
|
||||
|
||||
class DotBox(Widget):
|
||||
"""Returns a dotbox widget."""
|
||||
|
||||
#Doesn't use TypedPropertyCollection for labels - this can be a later improvement
|
||||
_attrMap = AttrMap(
|
||||
xlabels = AttrMapValue(isNoneOrListOfNoneOrStrings,
|
||||
desc="List of text labels for boxes on left hand side"),
|
||||
ylabels = AttrMapValue(isNoneOrListOfNoneOrStrings,
|
||||
desc="Text label for second box on left hand side"),
|
||||
labelFontName = AttrMapValue(isString,
|
||||
desc="Name of font used for the labels"),
|
||||
labelFontSize = AttrMapValue(isNumber,
|
||||
desc="Size of font used for the labels"),
|
||||
labelOffset = AttrMapValue(isNumber,
|
||||
desc="Space between label text and grid edge"),
|
||||
strokeWidth = AttrMapValue(isNumber,
|
||||
desc='Width of the grid and dot outline'),
|
||||
gridDivWidth = AttrMapValue(isNumber,
|
||||
desc="Width of each 'box'"),
|
||||
gridColor = AttrMapValue(isColor,
|
||||
desc='Colour for the box and gridding'),
|
||||
dotDiameter = AttrMapValue(isNumber,
|
||||
desc="Diameter of the circle used for the 'dot'"),
|
||||
dotColor = AttrMapValue(isColor,
|
||||
desc='Colour of the circle on the box'),
|
||||
dotXPosition = AttrMapValue(isNumber,
|
||||
desc='X Position of the circle'),
|
||||
dotYPosition = AttrMapValue(isNumber,
|
||||
desc='X Position of the circle'),
|
||||
x = AttrMapValue(isNumber,
|
||||
desc='X Position of dotbox'),
|
||||
y = AttrMapValue(isNumber,
|
||||
desc='Y Position of dotbox'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.xlabels=["Value", "Blend", "Growth"]
|
||||
self.ylabels=["Small", "Medium", "Large"]
|
||||
self.labelFontName = "Helvetica"
|
||||
self.labelFontSize = 6
|
||||
self.labelOffset = 5
|
||||
self.strokeWidth = 0.5
|
||||
self.gridDivWidth=0.5*cm
|
||||
self.gridColor=colors.Color(25/255.0,77/255.0,135/255.0)
|
||||
self.dotDiameter=0.4*cm
|
||||
self.dotColor=colors.Color(232/255.0,224/255.0,119/255.0)
|
||||
self.dotXPosition = 1
|
||||
self.dotYPosition = 1
|
||||
self.x = 30
|
||||
self.y = 5
|
||||
|
||||
|
||||
def _getDrawingDimensions(self):
|
||||
leftPadding=rightPadding=topPadding=bottomPadding=5
|
||||
#find width of grid
|
||||
tx=len(self.xlabels)*self.gridDivWidth
|
||||
#add padding (and offset)
|
||||
tx=tx+leftPadding+rightPadding+self.labelOffset
|
||||
#add in maximum width of text
|
||||
tx=tx+_maxWidth(self.xlabels, self.labelFontName, self.labelFontSize)
|
||||
#find height of grid
|
||||
ty=len(self.ylabels)*self.gridDivWidth
|
||||
#add padding (and offset)
|
||||
ty=ty+topPadding+bottomPadding+self.labelOffset
|
||||
#add in maximum width of text
|
||||
ty=ty+_maxWidth(self.ylabels, self.labelFontName, self.labelFontSize)
|
||||
#print (tx, ty)
|
||||
return (tx,ty)
|
||||
|
||||
def demo(self,drawing=None):
|
||||
if not drawing:
|
||||
tx,ty=self._getDrawingDimensions()
|
||||
drawing = Drawing(tx,ty)
|
||||
drawing.add(self.draw())
|
||||
return drawing
|
||||
|
||||
def draw(self):
|
||||
g = Group()
|
||||
|
||||
#box
|
||||
g.add(Rect(self.x,self.y,len(self.xlabels)*self.gridDivWidth,len(self.ylabels)*self.gridDivWidth,
|
||||
strokeColor=self.gridColor,
|
||||
strokeWidth=self.strokeWidth,
|
||||
fillColor=None))
|
||||
|
||||
#internal gridding
|
||||
for f in range (1,len(self.ylabels)):
|
||||
#horizontal
|
||||
g.add(Line(strokeColor=self.gridColor,
|
||||
strokeWidth=self.strokeWidth,
|
||||
x1 = self.x,
|
||||
y1 = self.y+f*self.gridDivWidth,
|
||||
x2 = self.x+len(self.xlabels)*self.gridDivWidth,
|
||||
y2 = self.y+f*self.gridDivWidth))
|
||||
for f in range (1,len(self.xlabels)):
|
||||
#vertical
|
||||
g.add(Line(strokeColor=self.gridColor,
|
||||
strokeWidth=self.strokeWidth,
|
||||
x1 = self.x+f*self.gridDivWidth,
|
||||
y1 = self.y,
|
||||
x2 = self.x+f*self.gridDivWidth,
|
||||
y2 = self.y+len(self.ylabels)*self.gridDivWidth))
|
||||
|
||||
# draw the 'dot'
|
||||
g.add(Circle(strokeColor=self.gridColor,
|
||||
strokeWidth=self.strokeWidth,
|
||||
fillColor=self.dotColor,
|
||||
cx = self.x+(self.dotXPosition*self.gridDivWidth),
|
||||
cy = self.y+(self.dotYPosition*self.gridDivWidth),
|
||||
r = self.dotDiameter/2.0))
|
||||
|
||||
#used for centering y-labels (below)
|
||||
ascent=getFont(self.labelFontName).face.ascent
|
||||
if ascent==0:
|
||||
ascent=0.718 # default (from helvetica)
|
||||
ascent=ascent*self.labelFontSize # normalize
|
||||
|
||||
#do y-labels
|
||||
if self.ylabels != None:
|
||||
for f in range (len(self.ylabels)-1,-1,-1):
|
||||
if self.ylabels[f]!= None:
|
||||
g.add(String(strokeColor=self.gridColor,
|
||||
text = self.ylabels[f],
|
||||
fontName = self.labelFontName,
|
||||
fontSize = self.labelFontSize,
|
||||
fillColor=_PCMYK_black,
|
||||
x = self.x-self.labelOffset,
|
||||
y = self.y+(f*self.gridDivWidth+(self.gridDivWidth-ascent)/2.0),
|
||||
textAnchor = 'end'))
|
||||
|
||||
#do x-labels
|
||||
if self.xlabels != None:
|
||||
for f in range (0,len(self.xlabels)):
|
||||
if self.xlabels[f]!= None:
|
||||
l=Label()
|
||||
l.x=self.x+(f*self.gridDivWidth)+(self.gridDivWidth+ascent)/2.0
|
||||
l.y=self.y+(len(self.ylabels)*self.gridDivWidth)+self.labelOffset
|
||||
l.angle=90
|
||||
l.textAnchor='start'
|
||||
l.fontName = self.labelFontName
|
||||
l.fontSize = self.labelFontSize
|
||||
l.fillColor = _PCMYK_black
|
||||
l.setText(self.xlabels[f])
|
||||
l.boxAnchor = 'sw'
|
||||
l.draw()
|
||||
g.add(l)
|
||||
|
||||
return g
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
d = DotBox()
|
||||
d.demo().save(fnRoot="dotbox")
|
|
@ -1,372 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/doughnut.py
|
||||
# doughnut chart
|
||||
|
||||
"""Doughnut chart
|
||||
|
||||
Produces a circular chart like the doughnut charts produced by Excel.
|
||||
Can handle multiple series (which produce concentric 'rings' in the chart).
|
||||
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
import copy
|
||||
from math import sin, cos, pi
|
||||
from types import ListType, TupleType
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.validators import isColor, isNumber, isListOfNumbersOrNone,\
|
||||
isListOfNumbers, isColorOrNone, isString,\
|
||||
isListOfStringsOrNone, OneOf, SequenceOf,\
|
||||
isBoolean, isListOfColors,\
|
||||
isNoneOrListOfNoneOrStrings,\
|
||||
isNoneOrListOfNoneOrNumbers,\
|
||||
isNumberOrNone
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.graphics.shapes import Group, Drawing, Line, Rect, Polygon, Ellipse, \
|
||||
Wedge, String, SolidShape, UserNode, STATE_DEFAULTS
|
||||
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
|
||||
from reportlab.graphics.charts.areas import PlotArea
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from reportlab.graphics.widgets.markers import Marker
|
||||
|
||||
class SectorProperties(PropHolder):
|
||||
"""This holds descriptive information about the sectors in a doughnut chart.
|
||||
|
||||
It is not to be confused with the 'sector itself'; this just holds
|
||||
a recipe for how to format one, and does not allow you to hack the
|
||||
angles. It can format a genuine Sector object for you with its
|
||||
format method.
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(
|
||||
strokeWidth = AttrMapValue(isNumber),
|
||||
fillColor = AttrMapValue(isColorOrNone),
|
||||
strokeColor = AttrMapValue(isColorOrNone),
|
||||
strokeDashArray = AttrMapValue(isListOfNumbersOrNone),
|
||||
popout = AttrMapValue(isNumber),
|
||||
fontName = AttrMapValue(isString),
|
||||
fontSize = AttrMapValue(isNumber),
|
||||
fontColor = AttrMapValue(isColorOrNone),
|
||||
labelRadius = AttrMapValue(isNumber),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.strokeWidth = 0
|
||||
self.fillColor = None
|
||||
self.strokeColor = STATE_DEFAULTS["strokeColor"]
|
||||
self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"]
|
||||
self.popout = 0
|
||||
self.fontName = STATE_DEFAULTS["fontName"]
|
||||
self.fontSize = STATE_DEFAULTS["fontSize"]
|
||||
self.fontColor = STATE_DEFAULTS["fillColor"]
|
||||
self.labelRadius = 1.2
|
||||
|
||||
|
||||
class Doughnut(Widget):
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber, desc='X position of the chart within its container.'),
|
||||
y = AttrMapValue(isNumber, desc='Y position of the chart within its container.'),
|
||||
width = AttrMapValue(isNumber, desc='width of doughnut bounding box. Need not be same as width.'),
|
||||
height = AttrMapValue(isNumber, desc='height of doughnut bounding box. Need not be same as height.'),
|
||||
data = AttrMapValue(None, desc='list of numbers defining sector sizes; need not sum to 1'),
|
||||
labels = AttrMapValue(isListOfStringsOrNone, desc="optional list of labels to use for each data point"),
|
||||
startAngle = AttrMapValue(isNumber, desc="angle of first slice; like the compass, 0 is due North"),
|
||||
direction = AttrMapValue(OneOf('clockwise', 'anticlockwise'), desc="'clockwise' or 'anticlockwise'"),
|
||||
slices = AttrMapValue(None, desc="collection of sector descriptor objects"),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.width = 100
|
||||
self.height = 100
|
||||
self.data = [1,1]
|
||||
self.labels = None # or list of strings
|
||||
self.startAngle = 90
|
||||
self.direction = "clockwise"
|
||||
|
||||
self.slices = TypedPropertyCollection(SectorProperties)
|
||||
self.slices[0].fillColor = colors.darkcyan
|
||||
self.slices[1].fillColor = colors.blueviolet
|
||||
self.slices[2].fillColor = colors.blue
|
||||
self.slices[3].fillColor = colors.cyan
|
||||
|
||||
def demo(self):
|
||||
d = Drawing(200, 100)
|
||||
|
||||
dn = Doughnut()
|
||||
dn.x = 50
|
||||
dn.y = 10
|
||||
dn.width = 100
|
||||
dn.height = 80
|
||||
dn.data = [10,20,30,40,50,60]
|
||||
dn.labels = ['a','b','c','d','e','f']
|
||||
|
||||
dn.slices.strokeWidth=0.5
|
||||
dn.slices[3].popout = 10
|
||||
dn.slices[3].strokeWidth = 2
|
||||
dn.slices[3].strokeDashArray = [2,2]
|
||||
dn.slices[3].labelRadius = 1.75
|
||||
dn.slices[3].fontColor = colors.red
|
||||
dn.slices[0].fillColor = colors.darkcyan
|
||||
dn.slices[1].fillColor = colors.blueviolet
|
||||
dn.slices[2].fillColor = colors.blue
|
||||
dn.slices[3].fillColor = colors.cyan
|
||||
dn.slices[4].fillColor = colors.aquamarine
|
||||
dn.slices[5].fillColor = colors.cadetblue
|
||||
dn.slices[6].fillColor = colors.lightcoral
|
||||
|
||||
d.add(dn)
|
||||
return d
|
||||
|
||||
def normalizeData(self, data=None):
|
||||
from operator import add
|
||||
sum = float(reduce(add,data,0))
|
||||
return abs(sum)>=1e-8 and map(lambda x,f=360./sum: f*x, data) or len(data)*[0]
|
||||
|
||||
def makeSectors(self):
|
||||
# normalize slice data
|
||||
if type(self.data) in (ListType, TupleType) and type(self.data[0]) in (ListType, TupleType):
|
||||
#it's a nested list, more than one sequence
|
||||
normData = []
|
||||
n = []
|
||||
for l in self.data:
|
||||
t = self.normalizeData(l)
|
||||
normData.append(t)
|
||||
n.append(len(t))
|
||||
else:
|
||||
normData = self.normalizeData(self.data)
|
||||
n = len(normData)
|
||||
|
||||
#labels
|
||||
if self.labels is None:
|
||||
labels = []
|
||||
if type(n) not in (ListType,TupleType):
|
||||
labels = [''] * n
|
||||
else:
|
||||
for m in n:
|
||||
labels = list(labels) + [''] * m
|
||||
else:
|
||||
labels = self.labels
|
||||
#there's no point in raising errors for less than enough errors if
|
||||
#we silently create all for the extreme case of no labels.
|
||||
if type(n) not in (ListType,TupleType):
|
||||
i = n-len(labels)
|
||||
if i>0:
|
||||
labels = list(labels) + [''] * i
|
||||
else:
|
||||
tlab = 0
|
||||
for m in n:
|
||||
tlab = tlab+m
|
||||
i = tlab-len(labels)
|
||||
if i>0:
|
||||
labels = list(labels) + [''] * i
|
||||
|
||||
xradius = self.width/2.0
|
||||
yradius = self.height/2.0
|
||||
centerx = self.x + xradius
|
||||
centery = self.y + yradius
|
||||
|
||||
if self.direction == "anticlockwise":
|
||||
whichWay = 1
|
||||
else:
|
||||
whichWay = -1
|
||||
|
||||
g = Group()
|
||||
i = 0
|
||||
sn = 0
|
||||
|
||||
startAngle = self.startAngle #% 360
|
||||
if type(self.data[0]) in (ListType, TupleType):
|
||||
#multi-series doughnut
|
||||
styleCount = len(self.slices)
|
||||
iradius = (self.height/5.0)/len(self.data)
|
||||
for series in normData:
|
||||
for angle in series:
|
||||
endAngle = (startAngle + (angle * whichWay)) #% 360
|
||||
if abs(startAngle-endAngle)>=1e-5:
|
||||
if startAngle < endAngle:
|
||||
a1 = startAngle
|
||||
a2 = endAngle
|
||||
else:
|
||||
a1 = endAngle
|
||||
a2 = startAngle
|
||||
|
||||
#if we didn't use %stylecount here we'd end up with the later sectors
|
||||
#all having the default style
|
||||
sectorStyle = self.slices[i%styleCount]
|
||||
|
||||
# is it a popout?
|
||||
cx, cy = centerx, centery
|
||||
if sectorStyle.popout != 0:
|
||||
# pop out the sector
|
||||
averageAngle = (a1+a2)/2.0
|
||||
aveAngleRadians = averageAngle * pi/180.0
|
||||
popdistance = sectorStyle.popout
|
||||
cx = centerx + popdistance * cos(aveAngleRadians)
|
||||
cy = centery + popdistance * sin(aveAngleRadians)
|
||||
|
||||
if type(n) in (ListType,TupleType):
|
||||
theSector = Wedge(cx, cy, xradius+(sn*iradius)-iradius, a1, a2, yradius=yradius+(sn*iradius)-iradius, radius1=yradius+(sn*iradius)-(2*iradius))
|
||||
else:
|
||||
theSector = Wedge(cx, cy, xradius, a1, a2, yradius=yradius, radius1=iradius)
|
||||
|
||||
theSector.fillColor = sectorStyle.fillColor
|
||||
theSector.strokeColor = sectorStyle.strokeColor
|
||||
theSector.strokeWidth = sectorStyle.strokeWidth
|
||||
theSector.strokeDashArray = sectorStyle.strokeDashArray
|
||||
|
||||
g.add(theSector)
|
||||
startAngle = endAngle
|
||||
|
||||
if labels[i] != "":
|
||||
averageAngle = (a1+a2)/2.0
|
||||
aveAngleRadians = averageAngle*pi/180.0
|
||||
labelRadius = sectorStyle.labelRadius
|
||||
labelX = centerx + (0.5 * self.width * cos(aveAngleRadians) * labelRadius)
|
||||
labelY = centery + (0.5 * self.height * sin(aveAngleRadians) * labelRadius)
|
||||
|
||||
theLabel = String(labelX, labelY, labels[i])
|
||||
theLabel.textAnchor = "middle"
|
||||
theLabel.fontSize = sectorStyle.fontSize
|
||||
theLabel.fontName = sectorStyle.fontName
|
||||
theLabel.fillColor = sectorStyle.fontColor
|
||||
g.add(theLabel)
|
||||
i = i + 1
|
||||
sn = sn + 1
|
||||
|
||||
else:
|
||||
#single series doughnut
|
||||
styleCount = len(self.slices)
|
||||
iradius = self.height/5.0
|
||||
for angle in normData:
|
||||
endAngle = (startAngle + (angle * whichWay)) #% 360
|
||||
if abs(startAngle-endAngle)>=1e-5:
|
||||
if startAngle < endAngle:
|
||||
a1 = startAngle
|
||||
a2 = endAngle
|
||||
else:
|
||||
a1 = endAngle
|
||||
a2 = startAngle
|
||||
|
||||
#if we didn't use %stylecount here we'd end up with the later sectors
|
||||
#all having the default style
|
||||
sectorStyle = self.slices[i%styleCount]
|
||||
|
||||
# is it a popout?
|
||||
cx, cy = centerx, centery
|
||||
if sectorStyle.popout != 0:
|
||||
# pop out the sector
|
||||
averageAngle = (a1+a2)/2.0
|
||||
aveAngleRadians = averageAngle * pi/180.0
|
||||
popdistance = sectorStyle.popout
|
||||
cx = centerx + popdistance * cos(aveAngleRadians)
|
||||
cy = centery + popdistance * sin(aveAngleRadians)
|
||||
|
||||
if n > 1:
|
||||
theSector = Wedge(cx, cy, xradius, a1, a2, yradius=yradius, radius1=iradius)
|
||||
elif n==1:
|
||||
theSector = Wedge(cx, cy, xradius, a1, a2, yradius=yradius, iradius=iradius)
|
||||
|
||||
theSector.fillColor = sectorStyle.fillColor
|
||||
theSector.strokeColor = sectorStyle.strokeColor
|
||||
theSector.strokeWidth = sectorStyle.strokeWidth
|
||||
theSector.strokeDashArray = sectorStyle.strokeDashArray
|
||||
|
||||
g.add(theSector)
|
||||
|
||||
# now draw a label
|
||||
if labels[i] != "":
|
||||
averageAngle = (a1+a2)/2.0
|
||||
aveAngleRadians = averageAngle*pi/180.0
|
||||
labelRadius = sectorStyle.labelRadius
|
||||
labelX = centerx + (0.5 * self.width * cos(aveAngleRadians) * labelRadius)
|
||||
labelY = centery + (0.5 * self.height * sin(aveAngleRadians) * labelRadius)
|
||||
|
||||
theLabel = String(labelX, labelY, labels[i])
|
||||
theLabel.textAnchor = "middle"
|
||||
theLabel.fontSize = sectorStyle.fontSize
|
||||
theLabel.fontName = sectorStyle.fontName
|
||||
theLabel.fillColor = sectorStyle.fontColor
|
||||
|
||||
g.add(theLabel)
|
||||
|
||||
startAngle = endAngle
|
||||
i = i + 1
|
||||
|
||||
return g
|
||||
|
||||
def draw(self):
|
||||
g = Group()
|
||||
g.add(self.makeSectors())
|
||||
return g
|
||||
|
||||
|
||||
def sample1():
|
||||
"Make up something from the individual Sectors"
|
||||
|
||||
d = Drawing(400, 400)
|
||||
g = Group()
|
||||
|
||||
s1 = Wedge(centerx=200, centery=200, radius=150, startangledegrees=0, endangledegrees=120, radius1=100)
|
||||
s1.fillColor=colors.red
|
||||
s1.strokeColor=None
|
||||
d.add(s1)
|
||||
s2 = Wedge(centerx=200, centery=200, radius=150, startangledegrees=120, endangledegrees=240, radius1=100)
|
||||
s2.fillColor=colors.green
|
||||
s2.strokeColor=None
|
||||
d.add(s2)
|
||||
s3 = Wedge(centerx=200, centery=200, radius=150, startangledegrees=240, endangledegrees=260, radius1=100)
|
||||
s3.fillColor=colors.blue
|
||||
s3.strokeColor=None
|
||||
d.add(s3)
|
||||
s4 = Wedge(centerx=200, centery=200, radius=150, startangledegrees=260, endangledegrees=360, radius1=100)
|
||||
s4.fillColor=colors.gray
|
||||
s4.strokeColor=None
|
||||
d.add(s4)
|
||||
|
||||
return d
|
||||
|
||||
def sample2():
|
||||
"Make a simple demo"
|
||||
|
||||
d = Drawing(400, 400)
|
||||
|
||||
dn = Doughnut()
|
||||
dn.x = 50
|
||||
dn.y = 50
|
||||
dn.width = 300
|
||||
dn.height = 300
|
||||
dn.data = [10,20,30,40,50,60]
|
||||
|
||||
d.add(dn)
|
||||
|
||||
return d
|
||||
|
||||
def sample3():
|
||||
"Make a more complex demo"
|
||||
|
||||
d = Drawing(400, 400)
|
||||
dn = Doughnut()
|
||||
dn.x = 50
|
||||
dn.y = 50
|
||||
dn.width = 300
|
||||
dn.height = 300
|
||||
dn.data = [[10,20,30,40,50,60], [10,20,30,40]]
|
||||
dn.labels = ['a','b','c','d','e','f']
|
||||
|
||||
d.add(dn)
|
||||
|
||||
return d
|
||||
|
||||
if __name__=='__main__':
|
||||
|
||||
from reportlab.graphics.renderPDF import drawToFile
|
||||
d = sample1()
|
||||
drawToFile(d, 'doughnut1.pdf')
|
||||
d = sample2()
|
||||
drawToFile(d, 'doughnut2.pdf')
|
||||
d = sample3()
|
||||
drawToFile(d, 'doughnut3.pdf')
|
|
@ -1,486 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/legends.py
|
||||
"""This will be a collection of legends to be used with charts.
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
import string, copy
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.validators import isNumber, OneOf, isString, isColorOrNone, isNumberOrNone, isListOfNumbersOrNone
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.pdfbase.pdfmetrics import stringWidth, getFont
|
||||
from reportlab.graphics.widgetbase import Widget
|
||||
from reportlab.graphics.shapes import Drawing, Group, String, Rect, Line, STATE_DEFAULTS
|
||||
|
||||
|
||||
class Legend(Widget):
|
||||
"""A simple legend containing rectangular swatches and strings.
|
||||
|
||||
The swatches are filled rectangles whenever the respective
|
||||
color object in 'colorNamePairs' is a subclass of Color in
|
||||
reportlab.lib.colors. Otherwise the object passed instead is
|
||||
assumed to have 'x', 'y', 'width' and 'height' attributes.
|
||||
A legend then tries to set them or catches any error. This
|
||||
lets you plug-in any widget you like as a replacement for
|
||||
the default rectangular swatches.
|
||||
|
||||
Strings can be nicely aligned left or right to the swatches.
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber, desc="x-coordinate of upper-left reference point"),
|
||||
y = AttrMapValue(isNumber, desc="y-coordinate of upper-left reference point"),
|
||||
deltax = AttrMapValue(isNumberOrNone, desc="x-distance between neighbouring swatches"),
|
||||
deltay = AttrMapValue(isNumberOrNone, desc="y-distance between neighbouring swatches"),
|
||||
dxTextSpace = AttrMapValue(isNumber, desc="Distance between swatch rectangle and text"),
|
||||
autoXPadding = AttrMapValue(isNumber, desc="x Padding between columns if deltax=None"),
|
||||
autoYPadding = AttrMapValue(isNumber, desc="y Padding between rows if deltay=None"),
|
||||
dx = AttrMapValue(isNumber, desc="Width of swatch rectangle"),
|
||||
dy = AttrMapValue(isNumber, desc="Height of swatch rectangle"),
|
||||
columnMaximum = AttrMapValue(isNumber, desc="Max. number of items per column"),
|
||||
alignment = AttrMapValue(OneOf("left", "right"), desc="Alignment of text with respect to swatches"),
|
||||
colorNamePairs = AttrMapValue(None, desc="List of color/name tuples (color can also be widget)"),
|
||||
fontName = AttrMapValue(isString, desc="Font name of the strings"),
|
||||
fontSize = AttrMapValue(isNumber, desc="Font size of the strings"),
|
||||
fillColor = AttrMapValue(isColorOrNone, desc=""),
|
||||
strokeColor = AttrMapValue(isColorOrNone, desc="Border color of the swatches"),
|
||||
strokeWidth = AttrMapValue(isNumber, desc="Width of the border color of the swatches"),
|
||||
callout = AttrMapValue(None, desc="a user callout(self,g,x,y,(color,text))"),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
# Upper-left reference point.
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
|
||||
# Alginment of text with respect to swatches.
|
||||
self.alignment = "left"
|
||||
|
||||
# x- and y-distances between neighbouring swatches.
|
||||
self.deltax = 75
|
||||
self.deltay = 20
|
||||
self.autoXPadding = 5
|
||||
self.autoYPadding = 2
|
||||
|
||||
# Size of swatch rectangle.
|
||||
self.dx = 10
|
||||
self.dy = 10
|
||||
|
||||
# Distance between swatch rectangle and text.
|
||||
self.dxTextSpace = 10
|
||||
|
||||
# Max. number of items per column.
|
||||
self.columnMaximum = 3
|
||||
|
||||
# Color/name pairs.
|
||||
self.colorNamePairs = [ (colors.red, "red"),
|
||||
(colors.blue, "blue"),
|
||||
(colors.green, "green"),
|
||||
(colors.pink, "pink"),
|
||||
(colors.yellow, "yellow") ]
|
||||
|
||||
# Font name and size of the labels.
|
||||
self.fontName = STATE_DEFAULTS['fontName']
|
||||
self.fontSize = STATE_DEFAULTS['fontSize']
|
||||
self.fillColor = STATE_DEFAULTS['fillColor']
|
||||
self.strokeColor = STATE_DEFAULTS['strokeColor']
|
||||
self.strokeWidth = STATE_DEFAULTS['strokeWidth']
|
||||
|
||||
def _calculateMaxWidth(self, colorNamePairs):
|
||||
"Calculate the maximum width of some given strings."
|
||||
m = 0
|
||||
for t in map(lambda p:str(p[1]),colorNamePairs):
|
||||
if t:
|
||||
for s in string.split(t,'\n'):
|
||||
m = max(m,stringWidth(s, self.fontName, self.fontSize))
|
||||
return m
|
||||
|
||||
|
||||
def _calcHeight(self):
|
||||
deltay = self.deltay
|
||||
dy = self.dy
|
||||
thisy = upperlefty = self.y - dy
|
||||
ascent=getFont(self.fontName).face.ascent/1000.
|
||||
if ascent==0: ascent=0.718 # default (from helvetica)
|
||||
leading = self.fontSize*1.2
|
||||
columnCount = 0
|
||||
count = 0
|
||||
lowy = upperlefty
|
||||
for unused, name in colorNamePairs:
|
||||
T = string.split(name and str(name) or '','\n')
|
||||
S = []
|
||||
# thisy+dy/2 = y+leading/2
|
||||
y = thisy+(dy-ascent)*0.5-leading
|
||||
newy = thisy-max(deltay,len(S)*leading)
|
||||
lowy = min(y,newy)
|
||||
if count == columnMaximum-1:
|
||||
count = 0
|
||||
thisy = upperlefty
|
||||
columnCount = columnCount + 1
|
||||
else:
|
||||
thisy = newy
|
||||
count = count+1
|
||||
return upperlefty - lowy
|
||||
|
||||
def draw(self):
|
||||
g = Group()
|
||||
colorNamePairs = self.colorNamePairs
|
||||
thisx = upperleftx = self.x
|
||||
thisy = upperlefty = self.y - self.dy
|
||||
dx, dy, alignment, columnMaximum = self.dx, self.dy, self.alignment, self.columnMaximum
|
||||
deltax, deltay, dxTextSpace = self.deltax, self.deltay, self.dxTextSpace
|
||||
fontName, fontSize, fillColor = self.fontName, self.fontSize, self.fillColor
|
||||
strokeWidth, strokeColor = self.strokeWidth, self.strokeColor
|
||||
leading = fontSize*1.2
|
||||
if not deltay:
|
||||
deltay = max(dy,leading)+self.autoYPadding
|
||||
if not deltax:
|
||||
maxWidth = self._calculateMaxWidth(colorNamePairs)
|
||||
deltax = maxWidth+dx+dxTextSpace+self.autoXPadding
|
||||
else:
|
||||
if alignment=='left': maxWidth = self._calculateMaxWidth(colorNamePairs)
|
||||
|
||||
def gAdd(t,g=g,fontName=fontName,fontSize=fontSize,fillColor=fillColor):
|
||||
t.fontName = fontName
|
||||
t.fontSize = fontSize
|
||||
t.fillColor = fillColor
|
||||
return g.add(t)
|
||||
|
||||
ascent=getFont(fontName).face.ascent/1000.
|
||||
if ascent==0: ascent=0.718 # default (from helvetica)
|
||||
ascent=ascent*fontSize # normalize
|
||||
|
||||
columnCount = 0
|
||||
count = 0
|
||||
callout = getattr(self,'callout',None)
|
||||
for col, name in colorNamePairs:
|
||||
T = string.split(name and str(name) or '','\n')
|
||||
S = []
|
||||
# thisy+dy/2 = y+leading/2
|
||||
y = thisy+(dy-ascent)*0.5
|
||||
if callout: callout(self,g,thisx,y,colorNamePairs[count])
|
||||
if alignment == "left":
|
||||
for t in T:
|
||||
# align text to left
|
||||
s = String(thisx+maxWidth,y,t)
|
||||
s.textAnchor = "end"
|
||||
S.append(s)
|
||||
y = y-leading
|
||||
x = thisx+maxWidth+dxTextSpace
|
||||
elif alignment == "right":
|
||||
for t in T:
|
||||
# align text to right
|
||||
s = String(thisx+dx+dxTextSpace, y, t)
|
||||
s.textAnchor = "start"
|
||||
S.append(s)
|
||||
y = y-leading
|
||||
x = thisx
|
||||
else:
|
||||
raise ValueError, "bad alignment"
|
||||
|
||||
# Make a 'normal' color swatch...
|
||||
if isinstance(col, colors.Color):
|
||||
r = Rect(x, thisy, dx, dy)
|
||||
r.fillColor = col
|
||||
r.strokeColor = strokeColor
|
||||
r.strokeWidth = strokeWidth
|
||||
g.add(r)
|
||||
else:
|
||||
#try and see if we should do better.
|
||||
try:
|
||||
c = copy.deepcopy(col)
|
||||
c.x = x
|
||||
c.y = thisy
|
||||
c.width = dx
|
||||
c.height = dy
|
||||
g.add(c)
|
||||
except:
|
||||
pass
|
||||
|
||||
map(gAdd,S)
|
||||
|
||||
if count%columnMaximum == columnMaximum-1:
|
||||
thisx = thisx+deltax
|
||||
thisy = upperlefty
|
||||
columnCount = columnCount + 1
|
||||
else:
|
||||
thisy = thisy-max(deltay,len(S)*leading)
|
||||
count = count+1
|
||||
|
||||
return g
|
||||
|
||||
|
||||
class LineSwatch(Widget):
|
||||
"""basically a Line with properties added so it can be used in a LineLegend"""
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber, desc="x-coordinate for swatch line start point"),
|
||||
y = AttrMapValue(isNumber, desc="y-coordinate for swatch line start point"),
|
||||
width = AttrMapValue(isNumber, desc="length of swatch line"),
|
||||
height = AttrMapValue(isNumber, desc="used for line strokeWidth"),
|
||||
strokeColor = AttrMapValue(isColorOrNone, desc="color of swatch line"),
|
||||
strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc="dash array for swatch line"),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
from reportlab.lib.colors import red
|
||||
from reportlab.graphics.shapes import Line
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.width = 20
|
||||
self.height = 1
|
||||
self.strokeColor = red
|
||||
self.strokeDashArray = None
|
||||
|
||||
def draw(self):
|
||||
l = Line(self.x,self.y,self.x+self.width,self.y)
|
||||
l.strokeColor = self.strokeColor
|
||||
l.strokeDashArray = self.strokeDashArray
|
||||
l.strokeWidth = self.height
|
||||
return l
|
||||
|
||||
class LineLegend(Legend):
|
||||
"""A subclass of Legend for drawing legends with lines as the
|
||||
swatches rather than rectangles. Useful for lineCharts and
|
||||
linePlots. Should be similar in all other ways the the standard
|
||||
Legend class.
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber, desc="x-coordinate of upper-left reference point"),
|
||||
y = AttrMapValue(isNumber, desc="y-coordinate of upper-left reference point"),
|
||||
deltax = AttrMapValue(isNumberOrNone, desc="x-distance between neighbouring line-swatches"),
|
||||
deltay = AttrMapValue(isNumberOrNone, desc="y-distance between neighbouring line-swatches"),
|
||||
dxTextSpace = AttrMapValue(isNumber, desc="Distance between line-swatches and text"),
|
||||
autoXPadding = AttrMapValue(isNumber, desc="x Padding between columns if deltax=None"),
|
||||
autoYPadding = AttrMapValue(isNumber, desc="y Padding between rows if deltay=None"),
|
||||
dx = AttrMapValue(isNumber, desc="Width of line-swatch - ie length of the line"),
|
||||
dy = AttrMapValue(isNumber, desc="Height of line-swatch - ie strokeWidth to be used for the line"),
|
||||
columnMaximum = AttrMapValue(isNumber, desc="Max. number of items per column"),
|
||||
alignment = AttrMapValue(OneOf("left", "right"), desc="Alignment of text with respect to line-swatches"),
|
||||
colorNamePairs = AttrMapValue(None, desc="List of color/name tuples (color can also be widget)"),
|
||||
fontName = AttrMapValue(isString, desc="Font name of the strings"),
|
||||
fontSize = AttrMapValue(isNumber, desc="Font size of the strings"),
|
||||
fillColor = AttrMapValue(isColorOrNone, desc=""),
|
||||
strokeColor = AttrMapValue(isColorOrNone, desc="Stroke color of the line-swatches"),
|
||||
strokeWidth = AttrMapValue(isNumber, desc="Width of the line-swatches"),
|
||||
callout = AttrMapValue(None, desc="a user callout(self,g,x,y,(color,text))"),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
Legend.__init__(self)
|
||||
|
||||
# Size of swatch rectangle.
|
||||
self.dx = 10 #width of line
|
||||
self.dy = 2 #strokeWidth for line
|
||||
|
||||
# Color/name pairs.
|
||||
self.colorNamePairs = []
|
||||
for col, colName in [ (colors.red, "red"),
|
||||
(colors.blue, "blue"),
|
||||
(colors.green, "green"),
|
||||
(colors.pink, "pink"),
|
||||
(colors.yellow, "yellow") ]:
|
||||
l = LineSwatch()
|
||||
l.strokeColor = col
|
||||
self.colorNamePairs.append((l, colName))
|
||||
|
||||
# Font name and size of the labels.
|
||||
self.fillColor = STATE_DEFAULTS['fillColor']
|
||||
self.strokeColor = STATE_DEFAULTS['strokeColor']
|
||||
self.strokeWidth = STATE_DEFAULTS['strokeWidth']
|
||||
|
||||
def draw(self):
|
||||
g = Group()
|
||||
colorNamePairs = self.colorNamePairs
|
||||
thisx = upperleftx = self.x
|
||||
thisy = upperlefty = self.y - self.dy
|
||||
dx, dy, alignment, columnMaximum = self.dx, self.dy, self.alignment, self.columnMaximum
|
||||
deltax, deltay, dxTextSpace = self.deltax, self.deltay, self.dxTextSpace
|
||||
fontName, fontSize, fillColor = self.fontName, self.fontSize, self.fillColor
|
||||
strokeWidth, strokeColor = self.strokeWidth, self.strokeColor
|
||||
leading = fontSize*1.2
|
||||
if not deltay:
|
||||
deltay = max(dy,leading)+self.autoYPadding
|
||||
if not deltax:
|
||||
maxWidth = self._calculateMaxWidth(colorNamePairs)
|
||||
deltax = maxWidth+dx+dxTextSpace+self.autoXPadding
|
||||
else:
|
||||
if alignment=='left': maxWidth = self._calculateMaxWidth(colorNamePairs)
|
||||
|
||||
def gAdd(t,g=g,fontName=fontName,fontSize=fontSize,fillColor=fillColor):
|
||||
t.fontName = fontName
|
||||
t.fontSize = fontSize
|
||||
t.fillColor = fillColor
|
||||
return g.add(t)
|
||||
|
||||
ascent=getFont(fontName).face.ascent/1000.
|
||||
if ascent==0: ascent=0.718 # default (from helvetica)
|
||||
ascent=ascent*fontSize # normalize
|
||||
|
||||
columnCount = 0
|
||||
count = 0
|
||||
callout = getattr(self,'callout',None)
|
||||
for col, name in colorNamePairs:
|
||||
T = string.split(name and str(name) or '','\n')
|
||||
S = []
|
||||
# thisy+dy/2 = y+leading/2
|
||||
y = thisy+(dy-ascent)*0.5
|
||||
if callout: callout(self,g,thisx,y,colorNamePairs[count])
|
||||
if alignment == "left":
|
||||
for t in T:
|
||||
# align text to left
|
||||
s = String(thisx+maxWidth,y,t)
|
||||
s.textAnchor = "end"
|
||||
S.append(s)
|
||||
y = y-leading
|
||||
x = thisx+maxWidth+dxTextSpace
|
||||
elif alignment == "right":
|
||||
for t in T:
|
||||
# align text to right
|
||||
s = String(thisx+dx+dxTextSpace, y, t)
|
||||
s.textAnchor = "start"
|
||||
S.append(s)
|
||||
y = y-leading
|
||||
x = thisx
|
||||
else:
|
||||
raise ValueError, "bad alignment"
|
||||
|
||||
# Make a 'normal' color line-swatch...
|
||||
if isinstance(col, colors.Color):
|
||||
l = LineSwatch()
|
||||
l.x = x
|
||||
l.y = thisy
|
||||
l.width = dx
|
||||
l.height = dy
|
||||
l.strokeColor = col
|
||||
g.add(l)
|
||||
else:
|
||||
#try and see if we should do better.
|
||||
try:
|
||||
c = copy.deepcopy(col)
|
||||
c.x = x
|
||||
c.y = thisy
|
||||
c.width = dx
|
||||
c.height = dy
|
||||
g.add(c)
|
||||
except:
|
||||
pass
|
||||
|
||||
map(gAdd,S)
|
||||
|
||||
if count%columnMaximum == columnMaximum-1:
|
||||
thisx = thisx+deltax
|
||||
thisy = upperlefty
|
||||
columnCount = columnCount + 1
|
||||
else:
|
||||
thisy = thisy-max(deltay,len(S)*leading)
|
||||
count = count+1
|
||||
|
||||
return g
|
||||
|
||||
|
||||
|
||||
def demo(self):
|
||||
"Make sample legend."
|
||||
|
||||
d = Drawing(200, 100)
|
||||
|
||||
legend = Legend()
|
||||
legend.alignment = 'left'
|
||||
legend.x = 0
|
||||
legend.y = 100
|
||||
legend.dxTextSpace = 5
|
||||
items = string.split('red green blue yellow pink black white', ' ')
|
||||
items = map(lambda i:(getattr(colors, i), i), items)
|
||||
legend.colorNamePairs = items
|
||||
|
||||
d.add(legend, 'legend')
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def sample1c():
|
||||
"Make sample legend."
|
||||
|
||||
d = Drawing(200, 100)
|
||||
|
||||
legend = Legend()
|
||||
legend.alignment = 'right'
|
||||
legend.x = 0
|
||||
legend.y = 100
|
||||
legend.dxTextSpace = 5
|
||||
items = string.split('red green blue yellow pink black white', ' ')
|
||||
items = map(lambda i:(getattr(colors, i), i), items)
|
||||
legend.colorNamePairs = items
|
||||
|
||||
d.add(legend, 'legend')
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def sample2c():
|
||||
"Make sample legend."
|
||||
|
||||
d = Drawing(200, 100)
|
||||
|
||||
legend = Legend()
|
||||
legend.alignment = 'right'
|
||||
legend.x = 20
|
||||
legend.y = 90
|
||||
legend.deltax = 60
|
||||
legend.dxTextSpace = 10
|
||||
legend.columnMaximum = 4
|
||||
items = string.split('red green blue yellow pink black white', ' ')
|
||||
items = map(lambda i:(getattr(colors, i), i), items)
|
||||
legend.colorNamePairs = items
|
||||
|
||||
d.add(legend, 'legend')
|
||||
|
||||
return d
|
||||
|
||||
def sample3():
|
||||
"Make sample legend with line swatches."
|
||||
|
||||
d = Drawing(200, 100)
|
||||
|
||||
legend = LineLegend()
|
||||
legend.alignment = 'right'
|
||||
legend.x = 20
|
||||
legend.y = 90
|
||||
legend.deltax = 60
|
||||
legend.dxTextSpace = 10
|
||||
legend.columnMaximum = 4
|
||||
items = string.split('red green blue yellow pink black white', ' ')
|
||||
items = map(lambda i:(getattr(colors, i), i), items)
|
||||
legend.colorNamePairs = items
|
||||
d.add(legend, 'legend')
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def sample3a():
|
||||
"Make sample legend with line swatches and dasharrays on the lines."
|
||||
|
||||
d = Drawing(200, 100)
|
||||
|
||||
legend = LineLegend()
|
||||
legend.alignment = 'right'
|
||||
legend.x = 20
|
||||
legend.y = 90
|
||||
legend.deltax = 60
|
||||
legend.dxTextSpace = 10
|
||||
legend.columnMaximum = 4
|
||||
items = string.split('red green blue yellow pink black white', ' ')
|
||||
darrays = ([2,1], [2,5], [2,2,5,5], [1,2,3,4], [4,2,3,4], [1,2,3,4,5,6], [1])
|
||||
cnp = []
|
||||
for i in range(0, len(items)):
|
||||
l = LineSwatch()
|
||||
l.strokeColor = getattr(colors, items[i])
|
||||
l.strokeDashArray = darrays[i]
|
||||
cnp.append((l, items[i]))
|
||||
legend.colorNamePairs = cnp
|
||||
d.add(legend, 'legend')
|
||||
|
||||
return d
|
|
@ -1,656 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/linecharts.py
|
||||
"""
|
||||
This modules defines a very preliminary Line Chart example.
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
import string
|
||||
from types import FunctionType, StringType
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.validators import isNumber, isColor, isColorOrNone, isListOfStrings, \
|
||||
isListOfStringsOrNone, SequenceOf, isBoolean, NoneOr, \
|
||||
isListOfNumbersOrNone
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.lib.formatters import Formatter
|
||||
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
|
||||
from reportlab.graphics.shapes import Line, Rect, Group, Drawing, Polygon, PolyLine
|
||||
from reportlab.graphics.widgets.signsandsymbols import NoEntry
|
||||
from reportlab.graphics.charts.axes import XCategoryAxis, YValueAxis
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from reportlab.graphics.widgets.markers import uSymbol2Symbol, isSymbol, makeMarker
|
||||
from reportlab.graphics.charts.areas import PlotArea
|
||||
|
||||
class LineChartProperties(PropHolder):
|
||||
_attrMap = AttrMap(
|
||||
strokeWidth = AttrMapValue(isNumber, desc='Width of a line.'),
|
||||
strokeColor = AttrMapValue(isColorOrNone, desc='Color of a line.'),
|
||||
strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array of a line.'),
|
||||
symbol = AttrMapValue(NoneOr(isSymbol), desc='Widget placed at data points.'),
|
||||
)
|
||||
|
||||
class LineChart(PlotArea):
|
||||
pass
|
||||
|
||||
# This is conceptually similar to the VerticalBarChart.
|
||||
# Still it is better named HorizontalLineChart... :-/
|
||||
|
||||
class HorizontalLineChart(LineChart):
|
||||
"""Line chart with multiple lines.
|
||||
|
||||
A line chart is assumed to have one category and one value axis.
|
||||
Despite its generic name this particular line chart class has
|
||||
a vertical value axis and a horizontal category one. It may
|
||||
evolve into individual horizontal and vertical variants (like
|
||||
with the existing bar charts).
|
||||
|
||||
Available attributes are:
|
||||
|
||||
x: x-position of lower-left chart origin
|
||||
y: y-position of lower-left chart origin
|
||||
width: chart width
|
||||
height: chart height
|
||||
|
||||
useAbsolute: disables auto-scaling of chart elements (?)
|
||||
lineLabelNudge: distance of data labels to data points
|
||||
lineLabels: labels associated with data values
|
||||
lineLabelFormat: format string or callback function
|
||||
groupSpacing: space between categories
|
||||
|
||||
joinedLines: enables drawing of lines
|
||||
|
||||
strokeColor: color of chart lines (?)
|
||||
fillColor: color for chart background (?)
|
||||
lines: style list, used cyclically for data series
|
||||
|
||||
valueAxis: value axis object
|
||||
categoryAxis: category axis object
|
||||
categoryNames: category names
|
||||
|
||||
data: chart data, a list of data series of equal length
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(BASE=LineChart,
|
||||
useAbsolute = AttrMapValue(isNumber, desc='Flag to use absolute spacing values.'),
|
||||
lineLabelNudge = AttrMapValue(isNumber, desc='Distance between a data point and its label.'),
|
||||
lineLabels = AttrMapValue(None, desc='Handle to the list of data point labels.'),
|
||||
lineLabelFormat = AttrMapValue(None, desc='Formatting string or function used for data point labels.'),
|
||||
lineLabelArray = AttrMapValue(None, desc='explicit array of line label values, must match size of data if present.'),
|
||||
groupSpacing = AttrMapValue(isNumber, desc='? - Likely to disappear.'),
|
||||
joinedLines = AttrMapValue(isNumber, desc='Display data points joined with lines if true.'),
|
||||
lines = AttrMapValue(None, desc='Handle of the lines.'),
|
||||
valueAxis = AttrMapValue(None, desc='Handle of the value axis.'),
|
||||
categoryAxis = AttrMapValue(None, desc='Handle of the category axis.'),
|
||||
categoryNames = AttrMapValue(isListOfStringsOrNone, desc='List of category names.'),
|
||||
data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'),
|
||||
inFill = AttrMapValue(isBoolean, desc='Whether infilling should be done.'),
|
||||
reversePlotOrder = AttrMapValue(isBoolean, desc='If true reverse plot order.'),
|
||||
annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
LineChart.__init__(self)
|
||||
|
||||
# Allow for a bounding rectangle.
|
||||
self.strokeColor = None
|
||||
self.fillColor = None
|
||||
|
||||
# Named so we have less recoding for the horizontal one :-)
|
||||
self.categoryAxis = XCategoryAxis()
|
||||
self.valueAxis = YValueAxis()
|
||||
|
||||
# This defines two series of 3 points. Just an example.
|
||||
self.data = [(100,110,120,130),
|
||||
(70, 80, 80, 90)]
|
||||
self.categoryNames = ('North','South','East','West')
|
||||
|
||||
self.lines = TypedPropertyCollection(LineChartProperties)
|
||||
self.lines.strokeWidth = 1
|
||||
self.lines[0].strokeColor = colors.red
|
||||
self.lines[1].strokeColor = colors.green
|
||||
self.lines[2].strokeColor = colors.blue
|
||||
|
||||
# control spacing. if useAbsolute = 1 then
|
||||
# the next parameters are in points; otherwise
|
||||
# they are 'proportions' and are normalized to
|
||||
# fit the available space.
|
||||
self.useAbsolute = 0 #- not done yet
|
||||
self.groupSpacing = 1 #5
|
||||
|
||||
self.lineLabels = TypedPropertyCollection(Label)
|
||||
self.lineLabelFormat = None
|
||||
self.lineLabelArray = None
|
||||
|
||||
# This says whether the origin is above or below
|
||||
# the data point. +10 means put the origin ten points
|
||||
# above the data point if value > 0, or ten
|
||||
# points below if data value < 0. This is different
|
||||
# to label dx/dy which are not dependent on the
|
||||
# sign of the data.
|
||||
self.lineLabelNudge = 10
|
||||
# If you have multiple series, by default they butt
|
||||
# together.
|
||||
|
||||
# New line chart attributes.
|
||||
self.joinedLines = 1 # Connect items with straight lines.
|
||||
self.inFill = 0
|
||||
self.reversePlotOrder = 0
|
||||
|
||||
|
||||
def demo(self):
|
||||
"""Shows basic use of a line chart."""
|
||||
|
||||
drawing = Drawing(200, 100)
|
||||
|
||||
data = [
|
||||
(13, 5, 20, 22, 37, 45, 19, 4),
|
||||
(14, 10, 21, 28, 38, 46, 25, 5)
|
||||
]
|
||||
|
||||
lc = HorizontalLineChart()
|
||||
|
||||
lc.x = 20
|
||||
lc.y = 10
|
||||
lc.height = 85
|
||||
lc.width = 170
|
||||
lc.data = data
|
||||
lc.lines.symbol = makeMarker('Circle')
|
||||
|
||||
drawing.add(lc)
|
||||
|
||||
return drawing
|
||||
|
||||
|
||||
def calcPositions(self):
|
||||
"""Works out where they go.
|
||||
|
||||
Sets an attribute _positions which is a list of
|
||||
lists of (x, y) matching the data.
|
||||
"""
|
||||
|
||||
self._seriesCount = len(self.data)
|
||||
self._rowLength = max(map(len,self.data))
|
||||
|
||||
if self.useAbsolute:
|
||||
# Dimensions are absolute.
|
||||
normFactor = 1.0
|
||||
else:
|
||||
# Dimensions are normalized to fit.
|
||||
normWidth = self.groupSpacing
|
||||
availWidth = self.categoryAxis.scale(0)[1]
|
||||
normFactor = availWidth / normWidth
|
||||
|
||||
self._positions = []
|
||||
for rowNo in range(len(self.data)):
|
||||
lineRow = []
|
||||
for colNo in range(len(self.data[rowNo])):
|
||||
datum = self.data[rowNo][colNo]
|
||||
if datum is not None:
|
||||
(groupX, groupWidth) = self.categoryAxis.scale(colNo)
|
||||
x = groupX + (0.5 * self.groupSpacing * normFactor)
|
||||
y = self.valueAxis.scale(0)
|
||||
height = self.valueAxis.scale(datum) - y
|
||||
lineRow.append((x, y+height))
|
||||
self._positions.append(lineRow)
|
||||
|
||||
|
||||
def _innerDrawLabel(self, rowNo, colNo, x, y):
|
||||
"Draw a label for a given item in the list."
|
||||
|
||||
labelFmt = self.lineLabelFormat
|
||||
labelValue = self.data[rowNo][colNo]
|
||||
|
||||
if labelFmt is None:
|
||||
labelText = None
|
||||
elif type(labelFmt) is StringType:
|
||||
if labelFmt == 'values':
|
||||
labelText = self.lineLabelArray[rowNo][colNo]
|
||||
else:
|
||||
labelText = labelFmt % labelValue
|
||||
elif type(labelFmt) is FunctionType:
|
||||
labelText = labelFmt(labelValue)
|
||||
elif isinstance(labelFmt, Formatter):
|
||||
labelText = labelFmt(labelValue)
|
||||
else:
|
||||
msg = "Unknown formatter type %s, expected string or function"
|
||||
raise Exception, msg % labelFmt
|
||||
|
||||
if labelText:
|
||||
label = self.lineLabels[(rowNo, colNo)]
|
||||
# Make sure labels are some distance off the data point.
|
||||
if y > 0:
|
||||
label.setOrigin(x, y + self.lineLabelNudge)
|
||||
else:
|
||||
label.setOrigin(x, y - self.lineLabelNudge)
|
||||
label.setText(labelText)
|
||||
else:
|
||||
label = None
|
||||
return label
|
||||
|
||||
def drawLabel(self, G, rowNo, colNo, x, y):
|
||||
'''Draw a label for a given item in the list.
|
||||
G must have an add method'''
|
||||
G.add(self._innerDrawLabel(rowNo,colNo,x,y))
|
||||
|
||||
def makeLines(self):
|
||||
g = Group()
|
||||
|
||||
labelFmt = self.lineLabelFormat
|
||||
P = range(len(self._positions))
|
||||
if self.reversePlotOrder: P.reverse()
|
||||
inFill = self.inFill
|
||||
if inFill:
|
||||
inFillY = self.categoryAxis._y
|
||||
inFillX0 = self.valueAxis._x
|
||||
inFillX1 = inFillX0 + self.categoryAxis._length
|
||||
inFillG = getattr(self,'_inFillG',g)
|
||||
|
||||
# Iterate over data rows.
|
||||
for rowNo in P:
|
||||
row = self._positions[rowNo]
|
||||
styleCount = len(self.lines)
|
||||
styleIdx = rowNo % styleCount
|
||||
rowStyle = self.lines[styleIdx]
|
||||
rowColor = rowStyle.strokeColor
|
||||
dash = getattr(rowStyle, 'strokeDashArray', None)
|
||||
|
||||
if hasattr(self.lines[styleIdx], 'strokeWidth'):
|
||||
strokeWidth = self.lines[styleIdx].strokeWidth
|
||||
elif hasattr(self.lines, 'strokeWidth'):
|
||||
strokeWidth = self.lines.strokeWidth
|
||||
else:
|
||||
strokeWidth = None
|
||||
|
||||
# Iterate over data columns.
|
||||
if self.joinedLines:
|
||||
points = []
|
||||
for colNo in range(len(row)):
|
||||
points += row[colNo]
|
||||
if inFill:
|
||||
points = points + [inFillX1,inFillY,inFillX0,inFillY]
|
||||
inFillG.add(Polygon(points,fillColor=rowColor,strokeColor=rowColor,strokeWidth=0.1))
|
||||
else:
|
||||
line = PolyLine(points,strokeColor=rowColor,strokeLineCap=0,strokeLineJoin=1)
|
||||
if strokeWidth:
|
||||
line.strokeWidth = strokeWidth
|
||||
if dash:
|
||||
line.strokeDashArray = dash
|
||||
g.add(line)
|
||||
|
||||
if hasattr(self.lines[styleIdx], 'symbol'):
|
||||
uSymbol = self.lines[styleIdx].symbol
|
||||
elif hasattr(self.lines, 'symbol'):
|
||||
uSymbol = self.lines.symbol
|
||||
else:
|
||||
uSymbol = None
|
||||
|
||||
if uSymbol:
|
||||
for colNo in range(len(row)):
|
||||
x1, y1 = row[colNo]
|
||||
symbol = uSymbol2Symbol(uSymbol,x1,y1,rowStyle.strokeColor)
|
||||
if symbol: g.add(symbol)
|
||||
|
||||
# Draw item labels.
|
||||
for colNo in range(len(row)):
|
||||
x1, y1 = row[colNo]
|
||||
self.drawLabel(g, rowNo, colNo, x1, y1)
|
||||
|
||||
return g
|
||||
|
||||
def draw(self):
|
||||
"Draws itself."
|
||||
|
||||
vA, cA = self.valueAxis, self.categoryAxis
|
||||
vA.setPosition(self.x, self.y, self.height)
|
||||
if vA: vA.joinAxis = cA
|
||||
if cA: cA.joinAxis = vA
|
||||
vA.configure(self.data)
|
||||
|
||||
# If zero is in chart, put x axis there, otherwise
|
||||
# use bottom.
|
||||
xAxisCrossesAt = vA.scale(0)
|
||||
if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)):
|
||||
y = self.y
|
||||
else:
|
||||
y = xAxisCrossesAt
|
||||
|
||||
cA.setPosition(self.x, y, self.width)
|
||||
cA.configure(self.data)
|
||||
|
||||
self.calcPositions()
|
||||
|
||||
g = Group()
|
||||
g.add(self.makeBackground())
|
||||
if self.inFill:
|
||||
self._inFillG = Group()
|
||||
g.add(self._inFillG)
|
||||
|
||||
g.add(cA)
|
||||
g.add(vA)
|
||||
vA.gridStart = cA._x
|
||||
vA.gridEnd = cA._x+cA._length
|
||||
cA.gridStart = vA._y
|
||||
cA.gridEnd = vA._y+vA._length
|
||||
cA.makeGrid(g,parent=self)
|
||||
vA.makeGrid(g,parent=self)
|
||||
g.add(self.makeLines())
|
||||
for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale))
|
||||
return g
|
||||
|
||||
def _cmpFakeItem(a,b):
|
||||
'''t, z0, z1, x, y = a[:5]'''
|
||||
return cmp((-a[1],a[3],a[0],-a[4]),(-b[1],b[3],b[0],-b[4]))
|
||||
|
||||
class _FakeGroup:
|
||||
def __init__(self):
|
||||
self._data = []
|
||||
|
||||
def add(self,what):
|
||||
if what: self._data.append(what)
|
||||
|
||||
def value(self):
|
||||
return self._data
|
||||
|
||||
def sort(self):
|
||||
self._data.sort(_cmpFakeItem)
|
||||
#for t in self._data: print t
|
||||
|
||||
class HorizontalLineChart3D(HorizontalLineChart):
|
||||
_attrMap = AttrMap(BASE=HorizontalLineChart,
|
||||
theta_x = AttrMapValue(isNumber, desc='dx/dz'),
|
||||
theta_y = AttrMapValue(isNumber, desc='dy/dz'),
|
||||
zDepth = AttrMapValue(isNumber, desc='depth of an individual series'),
|
||||
zSpace = AttrMapValue(isNumber, desc='z gap around series'),
|
||||
)
|
||||
theta_x = .5
|
||||
theta_y = .5
|
||||
zDepth = 10
|
||||
zSpace = 3
|
||||
|
||||
def calcPositions(self):
|
||||
HorizontalLineChart.calcPositions(self)
|
||||
nSeries = self._seriesCount
|
||||
zSpace = self.zSpace
|
||||
zDepth = self.zDepth
|
||||
if self.categoryAxis.style=='parallel_3d':
|
||||
_3d_depth = nSeries*zDepth+(nSeries+1)*zSpace
|
||||
else:
|
||||
_3d_depth = zDepth + 2*zSpace
|
||||
self._3d_dx = self.theta_x*_3d_depth
|
||||
self._3d_dy = self.theta_y*_3d_depth
|
||||
|
||||
def _calc_z0(self,rowNo):
|
||||
zSpace = self.zSpace
|
||||
if self.categoryAxis.style=='parallel_3d':
|
||||
z0 = rowNo*(self.zDepth+zSpace)+zSpace
|
||||
else:
|
||||
z0 = zSpace
|
||||
return z0
|
||||
|
||||
def _zadjust(self,x,y,z):
|
||||
return x+z*self.theta_x, y+z*self.theta_y
|
||||
|
||||
def makeLines(self):
|
||||
labelFmt = self.lineLabelFormat
|
||||
P = range(len(self._positions))
|
||||
if self.reversePlotOrder: P.reverse()
|
||||
inFill = self.inFill
|
||||
assert not inFill, "inFill not supported for 3d yet"
|
||||
#if inFill:
|
||||
#inFillY = self.categoryAxis._y
|
||||
#inFillX0 = self.valueAxis._x
|
||||
#inFillX1 = inFillX0 + self.categoryAxis._length
|
||||
#inFillG = getattr(self,'_inFillG',g)
|
||||
zDepth = self.zDepth
|
||||
_zadjust = self._zadjust
|
||||
theta_x = self.theta_x
|
||||
theta_y = self.theta_y
|
||||
F = _FakeGroup()
|
||||
from utils3d import _make_3d_line_info
|
||||
tileWidth = getattr(self,'_3d_tilewidth',None)
|
||||
if not tileWidth and self.categoryAxis.style!='parallel_3d': tileWidth = 1
|
||||
|
||||
# Iterate over data rows.
|
||||
for rowNo in P:
|
||||
row = self._positions[rowNo]
|
||||
n = len(row)
|
||||
styleCount = len(self.lines)
|
||||
styleIdx = rowNo % styleCount
|
||||
rowStyle = self.lines[styleIdx]
|
||||
rowColor = rowStyle.strokeColor
|
||||
dash = getattr(rowStyle, 'strokeDashArray', None)
|
||||
z0 = self._calc_z0(rowNo)
|
||||
z1 = z0 + zDepth
|
||||
|
||||
if hasattr(self.lines[styleIdx], 'strokeWidth'):
|
||||
strokeWidth = self.lines[styleIdx].strokeWidth
|
||||
elif hasattr(self.lines, 'strokeWidth'):
|
||||
strokeWidth = self.lines.strokeWidth
|
||||
else:
|
||||
strokeWidth = None
|
||||
|
||||
# Iterate over data columns.
|
||||
if self.joinedLines:
|
||||
if n:
|
||||
x0, y0 = row[0]
|
||||
for colNo in xrange(1,n):
|
||||
x1, y1 = row[colNo]
|
||||
_make_3d_line_info( F, x0, x1, y0, y1, z0, z1,
|
||||
theta_x, theta_y,
|
||||
rowColor, fillColorShaded=None, tileWidth=tileWidth,
|
||||
strokeColor=None, strokeWidth=None, strokeDashArray=None,
|
||||
shading=0.1)
|
||||
x0, y0 = x1, y1
|
||||
|
||||
if hasattr(self.lines[styleIdx], 'symbol'):
|
||||
uSymbol = self.lines[styleIdx].symbol
|
||||
elif hasattr(self.lines, 'symbol'):
|
||||
uSymbol = self.lines.symbol
|
||||
else:
|
||||
uSymbol = None
|
||||
|
||||
if uSymbol:
|
||||
for colNo in xrange(n):
|
||||
x1, y1 = row[colNo]
|
||||
x1, y1 = _zadjust(x1,y1,z0)
|
||||
symbol = uSymbol2Symbol(uSymbol,x1,y1,rowColor)
|
||||
if symbol: F.add((2,z0,z0,x1,y1,symbol))
|
||||
|
||||
# Draw item labels.
|
||||
for colNo in xrange(n):
|
||||
x1, y1 = row[colNo]
|
||||
x1, y1 = _zadjust(x1,y1,z0)
|
||||
L = self._innerDrawLabel(rowNo, colNo, x1, y1)
|
||||
if L: F.add((2,z0,z0,x1,y1,L))
|
||||
|
||||
F.sort()
|
||||
g = Group()
|
||||
map(lambda x,a=g.add: a(x[-1]),F.value())
|
||||
return g
|
||||
|
||||
class VerticalLineChart(LineChart):
|
||||
pass
|
||||
|
||||
|
||||
def sample1():
|
||||
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
|
||||
lc.lines.symbol = makeMarker('FilledDiamond')
|
||||
lc.lineLabelFormat = '%2.0f'
|
||||
|
||||
catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ')
|
||||
lc.categoryAxis.categoryNames = catNames
|
||||
lc.categoryAxis.labels.boxAnchor = 'n'
|
||||
|
||||
lc.valueAxis.valueMin = 0
|
||||
lc.valueAxis.valueMax = 60
|
||||
lc.valueAxis.valueStep = 15
|
||||
|
||||
drawing.add(lc)
|
||||
|
||||
return drawing
|
||||
|
||||
|
||||
class SampleHorizontalLineChart(HorizontalLineChart):
|
||||
"Sample class overwriting one method to draw additional horizontal lines."
|
||||
|
||||
def demo(self):
|
||||
"""Shows basic use of a line chart."""
|
||||
|
||||
drawing = Drawing(200, 100)
|
||||
|
||||
data = [
|
||||
(13, 5, 20, 22, 37, 45, 19, 4),
|
||||
(14, 10, 21, 28, 38, 46, 25, 5)
|
||||
]
|
||||
|
||||
lc = SampleHorizontalLineChart()
|
||||
|
||||
lc.x = 20
|
||||
lc.y = 10
|
||||
lc.height = 85
|
||||
lc.width = 170
|
||||
lc.data = data
|
||||
lc.strokeColor = colors.white
|
||||
lc.fillColor = colors.HexColor(0xCCCCCC)
|
||||
|
||||
drawing.add(lc)
|
||||
|
||||
return drawing
|
||||
|
||||
|
||||
def makeBackground(self):
|
||||
g = Group()
|
||||
|
||||
g.add(HorizontalLineChart.makeBackground(self))
|
||||
|
||||
valAxis = self.valueAxis
|
||||
valTickPositions = valAxis._tickValues
|
||||
|
||||
for y in valTickPositions:
|
||||
y = valAxis.scale(y)
|
||||
g.add(Line(self.x, y, self.x+self.width, y,
|
||||
strokeColor = self.strokeColor))
|
||||
|
||||
return g
|
||||
|
||||
|
||||
|
||||
def sample1a():
|
||||
drawing = Drawing(400, 200)
|
||||
|
||||
data = [
|
||||
(13, 5, 20, 22, 37, 45, 19, 4),
|
||||
(5, 20, 46, 38, 23, 21, 6, 14)
|
||||
]
|
||||
|
||||
lc = SampleHorizontalLineChart()
|
||||
|
||||
lc.x = 50
|
||||
lc.y = 50
|
||||
lc.height = 125
|
||||
lc.width = 300
|
||||
lc.data = data
|
||||
lc.joinedLines = 1
|
||||
lc.strokeColor = colors.white
|
||||
lc.fillColor = colors.HexColor(0xCCCCCC)
|
||||
lc.lines.symbol = makeMarker('FilledDiamond')
|
||||
lc.lineLabelFormat = '%2.0f'
|
||||
|
||||
catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ')
|
||||
lc.categoryAxis.categoryNames = catNames
|
||||
lc.categoryAxis.labels.boxAnchor = 'n'
|
||||
|
||||
lc.valueAxis.valueMin = 0
|
||||
lc.valueAxis.valueMax = 60
|
||||
lc.valueAxis.valueStep = 15
|
||||
|
||||
drawing.add(lc)
|
||||
|
||||
return drawing
|
||||
|
||||
|
||||
def sample2():
|
||||
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
|
||||
lc.lines.symbol = makeMarker('Smiley')
|
||||
lc.lineLabelFormat = '%2.0f'
|
||||
lc.strokeColor = colors.black
|
||||
lc.fillColor = colors.lightblue
|
||||
|
||||
catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ')
|
||||
lc.categoryAxis.categoryNames = catNames
|
||||
lc.categoryAxis.labels.boxAnchor = 'n'
|
||||
|
||||
lc.valueAxis.valueMin = 0
|
||||
lc.valueAxis.valueMax = 60
|
||||
lc.valueAxis.valueStep = 15
|
||||
|
||||
drawing.add(lc)
|
||||
|
||||
return drawing
|
||||
|
||||
|
||||
def sample3():
|
||||
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
|
||||
lc.lineLabelFormat = '%2.0f'
|
||||
lc.strokeColor = colors.black
|
||||
|
||||
lc.lines[0].symbol = makeMarker('Smiley')
|
||||
lc.lines[1].symbol = NoEntry
|
||||
lc.lines[0].strokeWidth = 2
|
||||
lc.lines[1].strokeWidth = 4
|
||||
|
||||
catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ')
|
||||
lc.categoryAxis.categoryNames = catNames
|
||||
lc.categoryAxis.labels.boxAnchor = 'n'
|
||||
|
||||
lc.valueAxis.valueMin = 0
|
||||
lc.valueAxis.valueMax = 60
|
||||
lc.valueAxis.valueStep = 15
|
||||
|
||||
drawing.add(lc)
|
||||
|
||||
return drawing
|
File diff suppressed because it is too large
Load Diff
|
@ -1,81 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/markers.py
|
||||
"""
|
||||
This modules defines a collection of markers used in charts.
|
||||
|
||||
The make* functions return a simple shape or a widget as for
|
||||
the smiley.
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
from reportlab.lib import colors
|
||||
from reportlab.graphics.shapes import Rect, Line, Circle, Polygon
|
||||
from reportlab.graphics.widgets.signsandsymbols import SmileyFace
|
||||
|
||||
|
||||
def makeEmptySquare(x, y, size, color):
|
||||
"Make an empty square marker."
|
||||
|
||||
d = size/2.0
|
||||
rect = Rect(x-d, y-d, 2*d, 2*d)
|
||||
rect.strokeColor = color
|
||||
rect.fillColor = None
|
||||
|
||||
return rect
|
||||
|
||||
|
||||
def makeFilledSquare(x, y, size, color):
|
||||
"Make a filled square marker."
|
||||
|
||||
d = size/2.0
|
||||
rect = Rect(x-d, y-d, 2*d, 2*d)
|
||||
rect.strokeColor = color
|
||||
rect.fillColor = color
|
||||
|
||||
return rect
|
||||
|
||||
|
||||
def makeFilledDiamond(x, y, size, color):
|
||||
"Make a filled diamond marker."
|
||||
|
||||
d = size/2.0
|
||||
poly = Polygon((x-d,y, x,y+d, x+d,y, x,y-d))
|
||||
poly.strokeColor = color
|
||||
poly.fillColor = color
|
||||
|
||||
return poly
|
||||
|
||||
|
||||
def makeEmptyCircle(x, y, size, color):
|
||||
"Make a hollow circle marker."
|
||||
|
||||
d = size/2.0
|
||||
circle = Circle(x, y, d)
|
||||
circle.strokeColor = color
|
||||
circle.fillColor = colors.white
|
||||
|
||||
return circle
|
||||
|
||||
|
||||
def makeFilledCircle(x, y, size, color):
|
||||
"Make a hollow circle marker."
|
||||
|
||||
d = size/2.0
|
||||
circle = Circle(x, y, d)
|
||||
circle.strokeColor = color
|
||||
circle.fillColor = color
|
||||
|
||||
return circle
|
||||
|
||||
|
||||
def makeSmiley(x, y, size, color):
|
||||
"Make a smiley marker."
|
||||
|
||||
d = size
|
||||
s = SmileyFace()
|
||||
s.fillColor = color
|
||||
s.x = x-d
|
||||
s.y = y-d
|
||||
s.size = d*2
|
||||
|
||||
return s
|
|
@ -1,863 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/piecharts.py
|
||||
# experimental pie chart script. Two types of pie - one is a monolithic
|
||||
#widget with all top-level properties, the other delegates most stuff to
|
||||
#a wedges collection whic lets you customize the group or every individual
|
||||
#wedge.
|
||||
|
||||
"""Basic Pie Chart class.
|
||||
|
||||
This permits you to customize and pop out individual wedges;
|
||||
supports elliptical and circular pies.
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
import copy
|
||||
from math import sin, cos, pi
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.validators import isColor, isNumber, isListOfNumbersOrNone,\
|
||||
isListOfNumbers, isColorOrNone, isString,\
|
||||
isListOfStringsOrNone, OneOf, SequenceOf,\
|
||||
isBoolean, isListOfColors, isNumberOrNone,\
|
||||
isNoneOrListOfNoneOrStrings, isTextAnchor,\
|
||||
isNoneOrListOfNoneOrNumbers, isBoxAnchor,\
|
||||
isStringOrNone
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.graphics.shapes import Group, Drawing, Ellipse, Wedge, String, STATE_DEFAULTS, ArcPath, Polygon
|
||||
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
|
||||
from textlabels import Label
|
||||
|
||||
_ANGLE2BOXANCHOR={0:'w', 45:'sw', 90:'s', 135:'se', 180:'e', 225:'ne', 270:'n', 315: 'nw', -45: 'nw'}
|
||||
class WedgeLabel(Label):
|
||||
def _checkDXY(self,ba):
|
||||
pass
|
||||
def _getBoxAnchor(self):
|
||||
na = (int((self._pmv%360)/45.)*45)%360
|
||||
if not (na % 90): # we have a right angle case
|
||||
da = (self._pmv - na) % 360
|
||||
if abs(da)>5:
|
||||
na = na + (da>0 and 45 or -45)
|
||||
ba = _ANGLE2BOXANCHOR[na]
|
||||
self._checkDXY(ba)
|
||||
return ba
|
||||
|
||||
class WedgeProperties(PropHolder):
|
||||
"""This holds descriptive information about the wedges in a pie chart.
|
||||
|
||||
It is not to be confused with the 'wedge itself'; this just holds
|
||||
a recipe for how to format one, and does not allow you to hack the
|
||||
angles. It can format a genuine Wedge object for you with its
|
||||
format method.
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(
|
||||
strokeWidth = AttrMapValue(isNumber),
|
||||
fillColor = AttrMapValue(isColorOrNone),
|
||||
strokeColor = AttrMapValue(isColorOrNone),
|
||||
strokeDashArray = AttrMapValue(isListOfNumbersOrNone),
|
||||
popout = AttrMapValue(isNumber),
|
||||
fontName = AttrMapValue(isString),
|
||||
fontSize = AttrMapValue(isNumber),
|
||||
fontColor = AttrMapValue(isColorOrNone),
|
||||
labelRadius = AttrMapValue(isNumber),
|
||||
label_dx = AttrMapValue(isNumber),
|
||||
label_dy = AttrMapValue(isNumber),
|
||||
label_angle = AttrMapValue(isNumber),
|
||||
label_boxAnchor = AttrMapValue(isBoxAnchor),
|
||||
label_boxStrokeColor = AttrMapValue(isColorOrNone),
|
||||
label_boxStrokeWidth = AttrMapValue(isNumber),
|
||||
label_boxFillColor = AttrMapValue(isColorOrNone),
|
||||
label_strokeColor = AttrMapValue(isColorOrNone),
|
||||
label_strokeWidth = AttrMapValue(isNumber),
|
||||
label_text = AttrMapValue(isStringOrNone),
|
||||
label_leading = AttrMapValue(isNumberOrNone),
|
||||
label_width = AttrMapValue(isNumberOrNone),
|
||||
label_maxWidth = AttrMapValue(isNumberOrNone),
|
||||
label_height = AttrMapValue(isNumberOrNone),
|
||||
label_textAnchor = AttrMapValue(isTextAnchor),
|
||||
label_visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
|
||||
label_topPadding = AttrMapValue(isNumber,'padding at top of box'),
|
||||
label_leftPadding = AttrMapValue(isNumber,'padding at left of box'),
|
||||
label_rightPadding = AttrMapValue(isNumber,'padding at right of box'),
|
||||
label_bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.strokeWidth = 0
|
||||
self.fillColor = None
|
||||
self.strokeColor = STATE_DEFAULTS["strokeColor"]
|
||||
self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"]
|
||||
self.popout = 0
|
||||
self.fontName = STATE_DEFAULTS["fontName"]
|
||||
self.fontSize = STATE_DEFAULTS["fontSize"]
|
||||
self.fontColor = STATE_DEFAULTS["fillColor"]
|
||||
self.labelRadius = 1.2
|
||||
self.label_dx = self.label_dy = self.label_angle = 0
|
||||
self.label_text = None
|
||||
self.label_topPadding = self.label_leftPadding = self.label_rightPadding = self.label_bottomPadding = 0
|
||||
self.label_boxAnchor = 'c'
|
||||
self.label_boxStrokeColor = None #boxStroke
|
||||
self.label_boxStrokeWidth = 0.5 #boxStrokeWidth
|
||||
self.label_boxFillColor = None
|
||||
self.label_strokeColor = None
|
||||
self.label_strokeWidth = 0.1
|
||||
self.label_leading = self.label_width = self.label_maxWidth = self.label_height = None
|
||||
self.label_textAnchor = 'start'
|
||||
self.label_visible = 1
|
||||
|
||||
def _addWedgeLabel(self,text,add,angle,labelX,labelY,wedgeStyle,labelClass=WedgeLabel):
|
||||
# now draw a label
|
||||
if self.simpleLabels:
|
||||
theLabel = String(labelX, labelY, text)
|
||||
theLabel.textAnchor = "middle"
|
||||
else:
|
||||
theLabel = labelClass()
|
||||
theLabel._pmv = angle
|
||||
theLabel.x = labelX
|
||||
theLabel.y = labelY
|
||||
theLabel.dx = wedgeStyle.label_dx
|
||||
theLabel.dy = wedgeStyle.label_dy
|
||||
theLabel.angle = wedgeStyle.label_angle
|
||||
theLabel.boxAnchor = wedgeStyle.label_boxAnchor
|
||||
theLabel.boxStrokeColor = wedgeStyle.label_boxStrokeColor
|
||||
theLabel.boxStrokeWidth = wedgeStyle.label_boxStrokeWidth
|
||||
theLabel.boxFillColor = wedgeStyle.label_boxFillColor
|
||||
theLabel.strokeColor = wedgeStyle.label_strokeColor
|
||||
theLabel.strokeWidth = wedgeStyle.label_strokeWidth
|
||||
_text = wedgeStyle.label_text
|
||||
if _text is None: _text = text
|
||||
theLabel._text = _text
|
||||
theLabel.leading = wedgeStyle.label_leading
|
||||
theLabel.width = wedgeStyle.label_width
|
||||
theLabel.maxWidth = wedgeStyle.label_maxWidth
|
||||
theLabel.height = wedgeStyle.label_height
|
||||
theLabel.textAnchor = wedgeStyle.label_textAnchor
|
||||
theLabel.visible = wedgeStyle.label_visible
|
||||
theLabel.topPadding = wedgeStyle.label_topPadding
|
||||
theLabel.leftPadding = wedgeStyle.label_leftPadding
|
||||
theLabel.rightPadding = wedgeStyle.label_rightPadding
|
||||
theLabel.bottomPadding = wedgeStyle.label_bottomPadding
|
||||
theLabel.fontSize = wedgeStyle.fontSize
|
||||
theLabel.fontName = wedgeStyle.fontName
|
||||
theLabel.fillColor = wedgeStyle.fontColor
|
||||
add(theLabel)
|
||||
|
||||
def _fixLabels(labels,n):
|
||||
if labels is None:
|
||||
labels = [''] * n
|
||||
else:
|
||||
i = n-len(labels)
|
||||
if i>0: labels = labels + ['']*i
|
||||
return labels
|
||||
|
||||
class Pie(Widget):
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber, desc='X position of the chart within its container.'),
|
||||
y = AttrMapValue(isNumber, desc='Y position of the chart within its container.'),
|
||||
width = AttrMapValue(isNumber, desc='width of pie bounding box. Need not be same as width.'),
|
||||
height = AttrMapValue(isNumber, desc='height of pie bounding box. Need not be same as height.'),
|
||||
data = AttrMapValue(isListOfNumbers, desc='list of numbers defining wedge sizes; need not sum to 1'),
|
||||
labels = AttrMapValue(isListOfStringsOrNone, desc="optional list of labels to use for each data point"),
|
||||
startAngle = AttrMapValue(isNumber, desc="angle of first slice; like the compass, 0 is due North"),
|
||||
direction = AttrMapValue( OneOf('clockwise', 'anticlockwise'), desc="'clockwise' or 'anticlockwise'"),
|
||||
slices = AttrMapValue(None, desc="collection of wedge descriptor objects"),
|
||||
simpleLabels = AttrMapValue(isBoolean, desc="If true(default) use String not super duper WedgeLabel"),
|
||||
other_threshold = AttrMapValue(isNumber, desc='A value for doing thresh holding, not used yet.'),
|
||||
)
|
||||
other_threshold=None
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.width = 100
|
||||
self.height = 100
|
||||
self.data = [1]
|
||||
self.labels = None # or list of strings
|
||||
self.startAngle = 90
|
||||
self.direction = "clockwise"
|
||||
self.simpleLabels = 1
|
||||
|
||||
self.slices = TypedPropertyCollection(WedgeProperties)
|
||||
self.slices[0].fillColor = colors.darkcyan
|
||||
self.slices[1].fillColor = colors.blueviolet
|
||||
self.slices[2].fillColor = colors.blue
|
||||
self.slices[3].fillColor = colors.cyan
|
||||
|
||||
def demo(self):
|
||||
d = Drawing(200, 100)
|
||||
|
||||
pc = Pie()
|
||||
pc.x = 50
|
||||
pc.y = 10
|
||||
pc.width = 100
|
||||
pc.height = 80
|
||||
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
|
||||
pc.slices[0].fillColor = colors.darkcyan
|
||||
pc.slices[1].fillColor = colors.blueviolet
|
||||
pc.slices[2].fillColor = colors.blue
|
||||
pc.slices[3].fillColor = colors.cyan
|
||||
pc.slices[4].fillColor = colors.aquamarine
|
||||
pc.slices[5].fillColor = colors.cadetblue
|
||||
pc.slices[6].fillColor = colors.lightcoral
|
||||
|
||||
d.add(pc)
|
||||
return d
|
||||
|
||||
def normalizeData(self):
|
||||
from operator import add
|
||||
data = self.data
|
||||
self._sum = sum = float(reduce(add,data,0))
|
||||
return abs(sum)>=1e-8 and map(lambda x,f=360./sum: f*x, data) or len(data)*[0]
|
||||
|
||||
def makeWedges(self):
|
||||
# normalize slice data
|
||||
normData = self.normalizeData()
|
||||
n = len(normData)
|
||||
labels = _fixLabels(self.labels,n)
|
||||
|
||||
xradius = self.width/2.0
|
||||
yradius = self.height/2.0
|
||||
centerx = self.x + xradius
|
||||
centery = self.y + yradius
|
||||
|
||||
if self.direction == "anticlockwise":
|
||||
whichWay = 1
|
||||
else:
|
||||
whichWay = -1
|
||||
|
||||
g = Group()
|
||||
i = 0
|
||||
styleCount = len(self.slices)
|
||||
|
||||
startAngle = self.startAngle #% 360
|
||||
for angle in normData:
|
||||
endAngle = (startAngle + (angle * whichWay)) #% 360
|
||||
if abs(startAngle-endAngle)>=1e-5:
|
||||
if startAngle < endAngle:
|
||||
a1 = startAngle
|
||||
a2 = endAngle
|
||||
else:
|
||||
a1 = endAngle
|
||||
a2 = startAngle
|
||||
|
||||
#if we didn't use %stylecount here we'd end up with the later wedges
|
||||
#all having the default style
|
||||
wedgeStyle = self.slices[i%styleCount]
|
||||
|
||||
# is it a popout?
|
||||
cx, cy = centerx, centery
|
||||
if wedgeStyle.popout <> 0:
|
||||
# pop out the wedge
|
||||
averageAngle = (a1+a2)/2.0
|
||||
aveAngleRadians = averageAngle * pi/180.0
|
||||
popdistance = wedgeStyle.popout
|
||||
cx = centerx + popdistance * cos(aveAngleRadians)
|
||||
cy = centery + popdistance * sin(aveAngleRadians)
|
||||
|
||||
if n > 1:
|
||||
theWedge = Wedge(cx, cy, xradius, a1, a2, yradius=yradius)
|
||||
elif n==1:
|
||||
theWedge = Ellipse(cx, cy, xradius, yradius)
|
||||
|
||||
theWedge.fillColor = wedgeStyle.fillColor
|
||||
theWedge.strokeColor = wedgeStyle.strokeColor
|
||||
theWedge.strokeWidth = wedgeStyle.strokeWidth
|
||||
theWedge.strokeDashArray = wedgeStyle.strokeDashArray
|
||||
|
||||
g.add(theWedge)
|
||||
text = labels[i]
|
||||
if text:
|
||||
averageAngle = (a1+a2)/2.0
|
||||
aveAngleRadians = averageAngle*pi/180.0
|
||||
labelRadius = wedgeStyle.labelRadius
|
||||
labelX = cx + (0.5 * self.width * cos(aveAngleRadians) * labelRadius)
|
||||
labelY = cy + (0.5 * self.height * sin(aveAngleRadians) * labelRadius)
|
||||
_addWedgeLabel(self,text,g.add,averageAngle,labelX,labelY,wedgeStyle)
|
||||
|
||||
startAngle = endAngle
|
||||
i = i + 1
|
||||
|
||||
return g
|
||||
|
||||
def draw(self):
|
||||
g = Group()
|
||||
g.add(self.makeWedges())
|
||||
return g
|
||||
|
||||
class LegendedPie(Pie):
|
||||
"""Pie with a two part legend (one editable with swatches, one hidden without swatches)."""
|
||||
|
||||
_attrMap = AttrMap(BASE=Pie,
|
||||
drawLegend = AttrMapValue(isBoolean, desc="If true then create and draw legend"),
|
||||
legend1 = AttrMapValue(None, desc="Handle to legend for pie"),
|
||||
legendNumberFormat = AttrMapValue(None, desc="Formatting routine for number on right hand side of legend."),
|
||||
legendNumberOffset = AttrMapValue(isNumber, desc="Horizontal space between legend and numbers on r/hand side"),
|
||||
pieAndLegend_colors = AttrMapValue(isListOfColors, desc="Colours used for both swatches and pie"),
|
||||
legend_names = AttrMapValue(isNoneOrListOfNoneOrStrings, desc="Names used in legend (or None)"),
|
||||
legend_data = AttrMapValue(isNoneOrListOfNoneOrNumbers, desc="Numbers used on r/hand side of legend (or None)"),
|
||||
leftPadding = AttrMapValue(isNumber, desc='Padding on left of drawing'),
|
||||
rightPadding = AttrMapValue(isNumber, desc='Padding on right of drawing'),
|
||||
topPadding = AttrMapValue(isNumber, desc='Padding at top of drawing'),
|
||||
bottomPadding = AttrMapValue(isNumber, desc='Padding at bottom of drawing'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
Pie.__init__(self)
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.height = 100
|
||||
self.width = 100
|
||||
self.data = [38.4, 20.7, 18.9, 15.4, 6.6]
|
||||
self.labels = None
|
||||
self.direction = 'clockwise'
|
||||
PCMYKColor, black = colors.PCMYKColor, colors.black
|
||||
self.pieAndLegend_colors = [PCMYKColor(11,11,72,0,spotName='PANTONE 458 CV'),
|
||||
PCMYKColor(100,65,0,30,spotName='PANTONE 288 CV'),
|
||||
PCMYKColor(11,11,72,0,spotName='PANTONE 458 CV',density=75),
|
||||
PCMYKColor(100,65,0,30,spotName='PANTONE 288 CV',density=75),
|
||||
PCMYKColor(11,11,72,0,spotName='PANTONE 458 CV',density=50),
|
||||
PCMYKColor(100,65,0,30,spotName='PANTONE 288 CV',density=50)]
|
||||
|
||||
#Allows us up to six 'wedges' to be coloured
|
||||
self.slices[0].fillColor=self.pieAndLegend_colors[0]
|
||||
self.slices[1].fillColor=self.pieAndLegend_colors[1]
|
||||
self.slices[2].fillColor=self.pieAndLegend_colors[2]
|
||||
self.slices[3].fillColor=self.pieAndLegend_colors[3]
|
||||
self.slices[4].fillColor=self.pieAndLegend_colors[4]
|
||||
self.slices[5].fillColor=self.pieAndLegend_colors[5]
|
||||
|
||||
self.slices.strokeWidth = 0.75
|
||||
self.slices.strokeColor = black
|
||||
|
||||
legendOffset = 17
|
||||
self.legendNumberOffset = 51
|
||||
self.legendNumberFormat = '%.1f%%'
|
||||
self.legend_data = self.data
|
||||
|
||||
#set up the legends
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
self.legend1 = Legend()
|
||||
self.legend1.x = self.width+legendOffset
|
||||
self.legend1.y = self.height
|
||||
self.legend1.deltax = 5.67
|
||||
self.legend1.deltay = 14.17
|
||||
self.legend1.dxTextSpace = 11.39
|
||||
self.legend1.dx = 5.67
|
||||
self.legend1.dy = 5.67
|
||||
self.legend1.columnMaximum = 7
|
||||
self.legend1.alignment = 'right'
|
||||
self.legend_names = ['AAA:','AA:','A:','BBB:','NR:']
|
||||
for f in range(0,len(self.data)):
|
||||
self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f], self.legend_names[f]))
|
||||
self.legend1.fontName = "Helvetica-Bold"
|
||||
self.legend1.fontSize = 6
|
||||
self.legend1.strokeColor = black
|
||||
self.legend1.strokeWidth = 0.5
|
||||
|
||||
self._legend2 = Legend()
|
||||
self._legend2.dxTextSpace = 0
|
||||
self._legend2.dx = 0
|
||||
self._legend2.alignment = 'right'
|
||||
self._legend2.fontName = "Helvetica-Oblique"
|
||||
self._legend2.fontSize = 6
|
||||
self._legend2.strokeColor = self.legend1.strokeColor
|
||||
|
||||
self.leftPadding = 5
|
||||
self.rightPadding = 5
|
||||
self.topPadding = 5
|
||||
self.bottomPadding = 5
|
||||
self.drawLegend = 1
|
||||
|
||||
def draw(self):
|
||||
if self.drawLegend:
|
||||
self.legend1.colorNamePairs = []
|
||||
self._legend2.colorNamePairs = []
|
||||
for f in range(0,len(self.data)):
|
||||
if self.legend_names == None:
|
||||
self.slices[f].fillColor = self.pieAndLegend_colors[f]
|
||||
self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f], None))
|
||||
else:
|
||||
try:
|
||||
self.slices[f].fillColor = self.pieAndLegend_colors[f]
|
||||
self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f], self.legend_names[f]))
|
||||
except IndexError:
|
||||
self.slices[f].fillColor = self.pieAndLegend_colors[f%len(self.pieAndLegend_colors)]
|
||||
self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f%len(self.pieAndLegend_colors)], self.legend_names[f]))
|
||||
if self.legend_data != None:
|
||||
ldf = self.legend_data[f]
|
||||
lNF = self.legendNumberFormat
|
||||
from types import StringType
|
||||
if ldf is None or lNF is None:
|
||||
pass
|
||||
elif type(lNF) is StringType:
|
||||
ldf = lNF % ldf
|
||||
elif callable(lNF):
|
||||
ldf = lNF(ldf)
|
||||
else:
|
||||
p = self.legend_names[f]
|
||||
if self.legend_data != None:
|
||||
ldf = self.legend_data[f]
|
||||
lNF = self.legendNumberFormat
|
||||
if ldf is None or lNF is None:
|
||||
pass
|
||||
elif type(lNF) is StringType:
|
||||
ldf = lNF % ldf
|
||||
elif callable(lNF):
|
||||
ldf = lNF(ldf)
|
||||
else:
|
||||
msg = "Unknown formatter type %s, expected string or function" % self.legendNumberFormat
|
||||
raise Exception, msg
|
||||
self._legend2.colorNamePairs.append((None,ldf))
|
||||
p = Pie.draw(self)
|
||||
if self.drawLegend:
|
||||
p.add(self.legend1)
|
||||
#hide from user - keeps both sides lined up!
|
||||
self._legend2.x = self.legend1.x+self.legendNumberOffset
|
||||
self._legend2.y = self.legend1.y
|
||||
self._legend2.deltax = self.legend1.deltax
|
||||
self._legend2.deltay = self.legend1.deltay
|
||||
self._legend2.dy = self.legend1.dy
|
||||
self._legend2.columnMaximum = self.legend1.columnMaximum
|
||||
p.add(self._legend2)
|
||||
p.shift(self.leftPadding, self.bottomPadding)
|
||||
return p
|
||||
|
||||
def _getDrawingDimensions(self):
|
||||
tx = self.rightPadding
|
||||
if self.drawLegend:
|
||||
tx = tx+self.legend1.x+self.legendNumberOffset #self._legend2.x
|
||||
tx = tx + self._legend2._calculateMaxWidth(self._legend2.colorNamePairs)
|
||||
ty = self.bottomPadding+self.height+self.topPadding
|
||||
return (tx,ty)
|
||||
|
||||
def demo(self, drawing=None):
|
||||
if not drawing:
|
||||
tx,ty = self._getDrawingDimensions()
|
||||
drawing = Drawing(tx, ty)
|
||||
drawing.add(self.draw())
|
||||
return drawing
|
||||
|
||||
from utils3d import _getShaded, _2rad, _360, _pi_2, _2pi
|
||||
class Wedge3dProperties(PropHolder):
|
||||
"""This holds descriptive information about the wedges in a pie chart.
|
||||
|
||||
It is not to be confused with the 'wedge itself'; this just holds
|
||||
a recipe for how to format one, and does not allow you to hack the
|
||||
angles. It can format a genuine Wedge object for you with its
|
||||
format method.
|
||||
"""
|
||||
_attrMap = AttrMap(
|
||||
fillColor = AttrMapValue(isColorOrNone),
|
||||
fillColorShaded = AttrMapValue(isColorOrNone),
|
||||
fontColor = AttrMapValue(isColorOrNone),
|
||||
fontName = AttrMapValue(isString),
|
||||
fontSize = AttrMapValue(isNumber),
|
||||
label_angle = AttrMapValue(isNumber),
|
||||
label_bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'),
|
||||
label_boxAnchor = AttrMapValue(isBoxAnchor),
|
||||
label_boxFillColor = AttrMapValue(isColorOrNone),
|
||||
label_boxStrokeColor = AttrMapValue(isColorOrNone),
|
||||
label_boxStrokeWidth = AttrMapValue(isNumber),
|
||||
label_dx = AttrMapValue(isNumber),
|
||||
label_dy = AttrMapValue(isNumber),
|
||||
label_height = AttrMapValue(isNumberOrNone),
|
||||
label_leading = AttrMapValue(isNumberOrNone),
|
||||
label_leftPadding = AttrMapValue(isNumber,'padding at left of box'),
|
||||
label_maxWidth = AttrMapValue(isNumberOrNone),
|
||||
label_rightPadding = AttrMapValue(isNumber,'padding at right of box'),
|
||||
label_strokeColor = AttrMapValue(isColorOrNone),
|
||||
label_strokeWidth = AttrMapValue(isNumber),
|
||||
label_text = AttrMapValue(isStringOrNone),
|
||||
label_textAnchor = AttrMapValue(isTextAnchor),
|
||||
label_topPadding = AttrMapValue(isNumber,'padding at top of box'),
|
||||
label_visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
|
||||
label_width = AttrMapValue(isNumberOrNone),
|
||||
labelRadius = AttrMapValue(isNumber),
|
||||
popout = AttrMapValue(isNumber),
|
||||
shading = AttrMapValue(isNumber),
|
||||
strokeColor = AttrMapValue(isColorOrNone),
|
||||
strokeColorShaded = AttrMapValue(isColorOrNone),
|
||||
strokeDashArray = AttrMapValue(isListOfNumbersOrNone),
|
||||
strokeWidth = AttrMapValue(isNumber),
|
||||
visible = AttrMapValue(isBoolean,'set to false to skip displaying'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.strokeWidth = 0
|
||||
self.shading = 0.3
|
||||
self.visible = 1
|
||||
self.strokeColorShaded = self.fillColorShaded = self.fillColor = None
|
||||
self.strokeColor = STATE_DEFAULTS["strokeColor"]
|
||||
self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"]
|
||||
self.popout = 0
|
||||
self.fontName = STATE_DEFAULTS["fontName"]
|
||||
self.fontSize = STATE_DEFAULTS["fontSize"]
|
||||
self.fontColor = STATE_DEFAULTS["fillColor"]
|
||||
self.labelRadius = 1.2
|
||||
self.label_dx = self.label_dy = self.label_angle = 0
|
||||
self.label_text = None
|
||||
self.label_topPadding = self.label_leftPadding = self.label_rightPadding = self.label_bottomPadding = 0
|
||||
self.label_boxAnchor = 'c'
|
||||
self.label_boxStrokeColor = None #boxStroke
|
||||
self.label_boxStrokeWidth = 0.5 #boxStrokeWidth
|
||||
self.label_boxFillColor = None
|
||||
self.label_strokeColor = None
|
||||
self.label_strokeWidth = 0.1
|
||||
self.label_leading = self.label_width = self.label_maxWidth = self.label_height = None
|
||||
self.label_textAnchor = 'start'
|
||||
self.label_visible = 1
|
||||
|
||||
class _SL3D:
|
||||
def __init__(self,lo,hi):
|
||||
if lo<0:
|
||||
lo += 360
|
||||
hi += 360
|
||||
self.lo = lo
|
||||
self.hi = hi
|
||||
self.mid = (lo+hi)*0.5
|
||||
|
||||
def __str__(self):
|
||||
return '_SL3D(%.2f,%.2f)' % (self.lo,self.hi)
|
||||
|
||||
_270r = _2rad(270)
|
||||
class Pie3d(Pie):
|
||||
_attrMap = AttrMap(BASE=Pie,
|
||||
perspective = AttrMapValue(isNumber, desc='A flattening parameter.'),
|
||||
depth_3d = AttrMapValue(isNumber, desc='depth of the pie.'),
|
||||
angle_3d = AttrMapValue(isNumber, desc='The view angle.'),
|
||||
)
|
||||
perspective = 70
|
||||
depth_3d = 25
|
||||
angle_3d = 180
|
||||
|
||||
def _popout(self,i):
|
||||
return self.slices[i].popout or 0
|
||||
|
||||
def CX(self, i,d ):
|
||||
return self._cx+(d and self._xdepth_3d or 0)+self._popout(i)*cos(_2rad(self._sl3d[i].mid))
|
||||
def CY(self,i,d):
|
||||
return self._cy+(d and self._ydepth_3d or 0)+self._popout(i)*sin(_2rad(self._sl3d[i].mid))
|
||||
def OX(self,i,o,d):
|
||||
return self.CX(i,d)+self._radiusx*cos(_2rad(o))
|
||||
def OY(self,i,o,d):
|
||||
return self.CY(i,d)+self._radiusy*sin(_2rad(o))
|
||||
|
||||
def rad_dist(self,a):
|
||||
_3dva = self._3dva
|
||||
return min(abs(a-_3dva),abs(a-_3dva+360))
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.width = 300
|
||||
self.height = 200
|
||||
self.data = [12.50,20.10,2.00,22.00,5.00,18.00,13.00]
|
||||
self.labels = None # or list of strings
|
||||
self.startAngle = 90
|
||||
self.direction = "clockwise"
|
||||
self.simpleLabels = 1
|
||||
self.slices = TypedPropertyCollection(Wedge3dProperties)
|
||||
self.slices[0].fillColor = colors.darkcyan
|
||||
self.slices[1].fillColor = colors.blueviolet
|
||||
self.slices[2].fillColor = colors.blue
|
||||
self.slices[3].fillColor = colors.cyan
|
||||
self.slices[4].fillColor = colors.azure
|
||||
self.slices[5].fillColor = colors.crimson
|
||||
self.slices[6].fillColor = colors.darkviolet
|
||||
|
||||
def _fillSide(self,L,i,angle,strokeColor,strokeWidth,fillColor):
|
||||
rd = self.rad_dist(angle)
|
||||
if rd<self.rad_dist(self._sl3d[i].mid):
|
||||
p = [self.CX(i,0),self.CY(i,0),
|
||||
self.CX(i,1),self.CY(i,1),
|
||||
self.OX(i,angle,1),self.OY(i,angle,1),
|
||||
self.OX(i,angle,0),self.OY(i,angle,0)]
|
||||
L.append((rd,Polygon(p, strokeColor=strokeColor, fillColor=fillColor,strokeWidth=strokeWidth,strokeLineJoin=1)))
|
||||
|
||||
def draw(self):
|
||||
slices = self.slices
|
||||
_3d_angle = self.angle_3d
|
||||
_3dva = self._3dva = _360(_3d_angle+90)
|
||||
a0 = _2rad(_3dva)
|
||||
self._xdepth_3d = cos(a0)*self.depth_3d
|
||||
self._ydepth_3d = sin(a0)*self.depth_3d
|
||||
self._cx = self.x+self.width/2.0
|
||||
self._cy = self.y+(self.height - self._ydepth_3d)/2.0
|
||||
radius = self._radius = self._cx-self.x
|
||||
self._radiusx = radiusx = radius
|
||||
self._radiusy = radiusy = (1.0 - self.perspective/100.0)*radius
|
||||
data = self.normalizeData()
|
||||
sum = self._sum
|
||||
|
||||
CX = self.CX
|
||||
CY = self.CY
|
||||
OX = self.OX
|
||||
OY = self.OY
|
||||
rad_dist = self.rad_dist
|
||||
_fillSide = self._fillSide
|
||||
n = len(data)
|
||||
_sl3d = self._sl3d = []
|
||||
g = Group()
|
||||
last = _360(self.startAngle)
|
||||
a0 = self.direction=='clockwise' and -1 or 1
|
||||
for v in data:
|
||||
v *= a0
|
||||
angle1, angle0 = last, v+last
|
||||
last = angle0
|
||||
if a0>0: angle0, angle1 = angle1, angle0
|
||||
_sl3d.append(_SL3D(angle0,angle1))
|
||||
#print '%d: %.2f %.2f --> %s' %(len(_sl3d)-1,angle0,angle1,_sl3d[-1])
|
||||
|
||||
labels = _fixLabels(self.labels,n)
|
||||
a0 = _3d_angle
|
||||
a1 = _3d_angle+180
|
||||
T = []
|
||||
S = []
|
||||
L = []
|
||||
|
||||
class WedgeLabel3d(WedgeLabel):
|
||||
def _checkDXY(self,ba):
|
||||
if ba[0]=='n':
|
||||
if not hasattr(self,'_ody'):
|
||||
self._ody = self.dy
|
||||
self.dy = -self._ody + self._ydepth_3d
|
||||
WedgeLabel3d._ydepth_3d = self._ydepth_3d
|
||||
|
||||
for i in xrange(n):
|
||||
style = slices[i]
|
||||
if not style.visible: continue
|
||||
sl = _sl3d[i]
|
||||
lo = angle0 = sl.lo
|
||||
hi = angle1 = sl.hi
|
||||
if abs(hi-lo)<=1e-7: continue
|
||||
fillColor = _getShaded(style.fillColor,style.fillColorShaded,style.shading)
|
||||
strokeColor = _getShaded(style.strokeColor,style.strokeColorShaded,style.shading) or fillColor
|
||||
strokeWidth = style.strokeWidth
|
||||
cx0 = CX(i,0)
|
||||
cy0 = CY(i,0)
|
||||
cx1 = CX(i,1)
|
||||
cy1 = CY(i,1)
|
||||
#background shaded pie bottom
|
||||
g.add(Wedge(cx1,cy1,radiusx, lo, hi,yradius=radiusy,
|
||||
strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,
|
||||
strokeLineJoin=1))
|
||||
#connect to top
|
||||
if lo < a0 < hi: angle0 = a0
|
||||
if lo < a1 < hi: angle1 = a1
|
||||
if 1:
|
||||
p = ArcPath(strokeColor=strokeColor, fillColor=fillColor,strokeWidth=strokeWidth,strokeLineJoin=1)
|
||||
p.addArc(cx1,cy1,radiusx,angle0,angle1,yradius=radiusy,moveTo=1)
|
||||
p.lineTo(OX(i,angle1,0),OY(i,angle1,0))
|
||||
p.addArc(cx0,cy0,radiusx,angle0,angle1,yradius=radiusy,reverse=1)
|
||||
p.closePath()
|
||||
if angle0<=_3dva and angle1>=_3dva:
|
||||
rd = 0
|
||||
else:
|
||||
rd = min(rad_dist(angle0),rad_dist(angle1))
|
||||
S.append((rd,p))
|
||||
_fillSide(S,i,lo,strokeColor,strokeWidth,fillColor)
|
||||
_fillSide(S,i,hi,strokeColor,strokeWidth,fillColor)
|
||||
|
||||
#bright shaded top
|
||||
fillColor = style.fillColor
|
||||
strokeColor = style.strokeColor or fillColor
|
||||
T.append(Wedge(cx0,cy0,radiusx,lo,hi,yradius=radiusy,
|
||||
strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,strokeLineJoin=1))
|
||||
|
||||
text = labels[i]
|
||||
if text:
|
||||
rat = style.labelRadius
|
||||
self._radiusx *= rat
|
||||
self._radiusy *= rat
|
||||
mid = sl.mid
|
||||
_addWedgeLabel(self,text,L.append,mid,OX(i,mid,0),OY(i,mid,0),style,labelClass=WedgeLabel3d)
|
||||
self._radiusx = radiusx
|
||||
self._radiusy = radiusy
|
||||
|
||||
S.sort(lambda a,b: -cmp(a[0],b[0]))
|
||||
map(g.add,map(lambda x:x[1],S)+T+L)
|
||||
return g
|
||||
|
||||
def demo(self):
|
||||
d = Drawing(200, 100)
|
||||
|
||||
pc = Pie()
|
||||
pc.x = 50
|
||||
pc.y = 10
|
||||
pc.width = 100
|
||||
pc.height = 80
|
||||
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
|
||||
pc.slices[0].fillColor = colors.darkcyan
|
||||
pc.slices[1].fillColor = colors.blueviolet
|
||||
pc.slices[2].fillColor = colors.blue
|
||||
pc.slices[3].fillColor = colors.cyan
|
||||
pc.slices[4].fillColor = colors.aquamarine
|
||||
pc.slices[5].fillColor = colors.cadetblue
|
||||
pc.slices[6].fillColor = colors.lightcoral
|
||||
self.slices[1].visible = 0
|
||||
self.slices[3].visible = 1
|
||||
self.slices[4].visible = 1
|
||||
self.slices[5].visible = 1
|
||||
self.slices[6].visible = 0
|
||||
|
||||
d.add(pc)
|
||||
return d
|
||||
|
||||
|
||||
def sample0a():
|
||||
"Make a degenerated pie chart with only one slice."
|
||||
|
||||
d = Drawing(400, 200)
|
||||
|
||||
pc = Pie()
|
||||
pc.x = 150
|
||||
pc.y = 50
|
||||
pc.data = [10]
|
||||
pc.labels = ['a']
|
||||
pc.slices.strokeWidth=1#0.5
|
||||
|
||||
d.add(pc)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def sample0b():
|
||||
"Make a degenerated pie chart with only one slice."
|
||||
|
||||
d = Drawing(400, 200)
|
||||
|
||||
pc = Pie()
|
||||
pc.x = 150
|
||||
pc.y = 50
|
||||
pc.width = 120
|
||||
pc.height = 100
|
||||
pc.data = [10]
|
||||
pc.labels = ['a']
|
||||
pc.slices.strokeWidth=1#0.5
|
||||
|
||||
d.add(pc)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def sample1():
|
||||
"Make a typical pie chart with with one slice treated in a special way."
|
||||
|
||||
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=1#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)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def sample2():
|
||||
"Make a pie chart with nine slices."
|
||||
|
||||
d = Drawing(400, 200)
|
||||
|
||||
pc = Pie()
|
||||
pc.x = 125
|
||||
pc.y = 25
|
||||
pc.data = [0.31, 0.148, 0.108,
|
||||
0.076, 0.033, 0.03,
|
||||
0.019, 0.126, 0.15]
|
||||
pc.labels = ['1', '2', '3', '4', '5', '6', '7', '8', 'X']
|
||||
|
||||
pc.width = 150
|
||||
pc.height = 150
|
||||
pc.slices.strokeWidth=1#0.5
|
||||
|
||||
pc.slices[0].fillColor = colors.steelblue
|
||||
pc.slices[1].fillColor = colors.thistle
|
||||
pc.slices[2].fillColor = colors.cornflower
|
||||
pc.slices[3].fillColor = colors.lightsteelblue
|
||||
pc.slices[4].fillColor = colors.aquamarine
|
||||
pc.slices[5].fillColor = colors.cadetblue
|
||||
pc.slices[6].fillColor = colors.lightcoral
|
||||
pc.slices[7].fillColor = colors.tan
|
||||
pc.slices[8].fillColor = colors.darkseagreen
|
||||
|
||||
d.add(pc)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def sample3():
|
||||
"Make a pie chart with a very slim slice."
|
||||
|
||||
d = Drawing(400, 200)
|
||||
|
||||
pc = Pie()
|
||||
pc.x = 125
|
||||
pc.y = 25
|
||||
|
||||
pc.data = [74, 1, 25]
|
||||
|
||||
pc.width = 150
|
||||
pc.height = 150
|
||||
pc.slices.strokeWidth=1#0.5
|
||||
pc.slices[0].fillColor = colors.steelblue
|
||||
pc.slices[1].fillColor = colors.thistle
|
||||
pc.slices[2].fillColor = colors.cornflower
|
||||
|
||||
d.add(pc)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def sample4():
|
||||
"Make a pie chart with several very slim slices."
|
||||
|
||||
d = Drawing(400, 200)
|
||||
|
||||
pc = Pie()
|
||||
pc.x = 125
|
||||
pc.y = 25
|
||||
|
||||
pc.data = [74, 1, 1, 1, 1, 22]
|
||||
|
||||
pc.width = 150
|
||||
pc.height = 150
|
||||
pc.slices.strokeWidth=1#0.5
|
||||
pc.slices[0].fillColor = colors.steelblue
|
||||
pc.slices[1].fillColor = colors.thistle
|
||||
pc.slices[2].fillColor = colors.cornflower
|
||||
pc.slices[3].fillColor = colors.lightsteelblue
|
||||
pc.slices[4].fillColor = colors.aquamarine
|
||||
pc.slices[5].fillColor = colors.cadetblue
|
||||
|
||||
d.add(pc)
|
||||
|
||||
return d
|
|
@ -1,186 +0,0 @@
|
|||
from reportlab.lib.colors import Color, white, black
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from reportlab.graphics.shapes import Polygon, Line, Circle, String, Drawing, PolyLine, Group, Rect
|
||||
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.lib.validators import *
|
||||
from reportlab.lib.units import cm
|
||||
from reportlab.pdfbase.pdfmetrics import stringWidth, getFont
|
||||
from reportlab.graphics.widgets.grids import ShadedRect, Grid
|
||||
|
||||
class SlideBox(Widget):
|
||||
"""Returns a slidebox widget"""
|
||||
_attrMap = AttrMap(
|
||||
labelFontName = AttrMapValue(isString, desc="Name of font used for the labels"),
|
||||
labelFontSize = AttrMapValue(isNumber, desc="Size of font used for the labels"),
|
||||
labelStrokeColor = AttrMapValue(isColorOrNone, desc="Colour for for number outlines"),
|
||||
labelFillColor = AttrMapValue(isColorOrNone, desc="Colour for number insides"),
|
||||
startColor = AttrMapValue(isColor, desc='Color of first box'),
|
||||
endColor = AttrMapValue(isColor, desc='Color of last box'),
|
||||
numberOfBoxes = AttrMapValue(isInt, desc='How many boxes there are'),
|
||||
trianglePosition = AttrMapValue(isInt, desc='Which box is highlighted by the triangles'),
|
||||
triangleHeight = AttrMapValue(isNumber, desc="Height of indicator triangles"),
|
||||
triangleWidth = AttrMapValue(isNumber, desc="Width of indicator triangles"),
|
||||
triangleFillColor = AttrMapValue(isColor, desc="Colour of indicator triangles"),
|
||||
triangleStrokeColor = AttrMapValue(isColorOrNone, desc="Colour of indicator triangle outline"),
|
||||
triangleStrokeWidth = AttrMapValue(isNumber, desc="Colour of indicator triangle outline"),
|
||||
boxHeight = AttrMapValue(isNumber, desc="Height of the boxes"),
|
||||
boxWidth = AttrMapValue(isNumber, desc="Width of the boxes"),
|
||||
boxSpacing = AttrMapValue(isNumber, desc="Space between the boxes"),
|
||||
boxOutlineColor = AttrMapValue(isColorOrNone, desc="Colour used to outline the boxes (if any)"),
|
||||
boxOutlineWidth = AttrMapValue(isNumberOrNone, desc="Width of the box outline (if any)"),
|
||||
leftPadding = AttrMapValue(isNumber, desc='Padding on left of drawing'),
|
||||
rightPadding = AttrMapValue(isNumber, desc='Padding on right of drawing'),
|
||||
topPadding = AttrMapValue(isNumber, desc='Padding at top of drawing'),
|
||||
bottomPadding = AttrMapValue(isNumber, desc='Padding at bottom of drawing'),
|
||||
background = AttrMapValue(isColorOrNone, desc='Colour of the background to the drawing (if any)'),
|
||||
sourceLabelText = AttrMapValue(isNoneOrString, desc="Text used for the 'source' label (can be empty)"),
|
||||
sourceLabelOffset = AttrMapValue(isNumber, desc='Padding at bottom of drawing'),
|
||||
sourceLabelFontName = AttrMapValue(isString, desc="Name of font used for the 'source' label"),
|
||||
sourceLabelFontSize = AttrMapValue(isNumber, desc="Font size for the 'source' label"),
|
||||
sourceLabelFillColor = AttrMapValue(isColorOrNone, desc="Colour ink for the 'source' label (bottom right)"),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.labelFontName = "Helvetica-Bold"
|
||||
self.labelFontSize = 10
|
||||
self.labelStrokeColor = black
|
||||
self.labelFillColor = white
|
||||
self.startColor = colors.Color(232/255.0,224/255.0,119/255.0)
|
||||
self.endColor = colors.Color(25/255.0,77/255.0,135/255.0)
|
||||
self.numberOfBoxes = 7
|
||||
self.trianglePosition = 7
|
||||
self.triangleHeight = 0.12*cm
|
||||
self.triangleWidth = 0.38*cm
|
||||
self.triangleFillColor = white
|
||||
self.triangleStrokeColor = black
|
||||
self.triangleStrokeWidth = 0.58
|
||||
self.boxHeight = 0.55*cm
|
||||
self.boxWidth = 0.73*cm
|
||||
self.boxSpacing = 0.075*cm
|
||||
self.boxOutlineColor = black
|
||||
self.boxOutlineWidth = 0.58
|
||||
self.leftPadding=5
|
||||
self.rightPadding=5
|
||||
self.topPadding=5
|
||||
self.bottomPadding=5
|
||||
self.background=None
|
||||
self.sourceLabelText = "Source: ReportLab"
|
||||
self.sourceLabelOffset = 0.2*cm
|
||||
self.sourceLabelFontName = "Helvetica-Oblique"
|
||||
self.sourceLabelFontSize = 6
|
||||
self.sourceLabelFillColor = black
|
||||
|
||||
def _getDrawingDimensions(self):
|
||||
tx=(self.numberOfBoxes*self.boxWidth)
|
||||
if self.numberOfBoxes>1: tx=tx+((self.numberOfBoxes-1)*self.boxSpacing)
|
||||
tx=tx+self.leftPadding+self.rightPadding
|
||||
ty=self.boxHeight+self.triangleHeight
|
||||
ty=ty+self.topPadding+self.bottomPadding+self.sourceLabelOffset+self.sourceLabelFontSize
|
||||
return (tx,ty)
|
||||
|
||||
def _getColors(self):
|
||||
# for calculating intermediate colors...
|
||||
numShades = self.numberOfBoxes+1
|
||||
fillColorStart = self.startColor
|
||||
fillColorEnd = self.endColor
|
||||
colorsList =[]
|
||||
|
||||
for i in range(0,numShades):
|
||||
colorsList.append(colors.linearlyInterpolatedColor(fillColorStart, fillColorEnd, 0, numShades-1, i))
|
||||
return colorsList
|
||||
|
||||
def demo(self,drawing=None):
|
||||
from reportlab.lib import colors
|
||||
if not drawing:
|
||||
tx,ty=self._getDrawingDimensions()
|
||||
drawing = Drawing(tx,ty)
|
||||
drawing.add(self.draw())
|
||||
return drawing
|
||||
|
||||
def draw(self):
|
||||
g = Group()
|
||||
ys = self.bottomPadding+(self.triangleHeight/2)+self.sourceLabelOffset+self.sourceLabelFontSize
|
||||
if self.background:
|
||||
x,y = self._getDrawingDimensions()
|
||||
g.add(Rect(-self.leftPadding,-ys,x,y,
|
||||
strokeColor=None,
|
||||
strokeWidth=0,
|
||||
fillColor=self.background))
|
||||
|
||||
ascent=getFont(self.labelFontName).face.ascent/1000.
|
||||
if ascent==0: ascent=0.718 # default (from helvetica)
|
||||
ascent=ascent*self.labelFontSize # normalize
|
||||
|
||||
colorsList = self._getColors()
|
||||
|
||||
# Draw the boxes - now uses ShadedRect from grids
|
||||
x=0
|
||||
for f in range (0,self.numberOfBoxes):
|
||||
sr=ShadedRect()
|
||||
sr.x=x
|
||||
sr.y=0
|
||||
sr.width=self.boxWidth
|
||||
sr.height=self.boxHeight
|
||||
sr.orientation = 'vertical'
|
||||
sr.numShades = 30
|
||||
sr.fillColorStart = colorsList[f]
|
||||
sr.fillColorEnd = colorsList[f+1]
|
||||
sr.strokeColor = None
|
||||
sr.strokeWidth = 0
|
||||
|
||||
g.add(sr)
|
||||
|
||||
g.add(Rect(x,0,self.boxWidth,self.boxHeight,
|
||||
strokeColor=self.boxOutlineColor,
|
||||
strokeWidth=self.boxOutlineWidth,
|
||||
fillColor=None))
|
||||
|
||||
g.add(String(x+self.boxWidth/2.,(self.boxHeight-ascent)/2.,
|
||||
text = str(f+1),
|
||||
fillColor = self.labelFillColor,
|
||||
strokeColor=self.labelStrokeColor,
|
||||
textAnchor = 'middle',
|
||||
fontName = self.labelFontName,
|
||||
fontSize = self.labelFontSize))
|
||||
x=x+self.boxWidth+self.boxSpacing
|
||||
|
||||
#do triangles
|
||||
xt = (self.trianglePosition*self.boxWidth)
|
||||
if self.trianglePosition>1:
|
||||
xt = xt+(self.trianglePosition-1)*self.boxSpacing
|
||||
xt = xt-(self.boxWidth/2)
|
||||
g.add(Polygon(
|
||||
strokeColor = self.triangleStrokeColor,
|
||||
strokeWidth = self.triangleStrokeWidth,
|
||||
fillColor = self.triangleFillColor,
|
||||
points=[xt,self.boxHeight-(self.triangleHeight/2),
|
||||
xt-(self.triangleWidth/2),self.boxHeight+(self.triangleHeight/2),
|
||||
xt+(self.triangleWidth/2),self.boxHeight+(self.triangleHeight/2),
|
||||
xt,self.boxHeight-(self.triangleHeight/2)]))
|
||||
g.add(Polygon(
|
||||
strokeColor = self.triangleStrokeColor,
|
||||
strokeWidth = self.triangleStrokeWidth,
|
||||
fillColor = self.triangleFillColor,
|
||||
points=[xt,0+(self.triangleHeight/2),
|
||||
xt-(self.triangleWidth/2),0-(self.triangleHeight/2),
|
||||
xt+(self.triangleWidth/2),0-(self.triangleHeight/2),
|
||||
xt,0+(self.triangleHeight/2)]))
|
||||
|
||||
#source label
|
||||
if self.sourceLabelText != None:
|
||||
g.add(String(x-self.boxSpacing,0-(self.triangleHeight/2)-self.sourceLabelOffset-(self.sourceLabelFontSize),
|
||||
text = self.sourceLabelText,
|
||||
fillColor = self.sourceLabelFillColor,
|
||||
textAnchor = 'end',
|
||||
fontName = self.sourceLabelFontName,
|
||||
fontSize = self.sourceLabelFontSize))
|
||||
|
||||
g.shift(self.leftPadding, ys)
|
||||
|
||||
return g
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
d = SlideBox()
|
||||
d.demo().save(fnRoot="slidebox")
|
|
@ -1,353 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/spider.py
|
||||
# spider chart, also known as radar chart
|
||||
|
||||
"""Spider Chart
|
||||
|
||||
Normal use shows variation of 5-10 parameters against some 'norm' or target.
|
||||
When there is more than one series, place the series with the largest
|
||||
numbers first, as it will be overdrawn by each successive one.
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
import copy
|
||||
from math import sin, cos, pi
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.validators import isColor, isNumber, isListOfNumbersOrNone,\
|
||||
isListOfNumbers, isColorOrNone, isString,\
|
||||
isListOfStringsOrNone, OneOf, SequenceOf,\
|
||||
isBoolean, isListOfColors, isNumberOrNone,\
|
||||
isNoneOrListOfNoneOrStrings, isTextAnchor,\
|
||||
isNoneOrListOfNoneOrNumbers, isBoxAnchor,\
|
||||
isStringOrNone
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.graphics.shapes import Group, Drawing, Line, Rect, Polygon, Ellipse, \
|
||||
Wedge, String, STATE_DEFAULTS
|
||||
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
|
||||
from reportlab.graphics.charts.areas import PlotArea
|
||||
from piecharts import WedgeLabel
|
||||
from reportlab.graphics.widgets.markers import makeMarker, uSymbol2Symbol
|
||||
|
||||
class StrandProperties(PropHolder):
|
||||
"""This holds descriptive information about concentric 'strands'.
|
||||
|
||||
Line style, whether filled etc.
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(
|
||||
strokeWidth = AttrMapValue(isNumber),
|
||||
fillColor = AttrMapValue(isColorOrNone),
|
||||
strokeColor = AttrMapValue(isColorOrNone),
|
||||
strokeDashArray = AttrMapValue(isListOfNumbersOrNone),
|
||||
fontName = AttrMapValue(isString),
|
||||
fontSize = AttrMapValue(isNumber),
|
||||
fontColor = AttrMapValue(isColorOrNone),
|
||||
labelRadius = AttrMapValue(isNumber),
|
||||
markers = AttrMapValue(isBoolean),
|
||||
markerType = AttrMapValue(isAnything),
|
||||
markerSize = AttrMapValue(isNumber),
|
||||
label_dx = AttrMapValue(isNumber),
|
||||
label_dy = AttrMapValue(isNumber),
|
||||
label_angle = AttrMapValue(isNumber),
|
||||
label_boxAnchor = AttrMapValue(isBoxAnchor),
|
||||
label_boxStrokeColor = AttrMapValue(isColorOrNone),
|
||||
label_boxStrokeWidth = AttrMapValue(isNumber),
|
||||
label_boxFillColor = AttrMapValue(isColorOrNone),
|
||||
label_strokeColor = AttrMapValue(isColorOrNone),
|
||||
label_strokeWidth = AttrMapValue(isNumber),
|
||||
label_text = AttrMapValue(isStringOrNone),
|
||||
label_leading = AttrMapValue(isNumberOrNone),
|
||||
label_width = AttrMapValue(isNumberOrNone),
|
||||
label_maxWidth = AttrMapValue(isNumberOrNone),
|
||||
label_height = AttrMapValue(isNumberOrNone),
|
||||
label_textAnchor = AttrMapValue(isTextAnchor),
|
||||
label_visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
|
||||
label_topPadding = AttrMapValue(isNumber,'padding at top of box'),
|
||||
label_leftPadding = AttrMapValue(isNumber,'padding at left of box'),
|
||||
label_rightPadding = AttrMapValue(isNumber,'padding at right of box'),
|
||||
label_bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.strokeWidth = 0
|
||||
self.fillColor = None
|
||||
self.strokeColor = STATE_DEFAULTS["strokeColor"]
|
||||
self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"]
|
||||
self.fontName = STATE_DEFAULTS["fontName"]
|
||||
self.fontSize = STATE_DEFAULTS["fontSize"]
|
||||
self.fontColor = STATE_DEFAULTS["fillColor"]
|
||||
self.labelRadius = 1.2
|
||||
self.markers = 0
|
||||
self.markerType = None
|
||||
self.markerSize = 0
|
||||
self.label_dx = self.label_dy = self.label_angle = 0
|
||||
self.label_text = None
|
||||
self.label_topPadding = self.label_leftPadding = self.label_rightPadding = self.label_bottomPadding = 0
|
||||
self.label_boxAnchor = 'c'
|
||||
self.label_boxStrokeColor = None #boxStroke
|
||||
self.label_boxStrokeWidth = 0.5 #boxStrokeWidth
|
||||
self.label_boxFillColor = None
|
||||
self.label_strokeColor = None
|
||||
self.label_strokeWidth = 0.1
|
||||
self.label_leading = self.label_width = self.label_maxWidth = self.label_height = None
|
||||
self.label_textAnchor = 'start'
|
||||
self.label_visible = 1
|
||||
|
||||
class SpiderChart(PlotArea):
|
||||
_attrMap = AttrMap(BASE=PlotArea,
|
||||
data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'),
|
||||
labels = AttrMapValue(isListOfStringsOrNone, desc="optional list of labels to use for each data point"),
|
||||
startAngle = AttrMapValue(isNumber, desc="angle of first slice; like the compass, 0 is due North"),
|
||||
direction = AttrMapValue( OneOf('clockwise', 'anticlockwise'), desc="'clockwise' or 'anticlockwise'"),
|
||||
strands = AttrMapValue(None, desc="collection of strand descriptor objects"),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
PlotArea.__init__(self)
|
||||
|
||||
self.data = [[10,12,14,16,14,12], [6,8,10,12,9,11]]
|
||||
self.labels = None # or list of strings
|
||||
self.startAngle = 90
|
||||
self.direction = "clockwise"
|
||||
|
||||
self.strands = TypedPropertyCollection(StrandProperties)
|
||||
self.strands[0].fillColor = colors.cornsilk
|
||||
self.strands[1].fillColor = colors.cyan
|
||||
|
||||
|
||||
def demo(self):
|
||||
d = Drawing(200, 100)
|
||||
|
||||
sp = SpiderChart()
|
||||
sp.x = 50
|
||||
sp.y = 10
|
||||
sp.width = 100
|
||||
sp.height = 80
|
||||
sp.data = [[10,12,14,16,18,20],[6,8,4,6,8,10]]
|
||||
sp.labels = ['a','b','c','d','e','f']
|
||||
|
||||
d.add(sp)
|
||||
return d
|
||||
|
||||
def normalizeData(self, outer = 0.0):
|
||||
"""Turns data into normalized ones where each datum is < 1.0,
|
||||
and 1.0 = maximum radius. Adds 10% at outside edge by default"""
|
||||
data = self.data
|
||||
theMax = 0.0
|
||||
for row in data:
|
||||
for element in row:
|
||||
assert element >=0, "Cannot do spider plots of negative numbers!"
|
||||
if element > theMax:
|
||||
theMax = element
|
||||
theMax = theMax * (1.0+outer)
|
||||
|
||||
scaled = []
|
||||
for row in data:
|
||||
scaledRow = []
|
||||
for element in row:
|
||||
scaledRow.append(element / theMax)
|
||||
scaled.append(scaledRow)
|
||||
return scaled
|
||||
|
||||
|
||||
def draw(self):
|
||||
# normalize slice data
|
||||
g = self.makeBackground() or Group()
|
||||
|
||||
xradius = self.width/2.0
|
||||
yradius = self.height/2.0
|
||||
self._radius = radius = min(xradius, yradius)
|
||||
centerx = self.x + xradius
|
||||
centery = self.y + yradius
|
||||
|
||||
data = self.normalizeData()
|
||||
|
||||
n = len(data[0])
|
||||
|
||||
#labels
|
||||
if self.labels is None:
|
||||
labels = [''] * n
|
||||
else:
|
||||
labels = self.labels
|
||||
#there's no point in raising errors for less than enough errors if
|
||||
#we silently create all for the extreme case of no labels.
|
||||
i = n-len(labels)
|
||||
if i>0:
|
||||
labels = labels + ['']*i
|
||||
|
||||
spokes = []
|
||||
csa = []
|
||||
angle = self.startAngle*pi/180
|
||||
direction = self.direction == "clockwise" and -1 or 1
|
||||
angleBetween = direction*(2 * pi)/n
|
||||
markers = self.strands.markers
|
||||
for i in xrange(n):
|
||||
car = cos(angle)*radius
|
||||
sar = sin(angle)*radius
|
||||
csa.append((car,sar,angle))
|
||||
spoke = Line(centerx, centery, centerx + car, centery + sar, strokeWidth = 0.5)
|
||||
#print 'added spoke (%0.2f, %0.2f) -> (%0.2f, %0.2f)' % (spoke.x1, spoke.y1, spoke.x2, spoke.y2)
|
||||
spokes.append(spoke)
|
||||
if labels:
|
||||
si = self.strands[i]
|
||||
text = si.label_text
|
||||
if text is None: text = labels[i]
|
||||
if text:
|
||||
labelRadius = si.labelRadius
|
||||
L = WedgeLabel()
|
||||
L.x = centerx + labelRadius*car
|
||||
L.y = centery + labelRadius*sar
|
||||
L.boxAnchor = si.label_boxAnchor
|
||||
L._pmv = angle*180/pi
|
||||
L.dx = si.label_dx
|
||||
L.dy = si.label_dy
|
||||
L.angle = si.label_angle
|
||||
L.boxAnchor = si.label_boxAnchor
|
||||
L.boxStrokeColor = si.label_boxStrokeColor
|
||||
L.boxStrokeWidth = si.label_boxStrokeWidth
|
||||
L.boxFillColor = si.label_boxFillColor
|
||||
L.strokeColor = si.label_strokeColor
|
||||
L.strokeWidth = si.label_strokeWidth
|
||||
L._text = text
|
||||
L.leading = si.label_leading
|
||||
L.width = si.label_width
|
||||
L.maxWidth = si.label_maxWidth
|
||||
L.height = si.label_height
|
||||
L.textAnchor = si.label_textAnchor
|
||||
L.visible = si.label_visible
|
||||
L.topPadding = si.label_topPadding
|
||||
L.leftPadding = si.label_leftPadding
|
||||
L.rightPadding = si.label_rightPadding
|
||||
L.bottomPadding = si.label_bottomPadding
|
||||
L.fontName = si.fontName
|
||||
L.fontSize = si.fontSize
|
||||
L.fillColor = si.fontColor
|
||||
spokes.append(L)
|
||||
angle = angle + angleBetween
|
||||
|
||||
# now plot the polygons
|
||||
|
||||
rowIdx = 0
|
||||
for row in data:
|
||||
# series plot
|
||||
points = []
|
||||
car, sar = csa[-1][:2]
|
||||
r = row[-1]
|
||||
points.append(centerx+car*r)
|
||||
points.append(centery+sar*r)
|
||||
for i in xrange(n):
|
||||
car, sar = csa[i][:2]
|
||||
r = row[i]
|
||||
points.append(centerx+car*r)
|
||||
points.append(centery+sar*r)
|
||||
|
||||
# make up the 'strand'
|
||||
strand = Polygon(points)
|
||||
strand.fillColor = self.strands[rowIdx].fillColor
|
||||
strand.strokeColor = self.strands[rowIdx].strokeColor
|
||||
strand.strokeWidth = self.strands[rowIdx].strokeWidth
|
||||
strand.strokeDashArray = self.strands[rowIdx].strokeDashArray
|
||||
|
||||
g.add(strand)
|
||||
|
||||
# put in a marker, if it needs one
|
||||
if markers:
|
||||
if hasattr(self.strands[rowIdx], 'markerType'):
|
||||
uSymbol = self.strands[rowIdx].markerType
|
||||
elif hasattr(self.strands, 'markerType'):
|
||||
uSymbol = self.strands.markerType
|
||||
else:
|
||||
uSymbol = None
|
||||
m_x = centerx+car*r
|
||||
m_y = centery+sar*r
|
||||
m_size = self.strands[rowIdx].markerSize
|
||||
m_fillColor = self.strands[rowIdx].fillColor
|
||||
m_strokeColor = self.strands[rowIdx].strokeColor
|
||||
m_strokeWidth = self.strands[rowIdx].strokeWidth
|
||||
m_angle = 0
|
||||
if type(uSymbol) is type(''):
|
||||
symbol = makeMarker(uSymbol,
|
||||
size = m_size,
|
||||
x = m_x,
|
||||
y = m_y,
|
||||
fillColor = m_fillColor,
|
||||
strokeColor = m_strokeColor,
|
||||
strokeWidth = m_strokeWidth,
|
||||
angle = m_angle,
|
||||
)
|
||||
else:
|
||||
symbol = uSymbol2Symbol(uSymbol,m_x,m_y,m_fillColor)
|
||||
for k,v in (('size', m_size), ('fillColor', m_fillColor),
|
||||
('x', m_x), ('y', m_y),
|
||||
('strokeColor',m_strokeColor), ('strokeWidth',m_strokeWidth),
|
||||
('angle',m_angle),):
|
||||
try:
|
||||
setattr(uSymbol,k,v)
|
||||
except:
|
||||
pass
|
||||
g.add(symbol)
|
||||
|
||||
rowIdx = rowIdx + 1
|
||||
|
||||
# spokes go over strands
|
||||
for spoke in spokes:
|
||||
g.add(spoke)
|
||||
return g
|
||||
|
||||
def sample1():
|
||||
"Make a simple spider chart"
|
||||
|
||||
d = Drawing(400, 400)
|
||||
|
||||
pc = SpiderChart()
|
||||
pc.x = 50
|
||||
pc.y = 50
|
||||
pc.width = 300
|
||||
pc.height = 300
|
||||
pc.data = [[10,12,14,16,14,12], [6,8,10,12,9,15],[7,8,17,4,12,8,3]]
|
||||
pc.labels = ['a','b','c','d','e','f']
|
||||
pc.strands[2].fillColor=colors.palegreen
|
||||
|
||||
d.add(pc)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def sample2():
|
||||
"Make a spider chart with markers, but no fill"
|
||||
|
||||
d = Drawing(400, 400)
|
||||
|
||||
pc = SpiderChart()
|
||||
pc.x = 50
|
||||
pc.y = 50
|
||||
pc.width = 300
|
||||
pc.height = 300
|
||||
pc.data = [[10,12,14,16,14,12], [6,8,10,12,9,15],[7,8,17,4,12,8,3]]
|
||||
pc.labels = ['U','V','W','X','Y','Z']
|
||||
pc.strands.strokeWidth = 2
|
||||
pc.strands[0].fillColor = None
|
||||
pc.strands[1].fillColor = None
|
||||
pc.strands[2].fillColor = None
|
||||
pc.strands[0].strokeColor = colors.red
|
||||
pc.strands[1].strokeColor = colors.blue
|
||||
pc.strands[2].strokeColor = colors.green
|
||||
pc.strands.markers = 1
|
||||
pc.strands.markerType = "FilledDiamond"
|
||||
pc.strands.markerSize = 6
|
||||
|
||||
d.add(pc)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
d = sample1()
|
||||
from reportlab.graphics.renderPDF import drawToFile
|
||||
drawToFile(d, 'spider.pdf')
|
||||
d = sample2()
|
||||
drawToFile(d, 'spider2.pdf')
|
||||
#print 'saved spider.pdf'
|
|
@ -1,440 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/textlabels.py
|
||||
__version__=''' $Id$ '''
|
||||
import string
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.validators import isNumber, isNumberOrNone, OneOf, isColorOrNone, isString, \
|
||||
isTextAnchor, isBoxAnchor, isBoolean, NoneOr, isInstanceOf, isNoneOrString
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.pdfbase.pdfmetrics import stringWidth
|
||||
from reportlab.graphics.shapes import Drawing, Group, Circle, Rect, String, STATE_DEFAULTS
|
||||
from reportlab.graphics.shapes import _PATH_OP_ARG_COUNT, _PATH_OP_NAMES, definePath
|
||||
from reportlab.graphics.widgetbase import Widget, PropHolder
|
||||
|
||||
_gs = None
|
||||
_A2BA= {
|
||||
'x': {0:'n', 45:'ne', 90:'e', 135:'se', 180:'s', 225:'sw', 270:'w', 315: 'nw', -45: 'nw'},
|
||||
'y': {0:'e', 45:'se', 90:'s', 135:'sw', 180:'w', 225:'nw', 270:'n', 315: 'ne', -45: 'ne'},
|
||||
}
|
||||
def _simpleSplit(txt,mW,SW):
|
||||
L = []
|
||||
ws = SW(' ')
|
||||
O = []
|
||||
w = -ws
|
||||
for t in string.split(txt):
|
||||
lt = SW(t)
|
||||
if w+ws+lt<=mW or O==[]:
|
||||
O.append(t)
|
||||
w = w + ws + lt
|
||||
else:
|
||||
L.append(string.join(O,' '))
|
||||
O = [t]
|
||||
w = lt
|
||||
if O!=[]: L.append(string.join(O,' '))
|
||||
return L
|
||||
|
||||
def _pathNumTrunc(n):
|
||||
if int(n)==n: return int(n)
|
||||
return round(n,5)
|
||||
|
||||
def _processGlyph(G, truncate=1, pathReverse=0):
|
||||
O = []
|
||||
P = []
|
||||
R = []
|
||||
for g in G+(('end',),):
|
||||
op = g[0]
|
||||
if O and op in ['moveTo', 'moveToClosed','end']:
|
||||
if O[0]=='moveToClosed':
|
||||
O = O[1:]
|
||||
if pathReverse:
|
||||
for i in xrange(0,len(P),2):
|
||||
P[i+1], P[i] = P[i:i+2]
|
||||
P.reverse()
|
||||
O.reverse()
|
||||
O.insert(0,'moveTo')
|
||||
O.append('closePath')
|
||||
i = 0
|
||||
if truncate: P = map(_pathNumTrunc,P)
|
||||
for o in O:
|
||||
j = i + _PATH_OP_ARG_COUNT[_PATH_OP_NAMES.index(o)]
|
||||
if o=='closePath':
|
||||
R.append(o)
|
||||
else:
|
||||
R.append((o,)+ tuple(P[i:j]))
|
||||
i = j
|
||||
O = []
|
||||
P = []
|
||||
O.append(op)
|
||||
P.extend(g[1:])
|
||||
return R
|
||||
|
||||
def _text2PathDescription(text, x=0, y=0, fontName='Times-Roman', fontSize=1000,
|
||||
anchor='start', truncate=1, pathReverse=0):
|
||||
global _gs
|
||||
if not _gs:
|
||||
import _renderPM
|
||||
_gs = _renderPM.gstate(1,1)
|
||||
from reportlab.graphics import renderPM
|
||||
renderPM._setFont(_gs,fontName,fontSize)
|
||||
P = []
|
||||
if not anchor =='start':
|
||||
textLen = stringWidth(text, fontName,fontSize)
|
||||
if text_anchor=='end':
|
||||
x = x-textLen
|
||||
elif text_anchor=='middle':
|
||||
x = x - textLen/2.
|
||||
for g in _gs._stringPath(text,x,y):
|
||||
P.extend(_processGlyph(g,truncate=truncate,pathReverse=pathReverse))
|
||||
return P
|
||||
|
||||
def _text2Path(text, x=0, y=0, fontName='Times-Roman', fontSize=1000,
|
||||
anchor='start', truncate=1, pathReverse=0):
|
||||
return definePath(_text2PathDescription(text,x=x,y=y,fontName=fontName,
|
||||
fontSize=fontSize,anchor=anchor,truncate=truncate,pathReverse=pathReverse))
|
||||
|
||||
_BA2TA={'w':'start','nw':'start','sw':'start','e':'end', 'ne': 'end', 'se':'end', 'n':'middle','s':'middle','c':'middle'}
|
||||
class Label(Widget):
|
||||
"""A text label to attach to something else, such as a chart axis.
|
||||
|
||||
This allows you to specify an offset, angle and many anchor
|
||||
properties relative to the label's origin. It allows, for example,
|
||||
angled multiline axis labels.
|
||||
"""
|
||||
# fairly straight port of Robin Becker's textbox.py to new widgets
|
||||
# framework.
|
||||
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber),
|
||||
y = AttrMapValue(isNumber),
|
||||
dx = AttrMapValue(isNumber),
|
||||
dy = AttrMapValue(isNumber),
|
||||
angle = AttrMapValue(isNumber),
|
||||
boxAnchor = AttrMapValue(isBoxAnchor),
|
||||
boxStrokeColor = AttrMapValue(isColorOrNone),
|
||||
boxStrokeWidth = AttrMapValue(isNumber),
|
||||
boxFillColor = AttrMapValue(isColorOrNone),
|
||||
fillColor = AttrMapValue(isColorOrNone),
|
||||
strokeColor = AttrMapValue(isColorOrNone),
|
||||
strokeWidth = AttrMapValue(isNumber),
|
||||
text = AttrMapValue(isString),
|
||||
fontName = AttrMapValue(isString),
|
||||
fontSize = AttrMapValue(isNumber),
|
||||
leading = AttrMapValue(isNumberOrNone),
|
||||
width = AttrMapValue(isNumberOrNone),
|
||||
maxWidth = AttrMapValue(isNumberOrNone),
|
||||
height = AttrMapValue(isNumberOrNone),
|
||||
textAnchor = AttrMapValue(isTextAnchor),
|
||||
visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
|
||||
topPadding = AttrMapValue(isNumber,'padding at top of box'),
|
||||
leftPadding = AttrMapValue(isNumber,'padding at left of box'),
|
||||
rightPadding = AttrMapValue(isNumber,'padding at right of box'),
|
||||
bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'),
|
||||
)
|
||||
|
||||
def __init__(self,**kw):
|
||||
self._setKeywords(**kw)
|
||||
self._setKeywords(
|
||||
_text = 'Multi-Line\nString',
|
||||
boxAnchor = 'c',
|
||||
angle = 0,
|
||||
x = 0,
|
||||
y = 0,
|
||||
dx = 0,
|
||||
dy = 0,
|
||||
topPadding = 0,
|
||||
leftPadding = 0,
|
||||
rightPadding = 0,
|
||||
bottomPadding = 0,
|
||||
boxStrokeWidth = 0.5,
|
||||
boxStrokeColor = None,
|
||||
strokeColor = None,
|
||||
boxFillColor = None,
|
||||
leading = None,
|
||||
width = None,
|
||||
maxWidth = None,
|
||||
height = None,
|
||||
fillColor = STATE_DEFAULTS['fillColor'],
|
||||
fontName = STATE_DEFAULTS['fontName'],
|
||||
fontSize = STATE_DEFAULTS['fontSize'],
|
||||
strokeWidth = 0.1,
|
||||
textAnchor = 'start',
|
||||
visible = 1,
|
||||
)
|
||||
|
||||
def setText(self, text):
|
||||
"""Set the text property. May contain embedded newline characters.
|
||||
Called by the containing chart or axis."""
|
||||
self._text = text
|
||||
|
||||
|
||||
def setOrigin(self, x, y):
|
||||
"""Set the origin. This would be the tick mark or bar top relative to
|
||||
which it is defined. Called by the containing chart or axis."""
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
|
||||
def demo(self):
|
||||
"""This shows a label positioned with its top right corner
|
||||
at the top centre of the drawing, and rotated 45 degrees."""
|
||||
|
||||
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('Another\nMulti-Line\nString')
|
||||
d.add(lab)
|
||||
|
||||
return d
|
||||
|
||||
def _getBoxAnchor(self):
|
||||
'''hook for allowing special box anchor effects'''
|
||||
ba = self.boxAnchor
|
||||
if ba in ('autox', 'autoy'):
|
||||
angle = self.angle
|
||||
na = (int((angle%360)/45.)*45)%360
|
||||
if not (na % 90): # we have a right angle case
|
||||
da = (angle - na) % 360
|
||||
if abs(da)>5:
|
||||
na = na + (da>0 and 45 or -45)
|
||||
ba = _A2BA[ba[-1]][na]
|
||||
return ba
|
||||
|
||||
def computeSize(self):
|
||||
# the thing will draw in its own coordinate system
|
||||
self._lines = string.split(self._text, '\n')
|
||||
self._lineWidths = []
|
||||
topPadding = self.topPadding
|
||||
leftPadding = self.leftPadding
|
||||
rightPadding = self.rightPadding
|
||||
bottomPadding = self.bottomPadding
|
||||
SW = lambda text, fN=self.fontName, fS=self.fontSize: stringWidth(text, fN, fS)
|
||||
if self.maxWidth:
|
||||
L = []
|
||||
for l in self._lines:
|
||||
L[-1:-1] = _simpleSplit(l,self.maxWidth,SW)
|
||||
self._lines = L
|
||||
if not self.width:
|
||||
w = 0
|
||||
for line in self._lines:
|
||||
thisWidth = SW(line)
|
||||
self._lineWidths.append(thisWidth)
|
||||
w = max(w,thisWidth)
|
||||
self._width = w+leftPadding+rightPadding
|
||||
else:
|
||||
self._width = self.width
|
||||
self._height = self.height or ((self.leading or 1.2*self.fontSize) * len(self._lines)+topPadding+bottomPadding)
|
||||
self._ewidth = (self._width-leftPadding-rightPadding)
|
||||
self._eheight = (self._height-topPadding-bottomPadding)
|
||||
boxAnchor = self._getBoxAnchor()
|
||||
if boxAnchor in ['n','ne','nw']:
|
||||
self._top = -topPadding
|
||||
elif boxAnchor in ['s','sw','se']:
|
||||
self._top = self._height-topPadding
|
||||
else:
|
||||
self._top = 0.5*self._eheight
|
||||
self._bottom = self._top - self._eheight
|
||||
|
||||
if boxAnchor in ['ne','e','se']:
|
||||
self._left = leftPadding - self._width
|
||||
elif boxAnchor in ['nw','w','sw']:
|
||||
self._left = leftPadding
|
||||
else:
|
||||
self._left = -self._ewidth*0.5
|
||||
self._right = self._left+self._ewidth
|
||||
|
||||
def _getTextAnchor(self):
|
||||
'''This can be overridden to allow special effects'''
|
||||
ta = self.textAnchor
|
||||
if ta=='boxauto': ta = _BA2TA[self._getBoxAnchor()]
|
||||
return ta
|
||||
|
||||
def draw(self):
|
||||
_text = self._text
|
||||
self._text = _text or ''
|
||||
self.computeSize()
|
||||
self._text = _text
|
||||
g = Group()
|
||||
g.translate(self.x + self.dx, self.y + self.dy)
|
||||
g.rotate(self.angle)
|
||||
|
||||
y = self._top - self.fontSize
|
||||
textAnchor = self._getTextAnchor()
|
||||
if textAnchor == 'start':
|
||||
x = self._left
|
||||
elif textAnchor == 'middle':
|
||||
x = self._left + self._ewidth*0.5
|
||||
else:
|
||||
x = self._right
|
||||
|
||||
# paint box behind text just in case they
|
||||
# fill it
|
||||
if self.boxFillColor or (self.boxStrokeColor and self.boxStrokeWidth):
|
||||
g.add(Rect( self._left-self.leftPadding,
|
||||
self._bottom-self.bottomPadding,
|
||||
self._width,
|
||||
self._height,
|
||||
strokeColor=self.boxStrokeColor,
|
||||
strokeWidth=self.boxStrokeWidth,
|
||||
fillColor=self.boxFillColor)
|
||||
)
|
||||
|
||||
fillColor, fontName, fontSize = self.fillColor, self.fontName, self.fontSize
|
||||
strokeColor, strokeWidth, leading = self.strokeColor, self.strokeWidth, (self.leading or 1.2*fontSize)
|
||||
if strokeColor:
|
||||
for line in self._lines:
|
||||
s = _text2Path(line, x, y, fontName, fontSize, textAnchor)
|
||||
s.fillColor = fillColor
|
||||
s.strokeColor = strokeColor
|
||||
s.strokeWidth = strokeWidth
|
||||
g.add(s)
|
||||
y = y - leading
|
||||
else:
|
||||
for line in self._lines:
|
||||
s = String(x, y, line)
|
||||
s.textAnchor = textAnchor
|
||||
s.fontName = fontName
|
||||
s.fontSize = fontSize
|
||||
s.fillColor = fillColor
|
||||
g.add(s)
|
||||
y = y - leading
|
||||
|
||||
return g
|
||||
|
||||
class LabelDecorator:
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumberOrNone),
|
||||
y = AttrMapValue(isNumberOrNone),
|
||||
dx = AttrMapValue(isNumberOrNone),
|
||||
dy = AttrMapValue(isNumberOrNone),
|
||||
angle = AttrMapValue(isNumberOrNone),
|
||||
boxAnchor = AttrMapValue(isBoxAnchor),
|
||||
boxStrokeColor = AttrMapValue(isColorOrNone),
|
||||
boxStrokeWidth = AttrMapValue(isNumberOrNone),
|
||||
boxFillColor = AttrMapValue(isColorOrNone),
|
||||
fillColor = AttrMapValue(isColorOrNone),
|
||||
strokeColor = AttrMapValue(isColorOrNone),
|
||||
strokeWidth = AttrMapValue(isNumberOrNone),
|
||||
fontName = AttrMapValue(isNoneOrString),
|
||||
fontSize = AttrMapValue(isNumberOrNone),
|
||||
leading = AttrMapValue(isNumberOrNone),
|
||||
width = AttrMapValue(isNumberOrNone),
|
||||
maxWidth = AttrMapValue(isNumberOrNone),
|
||||
height = AttrMapValue(isNumberOrNone),
|
||||
textAnchor = AttrMapValue(isTextAnchor),
|
||||
visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.textAnchor = 'start'
|
||||
self.boxAnchor = 'w'
|
||||
for a in self._attrMap.keys():
|
||||
if not hasattr(self,a): setattr(self,a,None)
|
||||
|
||||
def decorate(self,l,L):
|
||||
chart,g,rowNo,colNo,x,y,width,height,x00,y00,x0,y0 = l._callOutInfo
|
||||
L.setText(chart.categoryAxis.categoryNames[colNo])
|
||||
g.add(L)
|
||||
|
||||
def __call__(self,l):
|
||||
from copy import deepcopy
|
||||
L = Label()
|
||||
for a,v in self.__dict__.items():
|
||||
if v is None: v = getattr(l,a,None)
|
||||
setattr(L,a,v)
|
||||
self.decorate(l,L)
|
||||
|
||||
isOffsetMode=OneOf('high','low','bar','axis')
|
||||
class LabelOffset(PropHolder):
|
||||
_attrMap = AttrMap(
|
||||
posMode = AttrMapValue(isOffsetMode,desc="Where to base +ve offset"),
|
||||
pos = AttrMapValue(isNumber,desc='Value for positive elements'),
|
||||
negMode = AttrMapValue(isOffsetMode,desc="Where to base -ve offset"),
|
||||
neg = AttrMapValue(isNumber,desc='Value for negative elements'),
|
||||
)
|
||||
def __init__(self):
|
||||
self.posMode=self.negMode='axis'
|
||||
self.pos = self.neg = 0
|
||||
|
||||
def _getValue(self, chart, val):
|
||||
flipXY = chart._flipXY
|
||||
A = chart.categoryAxis
|
||||
jA = A.joinAxis
|
||||
if val>=0:
|
||||
mode = self.posMode
|
||||
delta = self.pos
|
||||
else:
|
||||
mode = self.negMode
|
||||
delta = self.neg
|
||||
if flipXY:
|
||||
v = A._x
|
||||
else:
|
||||
v = A._y
|
||||
if jA:
|
||||
if flipXY:
|
||||
_v = jA._x
|
||||
else:
|
||||
_v = jA._y
|
||||
if mode=='high':
|
||||
v = _v + jA._length
|
||||
elif mode=='low':
|
||||
v = _v
|
||||
elif mode=='bar':
|
||||
v = _v+val
|
||||
return v+delta
|
||||
|
||||
NoneOrInstanceOfLabelOffset=NoneOr(isInstanceOf(LabelOffset))
|
||||
|
||||
class BarChartLabel(Label):
|
||||
"""
|
||||
An extended Label allowing for nudging, lines visibility etc
|
||||
"""
|
||||
_attrMap = AttrMap(
|
||||
BASE=Label,
|
||||
lineStrokeWidth = AttrMapValue(isNumberOrNone, desc="Non-zero for a drawn line"),
|
||||
lineStrokeColor = AttrMapValue(isColorOrNone, desc="Color for a drawn line"),
|
||||
fixedEnd = AttrMapValue(NoneOrInstanceOfLabelOffset, desc="None or fixed draw ends +/-"),
|
||||
fixedStart = AttrMapValue(NoneOrInstanceOfLabelOffset, desc="None or fixed draw starts +/-"),
|
||||
nudge = AttrMapValue(isNumber, desc="Non-zero sign dependent nudge"),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
Label.__init__(self)
|
||||
self.lineStrokeWidth = 0
|
||||
self.lineStrokeColor = None
|
||||
self.nudge = 0
|
||||
self.fixedStart = self.fixedEnd = None
|
||||
self._pmv = 0
|
||||
|
||||
def _getBoxAnchor(self):
|
||||
a = self.boxAnchor
|
||||
if self._pmv<0: a = {'nw':'se','n':'s','ne':'sw','w':'e','c':'c','e':'w','sw':'ne','s':'n','se':'nw'}[a]
|
||||
return a
|
||||
|
||||
def _getTextAnchor(self):
|
||||
a = self.textAnchor
|
||||
if self._pmv<0: a = {'start':'end', 'middle':'middle', 'end':'start'}[a]
|
||||
return a
|
||||
|
||||
class NA_Label(BarChartLabel):
|
||||
"""
|
||||
An extended Label allowing for nudging, lines visibility etc
|
||||
"""
|
||||
_attrMap = AttrMap(
|
||||
BASE=BarChartLabel,
|
||||
text = AttrMapValue(isNoneOrString, desc="Text to be used for N/A values"),
|
||||
)
|
||||
def __init__(self):
|
||||
BarChartLabel.__init__(self)
|
||||
self.text = 'n/a'
|
||||
NoneOrInstanceOfNA_Label=NoneOr(isInstanceOf(NA_Label))
|
|
@ -1,191 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/utils.py
|
||||
"Utilities used here and there."
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
from time import mktime, gmtime, strftime
|
||||
import string
|
||||
|
||||
|
||||
### Dinu's stuff used in some line plots (likely to vansih).
|
||||
|
||||
def mkTimeTuple(timeString):
|
||||
"Convert a 'dd/mm/yyyy' formatted string to a tuple for use in the time module."
|
||||
|
||||
list = [0] * 9
|
||||
dd, mm, yyyy = map(int, string.split(timeString, '/'))
|
||||
list[:3] = [yyyy, mm, dd]
|
||||
|
||||
return tuple(list)
|
||||
|
||||
|
||||
def str2seconds(timeString):
|
||||
"Convert a number of seconds since the epoch into a date string."
|
||||
|
||||
return mktime(mkTimeTuple(timeString))
|
||||
|
||||
|
||||
def seconds2str(seconds):
|
||||
"Convert a date string into the number of seconds since the epoch."
|
||||
|
||||
return strftime('%Y-%m-%d', gmtime(seconds))
|
||||
|
||||
|
||||
### Aaron's rounding function for making nice values on axes.
|
||||
|
||||
from math import log10
|
||||
|
||||
def nextRoundNumber(x):
|
||||
"""Return the first 'nice round number' greater than or equal to x
|
||||
|
||||
Used in selecting apropriate tick mark intervals; we say we want
|
||||
an interval which places ticks at least 10 points apart, work out
|
||||
what that is in chart space, and ask for the nextRoundNumber().
|
||||
Tries the series 1,2,5,10,20,50,100.., going up or down as needed.
|
||||
"""
|
||||
|
||||
#guess to nearest order of magnitude
|
||||
if x in (0, 1):
|
||||
return x
|
||||
|
||||
if x < 0:
|
||||
return -1.0 * nextRoundNumber(-x)
|
||||
else:
|
||||
lg = int(log10(x))
|
||||
|
||||
if lg == 0:
|
||||
if x < 1:
|
||||
base = 0.1
|
||||
else:
|
||||
base = 1.0
|
||||
elif lg < 0:
|
||||
base = 10.0 ** (lg - 1)
|
||||
else:
|
||||
base = 10.0 ** lg # e.g. base(153) = 100
|
||||
# base will always be lower than x
|
||||
|
||||
if base >= x:
|
||||
return base * 1.0
|
||||
elif (base * 2) >= x:
|
||||
return base * 2.0
|
||||
elif (base * 5) >= x:
|
||||
return base * 5.0
|
||||
else:
|
||||
return base * 10.0
|
||||
|
||||
|
||||
### Robin's stuff from rgb_ticks.
|
||||
|
||||
from math import log10, floor
|
||||
|
||||
_intervals=(.1, .2, .25, .5)
|
||||
_j_max=len(_intervals)-1
|
||||
|
||||
|
||||
def find_interval(lo,hi,I=5):
|
||||
'determine tick parameters for range [lo, hi] using I intervals'
|
||||
|
||||
if lo >= hi:
|
||||
if lo==hi:
|
||||
if lo==0:
|
||||
lo = -.1
|
||||
hi = .1
|
||||
else:
|
||||
lo = 0.9*lo
|
||||
hi = 1.1*hi
|
||||
else:
|
||||
raise ValueError, "lo>hi"
|
||||
x=(hi - lo)/float(I)
|
||||
b= (x>0 and (x<1 or x>10)) and 10**floor(log10(x)) or 1
|
||||
b = b
|
||||
while 1:
|
||||
a = x/b
|
||||
if a<=_intervals[-1]: break
|
||||
b = b*10
|
||||
|
||||
j = 0
|
||||
while a>_intervals[j]: j = j + 1
|
||||
|
||||
while 1:
|
||||
ss = _intervals[j]*b
|
||||
n = lo/ss
|
||||
l = int(n)-(n<0)
|
||||
n = ss*l
|
||||
x = ss*(l+I)
|
||||
a = I*ss
|
||||
if n>0:
|
||||
if a>=hi:
|
||||
n = 0.0
|
||||
x = a
|
||||
elif hi<0:
|
||||
a = -a
|
||||
if lo>a:
|
||||
n = a
|
||||
x = 0
|
||||
if hi<=x and n<=lo: break
|
||||
j = j + 1
|
||||
if j>_j_max:
|
||||
j = 0
|
||||
b = b*10
|
||||
return n, x, ss, lo - n + x - hi
|
||||
|
||||
|
||||
def find_good_grid(lower,upper,n=(4,5,6,7,8,9), grid=None):
|
||||
if grid:
|
||||
t = divmod(lower,grid)[0] * grid
|
||||
hi, z = divmod(upper,grid)
|
||||
if z>1e-8: hi = hi+1
|
||||
hi = hi*grid
|
||||
else:
|
||||
try:
|
||||
n[0]
|
||||
except TypeError:
|
||||
n = xrange(max(1,n-2),max(n+3,2))
|
||||
|
||||
w = 1e308
|
||||
for i in n:
|
||||
z=find_interval(lower,upper,i)
|
||||
if z[3]<w:
|
||||
t, hi, grid = z[:3]
|
||||
w=z[3]
|
||||
return t, hi, grid
|
||||
|
||||
|
||||
def ticks(lower, upper, n=(4,5,6,7,8,9), split=1, percent=0, grid=None):
|
||||
'''
|
||||
return tick positions and labels for range lower<=x<=upper
|
||||
n=number of intervals to try (can be a list or sequence)
|
||||
split=1 return ticks then labels else (tick,label) pairs
|
||||
'''
|
||||
t, hi, grid = find_good_grid(lower, upper, n, grid)
|
||||
power = floor(log10(grid))
|
||||
if power==0: power = 1
|
||||
w = grid/10.**power
|
||||
w = int(w)!=w
|
||||
|
||||
if power > 3 or power < -3:
|
||||
format = '%+'+`w+7`+'.0e'
|
||||
else:
|
||||
if power >= 0:
|
||||
digits = int(power)+w
|
||||
format = '%' + `digits`+'.0f'
|
||||
else:
|
||||
digits = w-int(power)
|
||||
format = '%'+`digits+2`+'.'+`digits`+'f'
|
||||
|
||||
if percent: format=format+'%%'
|
||||
T = []
|
||||
n = int(float(hi-t)/grid+0.1)+1
|
||||
if split:
|
||||
labels = []
|
||||
for i in xrange(n):
|
||||
v = t+grid*i
|
||||
T.append(v)
|
||||
labels.append(format % v)
|
||||
return T, labels
|
||||
else:
|
||||
for i in xrange(n):
|
||||
v = t+grid*i
|
||||
T.append((v, format % v))
|
||||
return T
|
|
@ -1,233 +0,0 @@
|
|||
from reportlab.lib import colors
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.graphics.shapes import Group, Drawing, Ellipse, Wedge, String, STATE_DEFAULTS, Polygon, Line
|
||||
|
||||
def _getShaded(col,shd=None,shading=0.1):
|
||||
if shd is None:
|
||||
from reportlab.lib.colors import Blacker
|
||||
if col: shd = Blacker(col,1-shading)
|
||||
return shd
|
||||
|
||||
def _getLit(col,shd=None,lighting=0.1):
|
||||
if shd is None:
|
||||
from reportlab.lib.colors import Whiter
|
||||
if col: shd = Whiter(col,1-lighting)
|
||||
return shd
|
||||
|
||||
|
||||
def _draw_3d_bar(G, x1, x2, y0, yhigh, xdepth, ydepth,
|
||||
fillColor=None, fillColorShaded=None,
|
||||
strokeColor=None, strokeWidth=1, shading=0.1):
|
||||
fillColorShaded = _getShaded(fillColor,None,shading)
|
||||
fillColorShadedTop = _getShaded(fillColor,None,shading/2.0)
|
||||
|
||||
def _add_3d_bar(x1, x2, y1, y2, xoff, yoff,
|
||||
G=G,strokeColor=strokeColor, strokeWidth=strokeWidth, fillColor=fillColor):
|
||||
G.add(Polygon((x1,y1, x1+xoff,y1+yoff, x2+xoff,y2+yoff, x2,y2),
|
||||
strokeWidth=strokeWidth, strokeColor=strokeColor, fillColor=fillColor,strokeLineJoin=1))
|
||||
|
||||
usd = max(y0, yhigh)
|
||||
if xdepth or ydepth:
|
||||
if y0!=yhigh: #non-zero height
|
||||
_add_3d_bar( x2, x2, y0, yhigh, xdepth, ydepth, fillColor=fillColorShaded) #side
|
||||
|
||||
_add_3d_bar(x1, x2, usd, usd, xdepth, ydepth, fillColor=fillColorShadedTop) #top
|
||||
|
||||
G.add(Polygon((x1,y0,x2,y0,x2,yhigh,x1,yhigh),
|
||||
strokeColor=strokeColor, strokeWidth=strokeWidth, fillColor=fillColor,strokeLineJoin=1)) #front
|
||||
|
||||
if xdepth or ydepth:
|
||||
G.add(Line( x1, usd, x2, usd, strokeWidth=strokeWidth, strokeColor=strokeColor or fillColorShaded))
|
||||
|
||||
class _YStrip:
|
||||
def __init__(self,y0,y1, slope, fillColor, fillColorShaded, shading=0.1):
|
||||
self.y0 = y0
|
||||
self.y1 = y1
|
||||
self.slope = slope
|
||||
self.fillColor = fillColor
|
||||
self.fillColorShaded = _getShaded(fillColor,fillColorShaded,shading)
|
||||
|
||||
def _ystrip_poly( x0, x1, y0, y1, xoff, yoff):
|
||||
return [x0,y0,x0+xoff,y0+yoff,x1+xoff,y1+yoff,x1,y1]
|
||||
|
||||
|
||||
def _make_3d_line_info( G, x0, x1, y0, y1, z0, z1,
|
||||
theta_x, theta_y,
|
||||
fillColor, fillColorShaded=None, tileWidth=1,
|
||||
strokeColor=None, strokeWidth=None, strokeDashArray=None,
|
||||
shading=0.1):
|
||||
zwidth = abs(z1-z0)
|
||||
xdepth = zwidth*theta_x
|
||||
ydepth = zwidth*theta_y
|
||||
depth_slope = xdepth==0 and 1e150 or -ydepth/float(xdepth)
|
||||
|
||||
x = float(x1-x0)
|
||||
slope = x==0 and 1e150 or (y1-y0)/x
|
||||
|
||||
c = slope>depth_slope and _getShaded(fillColor,fillColorShaded,shading) or fillColor
|
||||
zy0 = z0*theta_y
|
||||
zx0 = z0*theta_x
|
||||
|
||||
tileStrokeWidth = 0.6
|
||||
if tileWidth is None:
|
||||
D = [(x1,y1)]
|
||||
else:
|
||||
T = ((y1-y0)**2+(x1-x0)**2)**0.5
|
||||
tileStrokeWidth *= tileWidth
|
||||
if T<tileWidth:
|
||||
D = [(x1,y1)]
|
||||
else:
|
||||
n = int(T/float(tileWidth))+1
|
||||
dx = float(x1-x0)/n
|
||||
dy = float(y1-y0)/n
|
||||
D = []
|
||||
a = D.append
|
||||
for i in xrange(1,n):
|
||||
a((x0+dx*i,y0+dy*i))
|
||||
|
||||
a = G.add
|
||||
x_0 = x0+zx0
|
||||
y_0 = y0+zy0
|
||||
for x,y in D:
|
||||
x_1 = x+zx0
|
||||
y_1 = y+zy0
|
||||
P = Polygon(_ystrip_poly(x_0, x_1, y_0, y_1, xdepth, ydepth),
|
||||
fillColor = c, strokeColor=c, strokeWidth=tileStrokeWidth)
|
||||
a((0,z0,z1,x_0,y_0,P))
|
||||
x_0 = x_1
|
||||
y_0 = y_1
|
||||
|
||||
from math import pi, sin, cos
|
||||
_pi_2 = pi*0.5
|
||||
_2pi = 2*pi
|
||||
_180_pi=180./pi
|
||||
|
||||
def _2rad(angle):
|
||||
return (angle*pi)/180
|
||||
|
||||
def mod_2pi(radians):
|
||||
radians = radians % _2pi
|
||||
if radians<-1e-6: radians += _2pi
|
||||
return radians
|
||||
|
||||
def _2deg(o):
|
||||
return o*_180_pi
|
||||
|
||||
def _360(a):
|
||||
a %= 360
|
||||
if a<-1e-6: a += 360
|
||||
return a
|
||||
|
||||
_ZERO = 1e-8
|
||||
_ONE = 1-_ZERO
|
||||
class _Segment:
|
||||
def __init__(self,s,i,data):
|
||||
S = data[s]
|
||||
x0 = S[i-1][0]
|
||||
y0 = S[i-1][1]
|
||||
x1 = S[i][0]
|
||||
y1 = S[i][1]
|
||||
if x1<x0:
|
||||
x0,y0,x1,y1 = x1,y1,x0,y0
|
||||
# (y-y0)*(x1-x0) = (y1-y0)*(x-x0)
|
||||
# (x1-x0)*y + (y0-y1)*x = y0*(x1-x0)+x0*(y0-y1)
|
||||
# a*y+b*x = c
|
||||
self.a = float(x1-x0)
|
||||
self.b = float(y1-y0)
|
||||
self.x0 = x0
|
||||
self.x1 = x1
|
||||
self.y0 = y0
|
||||
self.y1 = y1
|
||||
self.series = s
|
||||
self.i = i
|
||||
self.s = s
|
||||
|
||||
def __str__(self):
|
||||
return '[(%s,%s),(%s,%s)]' % (self.x0,self.y0,self.x1,self.y1)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def intersect(self,o,I):
|
||||
'''try to find an intersection with _Segment o
|
||||
'''
|
||||
x0 = self.x0
|
||||
ox0 = o.x0
|
||||
assert x0<=ox0
|
||||
if ox0>self.x1: return 1
|
||||
if o.s==self.s and o.i in (self.i-1,self.i+1): return
|
||||
a = self.a
|
||||
b = self.b
|
||||
oa = o.a
|
||||
ob = o.b
|
||||
det = ob*a - oa*b
|
||||
if -1e-8<det<1e-8: return
|
||||
dx = x0 - ox0
|
||||
dy = self.y0 - o.y0
|
||||
u = (oa*dy - ob*dx)/det
|
||||
ou = (a*dy - b*dx)/det
|
||||
if u<0 or u>1 or ou<0 or ou>1: return
|
||||
x = x0 + u*a
|
||||
y = self.y0 + u*b
|
||||
if _ZERO<u<_ONE:
|
||||
t = self.s,self.i,x,y
|
||||
if t not in I: I.append(t)
|
||||
if _ZERO<ou<_ONE:
|
||||
t = o.s,o.i,x,y
|
||||
if t not in I: I.append(t)
|
||||
|
||||
def _segCmp(a,b):
|
||||
return cmp((a.x0,a.x1,a.y0,a.y1,a.s,a.i),(b.x0,b.x1,b.y0,b.y1,b.s,b.i))
|
||||
|
||||
def find_intersections(data,small=0):
|
||||
'''
|
||||
data is a sequence of series
|
||||
each series is a list of (x,y) coordinates
|
||||
where x & y are ints or floats
|
||||
|
||||
find_intersections returns a sequence of 4-tuples
|
||||
i, j, x, y
|
||||
|
||||
where i is a data index j is an insertion position for data[i]
|
||||
and x, y are coordinates of an intersection of series data[i]
|
||||
with some other series. If correctly implemented we get all such
|
||||
intersections. We don't count endpoint intersections and consider
|
||||
parallel lines as non intersecting (even when coincident).
|
||||
We ignore segments that have an estimated size less than small.
|
||||
'''
|
||||
|
||||
#find all line segments
|
||||
S = []
|
||||
a = S.append
|
||||
for s in xrange(len(data)):
|
||||
ds = data[s]
|
||||
if not ds: continue
|
||||
n = len(ds)
|
||||
if n==1: continue
|
||||
for i in xrange(1,n):
|
||||
seg = _Segment(s,i,data)
|
||||
if seg.a+abs(seg.b)>=small: a(seg)
|
||||
S.sort(_segCmp)
|
||||
I = []
|
||||
n = len(S)
|
||||
for i in xrange(0,n-1):
|
||||
s = S[i]
|
||||
for j in xrange(i+1,n):
|
||||
if s.intersect(S[j],I)==1: break
|
||||
I.sort()
|
||||
return I
|
||||
|
||||
if __name__=='__main__':
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.lib.colors import lightgrey, pink
|
||||
D = Drawing(300,200)
|
||||
_draw_3d_bar(D, 10, 20, 10, 50, 5, 5, fillColor=lightgrey, strokeColor=pink)
|
||||
_draw_3d_bar(D, 30, 40, 10, 45, 5, 5, fillColor=lightgrey, strokeColor=pink)
|
||||
|
||||
D.save(formats=['pdf'],outDir='.',fnRoot='_draw_3d_bar')
|
||||
|
||||
print find_intersections([[(0,0.5),(1,0.5),(0.5,0),(0.5,1)],[(.2666666667,0.4),(0.1,0.4),(0.1,0.2),(0,0),(1,1)],[(0,1),(0.4,0.1),(1,0.1)]])
|
||||
print find_intersections([[(0.1, 0.2), (0.1, 0.4)], [(0, 1), (0.4, 0.1)]])
|
||||
print find_intersections([[(0.2, 0.4), (0.1, 0.4)], [(0.1, 0.8), (0.4, 0.1)]])
|
||||
print find_intersections([[(0,0),(1,1)],[(0.4,0.1),(1,0.1)]])
|
||||
print find_intersections([[(0,0.5),(1,0.5),(0.5,0),(0.5,1)],[(0,0),(1,1)],[(0.1,0.8),(0.4,0.1),(1,0.1)]])
|
|
@ -1,361 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/renderPDF.py
|
||||
# renderPDF - draws Drawings onto a canvas
|
||||
"""Usage:
|
||||
import renderpdf
|
||||
renderpdf.draw(drawing, canvas, x, y)
|
||||
Execute the script to see some test drawings.
|
||||
changed
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
from reportlab.graphics.shapes import *
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.pdfbase.pdfmetrics import stringWidth
|
||||
from reportlab.lib.utils import getStringIO
|
||||
from reportlab import rl_config
|
||||
|
||||
# the main entry point for users...
|
||||
def draw(drawing, canvas, x, y, showBoundary=rl_config._unset_):
|
||||
"""As it says"""
|
||||
R = _PDFRenderer()
|
||||
R.draw(drawing, canvas, x, y, showBoundary=showBoundary)
|
||||
|
||||
from renderbase import Renderer, StateTracker, getStateDelta
|
||||
|
||||
class _PDFRenderer(Renderer):
|
||||
"""This draws onto a PDF document. It needs to be a class
|
||||
rather than a function, as some PDF-specific state tracking is
|
||||
needed outside of the state info in the SVG model."""
|
||||
|
||||
def __init__(self):
|
||||
self._stroke = 0
|
||||
self._fill = 0
|
||||
self._tracker = StateTracker()
|
||||
|
||||
def drawNode(self, node):
|
||||
"""This is the recursive method called for each node
|
||||
in the tree"""
|
||||
#print "pdf:drawNode", self
|
||||
#if node.__class__ is Wedge: stop
|
||||
if not (isinstance(node, Path) and node.isClipPath):
|
||||
self._canvas.saveState()
|
||||
|
||||
#apply state changes
|
||||
deltas = getStateDelta(node)
|
||||
self._tracker.push(deltas)
|
||||
self.applyStateChanges(deltas, {})
|
||||
|
||||
#draw the object, or recurse
|
||||
self.drawNodeDispatcher(node)
|
||||
|
||||
self._tracker.pop()
|
||||
if not (isinstance(node, Path) and node.isClipPath):
|
||||
self._canvas.restoreState()
|
||||
|
||||
def drawRect(self, rect):
|
||||
if rect.rx == rect.ry == 0:
|
||||
#plain old rectangle
|
||||
self._canvas.rect(
|
||||
rect.x, rect.y,
|
||||
rect.width, rect.height,
|
||||
stroke=self._stroke,
|
||||
fill=self._fill
|
||||
)
|
||||
else:
|
||||
#cheat and assume ry = rx; better to generalize
|
||||
#pdfgen roundRect function. TODO
|
||||
self._canvas.roundRect(
|
||||
rect.x, rect.y,
|
||||
rect.width, rect.height, rect.rx,
|
||||
fill=self._fill,
|
||||
stroke=self._stroke
|
||||
)
|
||||
|
||||
def drawImage(self, image):
|
||||
# currently not implemented in other renderers
|
||||
if image.path and os.path.exists(image.path):
|
||||
self._canvas.drawInlineImage(
|
||||
image.path,
|
||||
image.x, image.y,
|
||||
image.width, image.height
|
||||
)
|
||||
|
||||
def drawLine(self, line):
|
||||
if self._stroke:
|
||||
self._canvas.line(line.x1, line.y1, line.x2, line.y2)
|
||||
|
||||
def drawCircle(self, circle):
|
||||
self._canvas.circle(
|
||||
circle.cx, circle.cy, circle.r,
|
||||
fill=self._fill,
|
||||
stroke=self._stroke
|
||||
)
|
||||
|
||||
def drawPolyLine(self, polyline):
|
||||
if self._stroke:
|
||||
assert len(polyline.points) >= 2, 'Polyline must have 2 or more points'
|
||||
head, tail = polyline.points[0:2], polyline.points[2:],
|
||||
path = self._canvas.beginPath()
|
||||
path.moveTo(head[0], head[1])
|
||||
for i in range(0, len(tail), 2):
|
||||
path.lineTo(tail[i], tail[i+1])
|
||||
self._canvas.drawPath(path)
|
||||
|
||||
def drawWedge(self, wedge):
|
||||
centerx, centery, radius, startangledegrees, endangledegrees = \
|
||||
wedge.centerx, wedge.centery, wedge.radius, wedge.startangledegrees, wedge.endangledegrees
|
||||
yradius, radius1, yradius1 = wedge._xtraRadii()
|
||||
if yradius is None: yradius = radius
|
||||
angle = endangledegrees-startangledegrees
|
||||
path = self._canvas.beginPath()
|
||||
if (radius1==0 or radius1 is None) and (yradius1==0 or yradius1 is None):
|
||||
path.moveTo(centerx, centery)
|
||||
path.arcTo(centerx-radius, centery-yradius, centerx+radius, centery+yradius,
|
||||
startangledegrees, angle)
|
||||
else:
|
||||
path.arc(centerx-radius, centery-yradius, centerx+radius, centery+yradius,
|
||||
startangledegrees, angle)
|
||||
path.arcTo(centerx-radius1, centery-yradius1, centerx+radius1, centery+yradius1,
|
||||
endangledegrees, -angle)
|
||||
path.close()
|
||||
self._canvas.drawPath(path,
|
||||
fill=self._fill,
|
||||
stroke=self._stroke)
|
||||
|
||||
def drawEllipse(self, ellipse):
|
||||
#need to convert to pdfgen's bounding box representation
|
||||
x1 = ellipse.cx - ellipse.rx
|
||||
x2 = ellipse.cx + ellipse.rx
|
||||
y1 = ellipse.cy - ellipse.ry
|
||||
y2 = ellipse.cy + ellipse.ry
|
||||
self._canvas.ellipse(x1,y1,x2,y2,fill=self._fill,stroke=self._stroke)
|
||||
|
||||
def drawPolygon(self, polygon):
|
||||
assert len(polygon.points) >= 2, 'Polyline must have 2 or more points'
|
||||
head, tail = polygon.points[0:2], polygon.points[2:],
|
||||
path = self._canvas.beginPath()
|
||||
path.moveTo(head[0], head[1])
|
||||
for i in range(0, len(tail), 2):
|
||||
path.lineTo(tail[i], tail[i+1])
|
||||
path.close()
|
||||
self._canvas.drawPath(
|
||||
path,
|
||||
stroke=self._stroke,
|
||||
fill=self._fill
|
||||
)
|
||||
|
||||
def drawString(self, stringObj):
|
||||
if self._fill:
|
||||
S = self._tracker.getState()
|
||||
text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text
|
||||
if not text_anchor in ['start','inherited']:
|
||||
font, font_size = S['fontName'], S['fontSize']
|
||||
textLen = stringWidth(text, font,font_size)
|
||||
if text_anchor=='end':
|
||||
x = x-textLen
|
||||
elif text_anchor=='middle':
|
||||
x = x - textLen/2
|
||||
else:
|
||||
raise ValueError, 'bad value for textAnchor '+str(text_anchor)
|
||||
t = self._canvas.beginText(x,y)
|
||||
t.textLine(text)
|
||||
self._canvas.drawText(t)
|
||||
|
||||
def drawPath(self, path):
|
||||
from reportlab.graphics.shapes import _renderPath
|
||||
pdfPath = self._canvas.beginPath()
|
||||
drawFuncs = (pdfPath.moveTo, pdfPath.lineTo, pdfPath.curveTo, pdfPath.close)
|
||||
isClosed = _renderPath(path, drawFuncs)
|
||||
if isClosed:
|
||||
fill = self._fill
|
||||
else:
|
||||
fill = 0
|
||||
if path.isClipPath:
|
||||
self._canvas.clipPath(pdfPath, fill=fill, stroke=self._stroke)
|
||||
else:
|
||||
self._canvas.drawPath(pdfPath,
|
||||
fill=fill,
|
||||
stroke=self._stroke)
|
||||
|
||||
def applyStateChanges(self, delta, newState):
|
||||
"""This takes a set of states, and outputs the PDF operators
|
||||
needed to set those properties"""
|
||||
for key, value in delta.items():
|
||||
if key == 'transform':
|
||||
self._canvas.transform(value[0], value[1], value[2],
|
||||
value[3], value[4], value[5])
|
||||
elif key == 'strokeColor':
|
||||
#this has different semantics in PDF to SVG;
|
||||
#we always have a color, and either do or do
|
||||
#not apply it; in SVG one can have a 'None' color
|
||||
if value is None:
|
||||
self._stroke = 0
|
||||
else:
|
||||
self._stroke = 1
|
||||
self._canvas.setStrokeColor(value)
|
||||
elif key == 'strokeWidth':
|
||||
self._canvas.setLineWidth(value)
|
||||
elif key == 'strokeLineCap': #0,1,2
|
||||
self._canvas.setLineCap(value)
|
||||
elif key == 'strokeLineJoin':
|
||||
self._canvas.setLineJoin(value)
|
||||
# elif key == 'stroke_dasharray':
|
||||
# self._canvas.setDash(array=value)
|
||||
elif key == 'strokeDashArray':
|
||||
if value:
|
||||
self._canvas.setDash(value)
|
||||
else:
|
||||
self._canvas.setDash()
|
||||
elif key == 'fillColor':
|
||||
#this has different semantics in PDF to SVG;
|
||||
#we always have a color, and either do or do
|
||||
#not apply it; in SVG one can have a 'None' color
|
||||
if value is None:
|
||||
self._fill = 0
|
||||
else:
|
||||
self._fill = 1
|
||||
self._canvas.setFillColor(value)
|
||||
elif key in ['fontSize', 'fontName']:
|
||||
# both need setting together in PDF
|
||||
# one or both might be in the deltas,
|
||||
# so need to get whichever is missing
|
||||
fontname = delta.get('fontName', self._canvas._fontname)
|
||||
fontsize = delta.get('fontSize', self._canvas._fontsize)
|
||||
self._canvas.setFont(fontname, fontsize)
|
||||
|
||||
from reportlab.platypus import Flowable
|
||||
|
||||
class GraphicsFlowable(Flowable):
|
||||
"""Flowable wrapper around a Pingo drawing"""
|
||||
def __init__(self, drawing):
|
||||
self.drawing = drawing
|
||||
self.width = self.drawing.width
|
||||
self.height = self.drawing.height
|
||||
|
||||
def draw(self):
|
||||
draw(self.drawing, self.canv, 0, 0)
|
||||
|
||||
def drawToFile(d, fn, msg="", showBoundary=rl_config._unset_, autoSize=1):
|
||||
"""Makes a one-page PDF with just the drawing.
|
||||
|
||||
If autoSize=1, the PDF will be the same size as
|
||||
the drawing; if 0, it will place the drawing on
|
||||
an A4 page with a title above it - possibly overflowing
|
||||
if too big."""
|
||||
c = Canvas(fn)
|
||||
c.setFont('Times-Roman', 36)
|
||||
c.drawString(80, 750, msg)
|
||||
c.setTitle(msg)
|
||||
|
||||
if autoSize:
|
||||
c.setPageSize((d.width, d.height))
|
||||
draw(d, c, 0, 0, showBoundary=showBoundary)
|
||||
else:
|
||||
#show with a title
|
||||
c.setFont('Times-Roman', 12)
|
||||
y = 740
|
||||
i = 1
|
||||
y = y - d.height
|
||||
draw(d, c, 80, y, showBoundary=showBoundary)
|
||||
|
||||
c.showPage()
|
||||
c.save()
|
||||
if sys.platform=='mac' and not hasattr(fn, "write"):
|
||||
try:
|
||||
import macfs, macostools
|
||||
macfs.FSSpec(fn).SetCreatorType("CARO", "PDF ")
|
||||
macostools.touched(fn)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def drawToString(d, msg="", showBoundary=rl_config._unset_,autoSize=1):
|
||||
"Returns a PDF as a string in memory, without touching the disk"
|
||||
s = getStringIO()
|
||||
drawToFile(d, s, msg=msg, showBoundary=showBoundary,autoSize=autoSize)
|
||||
return s.getvalue()
|
||||
|
||||
|
||||
#########################################################
|
||||
#
|
||||
# test code. First, defin a bunch of drawings.
|
||||
# Routine to draw them comes at the end.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
|
||||
def test():
|
||||
c = Canvas('renderPDF.pdf')
|
||||
c.setFont('Times-Roman', 36)
|
||||
c.drawString(80, 750, 'Graphics Test')
|
||||
|
||||
# print all drawings and their doc strings from the test
|
||||
# file
|
||||
|
||||
#grab all drawings from the test module
|
||||
from reportlab.graphics import testshapes
|
||||
drawings = []
|
||||
for funcname in dir(testshapes):
|
||||
if funcname[0:10] == 'getDrawing':
|
||||
drawing = eval('testshapes.' + funcname + '()') #execute it
|
||||
docstring = eval('testshapes.' + funcname + '.__doc__')
|
||||
drawings.append((drawing, docstring))
|
||||
|
||||
#print in a loop, with their doc strings
|
||||
c.setFont('Times-Roman', 12)
|
||||
y = 740
|
||||
i = 1
|
||||
for (drawing, docstring) in drawings:
|
||||
assert (docstring is not None), "Drawing %d has no docstring!" % i
|
||||
if y < 300: #allows 5-6 lines of text
|
||||
c.showPage()
|
||||
y = 740
|
||||
# draw a title
|
||||
y = y - 30
|
||||
c.setFont('Times-BoldItalic',12)
|
||||
c.drawString(80, y, 'Drawing %d' % i)
|
||||
c.setFont('Times-Roman',12)
|
||||
y = y - 14
|
||||
textObj = c.beginText(80, y)
|
||||
textObj.textLines(docstring)
|
||||
c.drawText(textObj)
|
||||
y = textObj.getY()
|
||||
y = y - drawing.height
|
||||
draw(drawing, c, 80, y)
|
||||
i = i + 1
|
||||
if y!=740: c.showPage()
|
||||
|
||||
c.save()
|
||||
print 'saved renderPDF.pdf'
|
||||
|
||||
##def testFlowable():
|
||||
## """Makes a platypus document"""
|
||||
## from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
|
||||
## from reportlab.lib.styles import getSampleStyleSheet
|
||||
## styles = getSampleStyleSheet()
|
||||
## styNormal = styles['Normal']
|
||||
##
|
||||
## doc = SimpleDocTemplate('test_flowable.pdf')
|
||||
## story = []
|
||||
## story.append(Paragraph("This sees is a drawing can work as a flowable", styNormal))
|
||||
##
|
||||
## import testdrawings
|
||||
## drawings = []
|
||||
##
|
||||
## for funcname in dir(testdrawings):
|
||||
## if funcname[0:10] == 'getDrawing':
|
||||
## drawing = eval('testdrawings.' + funcname + '()') #execute it
|
||||
## docstring = eval('testdrawings.' + funcname + '.__doc__')
|
||||
## story.append(Paragraph(docstring, styNormal))
|
||||
## story.append(Spacer(18,18))
|
||||
## story.append(drawing)
|
||||
## story.append(Spacer(36,36))
|
||||
##
|
||||
## doc.build(story)
|
||||
## print 'saves test_flowable.pdf'
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
||||
#testFlowable()
|
|
@ -1,631 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history www.reportlab.co.uk/rl-cgi/viewcvs.cgi/rlextra/graphics/Csrc/renderPM/renderP.py
|
||||
__version__=''' $Id$ '''
|
||||
"""Usage:
|
||||
from reportlab.graphics import renderPM
|
||||
renderPM.drawToFile(drawing,filename,fmt='GIF',configPIL={....})
|
||||
Other functions let you create a PM drawing as string or into a PM buffer.
|
||||
Execute the script to see some test drawings."""
|
||||
|
||||
from reportlab.graphics.shapes import *
|
||||
from reportlab.graphics.renderbase import StateTracker, getStateDelta
|
||||
from reportlab.pdfbase.pdfmetrics import getFont
|
||||
from math import sin, cos, pi, ceil
|
||||
from reportlab.lib.utils import getStringIO, open_and_read
|
||||
from reportlab import rl_config
|
||||
|
||||
class RenderPMError(Exception):
|
||||
pass
|
||||
|
||||
import string, os, sys
|
||||
|
||||
try:
|
||||
import _renderPM
|
||||
except ImportError, errMsg:
|
||||
raise ImportError, "No module named _renderPM\n" + \
|
||||
(str(errMsg)!='No module named _renderPM' and "it may be the wrong version or badly installed!" or
|
||||
"see http://www.reportlab.org/rl_addons.html")
|
||||
|
||||
from types import TupleType, ListType
|
||||
_SeqTypes = (TupleType,ListType)
|
||||
|
||||
def _getImage():
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
import Image
|
||||
return Image
|
||||
|
||||
def Color2Hex(c):
|
||||
#assert isinstance(colorobj, colors.Color) #these checks don't work well RGB
|
||||
if c: return ((0xFF&int(255*c.red)) << 16) | ((0xFF&int(255*c.green)) << 8) | (0xFF&int(255*c.blue))
|
||||
return c
|
||||
|
||||
# the main entry point for users...
|
||||
def draw(drawing, canvas, x, y, showBoundary=rl_config._unset_):
|
||||
"""As it says"""
|
||||
R = _PMRenderer()
|
||||
R.draw(drawing, canvas, x, y, showBoundary=showBoundary)
|
||||
|
||||
from reportlab.graphics.renderbase import Renderer
|
||||
class _PMRenderer(Renderer):
|
||||
"""This draws onto a pix map image. It needs to be a class
|
||||
rather than a function, as some image-specific state tracking is
|
||||
needed outside of the state info in the SVG model."""
|
||||
|
||||
def __init__(self):
|
||||
self._tracker = StateTracker()
|
||||
|
||||
def pop(self):
|
||||
self._tracker.pop()
|
||||
self.applyState()
|
||||
|
||||
def push(self,node):
|
||||
deltas = getStateDelta(node)
|
||||
self._tracker.push(deltas)
|
||||
self.applyState()
|
||||
|
||||
def applyState(self):
|
||||
s = self._tracker.getState()
|
||||
self._canvas.ctm = s['ctm']
|
||||
self._canvas.strokeWidth = s['strokeWidth']
|
||||
self._canvas.strokeColor = Color2Hex(s['strokeColor'])
|
||||
self._canvas.lineCap = s['strokeLineCap']
|
||||
self._canvas.lineJoin = s['strokeLineJoin']
|
||||
da = s['strokeDashArray']
|
||||
da = da and (0,da) or None
|
||||
self._canvas.dashArray = da
|
||||
self._canvas.fillColor = Color2Hex(s['fillColor'])
|
||||
self._canvas.setFont(s['fontName'], s['fontSize'])
|
||||
|
||||
def initState(self,x,y):
|
||||
deltas = STATE_DEFAULTS.copy()
|
||||
deltas['transform'] = self._canvas._baseCTM[0:4]+(x,y)
|
||||
self._tracker.push(deltas)
|
||||
self.applyState()
|
||||
|
||||
def drawNode(self, node):
|
||||
"""This is the recursive method called for each node
|
||||
in the tree"""
|
||||
|
||||
#apply state changes
|
||||
self.push(node)
|
||||
|
||||
#draw the object, or recurse
|
||||
self.drawNodeDispatcher(node)
|
||||
|
||||
# restore the state
|
||||
self.pop()
|
||||
|
||||
def drawRect(self, rect):
|
||||
c = self._canvas
|
||||
if rect.rx == rect.ry == 0:
|
||||
#plain old rectangle, draw clockwise (x-axis to y-axis) direction
|
||||
c.rect(rect.x,rect.y, rect.width, rect.height)
|
||||
else:
|
||||
c.roundRect(rect.x,rect.y, rect.width, rect.height, rect.rx, rect.ry)
|
||||
|
||||
def drawLine(self, line):
|
||||
self._canvas.line(line.x1,line.y1,line.x2,line.y2)
|
||||
|
||||
def drawImage(self, image):
|
||||
if image.path and os.path.exists(image.path):
|
||||
if type(image.path) is type(''):
|
||||
im = _getImage().open(image.path).convert('RGB')
|
||||
else:
|
||||
im = image.path.convert('RGB')
|
||||
srcW, srcH = im.size
|
||||
dstW, dstH = image.width, image.height
|
||||
if dstW is None: dstW = srcW
|
||||
if dstH is None: dstH = srcH
|
||||
self._canvas._aapixbuf(
|
||||
image.x, image.y, dstW, dstH,
|
||||
im.tostring(), srcW, srcH, 3,
|
||||
)
|
||||
|
||||
def drawCircle(self, circle):
|
||||
c = self._canvas
|
||||
c.circle(circle.cx,circle.cy, circle.r)
|
||||
c.fillstrokepath()
|
||||
|
||||
def drawPolyLine(self, polyline, _doClose=0):
|
||||
P = polyline.points
|
||||
assert len(P) >= 2, 'Polyline must have 1 or more points'
|
||||
c = self._canvas
|
||||
c.pathBegin()
|
||||
c.moveTo(P[0], P[1])
|
||||
for i in range(2, len(P), 2):
|
||||
c.lineTo(P[i], P[i+1])
|
||||
if _doClose:
|
||||
c.pathClose()
|
||||
c.pathFill()
|
||||
c.pathStroke()
|
||||
|
||||
def drawEllipse(self, ellipse):
|
||||
c=self._canvas
|
||||
c.ellipse(ellipse.cx, ellipse.cy, ellipse.rx,ellipse.ry)
|
||||
c.fillstrokepath()
|
||||
|
||||
def drawPolygon(self, polygon):
|
||||
self.drawPolyLine(polygon,_doClose=1)
|
||||
|
||||
def drawString(self, stringObj):
|
||||
fill = self._canvas.fillColor
|
||||
if fill is not None:
|
||||
S = self._tracker.getState()
|
||||
text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text
|
||||
if not text_anchor in ['start','inherited']:
|
||||
font, font_size = S['fontName'], S['fontSize']
|
||||
textLen = stringWidth(text, font,font_size)
|
||||
if text_anchor=='end':
|
||||
x = x-textLen
|
||||
elif text_anchor=='middle':
|
||||
x = x - textLen/2
|
||||
else:
|
||||
raise ValueError, 'bad value for textAnchor '+str(text_anchor)
|
||||
self._canvas.drawString(x,y,text)
|
||||
|
||||
def drawPath(self, path):
|
||||
c = self._canvas
|
||||
if path is EmptyClipPath:
|
||||
del c._clipPaths[-1]
|
||||
if c._clipPaths:
|
||||
P = c._clipPaths[-1]
|
||||
icp = P.isClipPath
|
||||
P.isClipPath = 1
|
||||
self.drawPath(P)
|
||||
P.isClipPath = icp
|
||||
else:
|
||||
c.clipPathClear()
|
||||
return
|
||||
c.pathBegin()
|
||||
drawFuncs = (c.moveTo, c.lineTo, c.curveTo, c.pathClose)
|
||||
from reportlab.graphics.shapes import _renderPath
|
||||
isClosed = _renderPath(path, drawFuncs)
|
||||
if path.isClipPath:
|
||||
c.clipPathSet()
|
||||
c._clipPaths.append(path)
|
||||
else:
|
||||
if isClosed: c.pathFill()
|
||||
c.pathStroke()
|
||||
|
||||
def _setFont(gs,fontName,fontSize):
|
||||
try:
|
||||
gs.setFont(fontName,fontSize)
|
||||
except _renderPM.Error, errMsg:
|
||||
if errMsg.args[0]!="Can't find font!": raise
|
||||
#here's where we try to add a font to the canvas
|
||||
try:
|
||||
f = getFont(fontName)
|
||||
if _renderPM._version<='0.98': #added reader arg in 0.99
|
||||
_renderPM.makeT1Font(fontName,f.face.findT1File(),f.encoding.vector)
|
||||
else:
|
||||
_renderPM.makeT1Font(fontName,f.face.findT1File(),f.encoding.vector,open_and_read)
|
||||
except:
|
||||
s1, s2 = map(str,sys.exc_info()[:2])
|
||||
raise RenderPMError, "Can't setFont(%s) missing the T1 files?\nOriginally %s: %s" % (fontName,s1,s2)
|
||||
gs.setFont(fontName,fontSize)
|
||||
|
||||
def _convert2pilp(im):
|
||||
Image = _getImage()
|
||||
return im.convert("P", dither=Image.NONE, palette=Image.ADAPTIVE)
|
||||
|
||||
def _saveAsPICT(im,fn,fmt,transparent=None):
|
||||
im = _convert2pilp(im)
|
||||
cols, rows = im.size
|
||||
#s = _renderPM.pil2pict(cols,rows,im.tostring(),im.im.getpalette(),transparent is not None and Color2Hex(transparent) or -1)
|
||||
s = _renderPM.pil2pict(cols,rows,im.tostring(),im.im.getpalette())
|
||||
if not hasattr(fn,'write'):
|
||||
open(os.path.splitext(fn)[0]+'.'+string.lower(fmt),'wb').write(s)
|
||||
if os.name=='mac':
|
||||
from reportlab.lib.utils import markfilename
|
||||
markfilename(fn,ext='PICT')
|
||||
else:
|
||||
fn.write(s)
|
||||
|
||||
BEZIER_ARC_MAGIC = 0.5522847498 #constant for drawing circular arcs w/ Beziers
|
||||
class PMCanvas:
|
||||
def __init__(self,w,h,dpi=72,bg=0xffffff,configPIL=None):
|
||||
'''configPIL dict is passed to image save method'''
|
||||
scale = dpi/72.0
|
||||
w = int(w*scale+0.5)
|
||||
h = int(h*scale+0.5)
|
||||
self.__dict__['_gs'] = _renderPM.gstate(w,h,bg=bg)
|
||||
self.__dict__['_bg'] = bg
|
||||
self.__dict__['_baseCTM'] = (scale,0,0,scale,0,0)
|
||||
self.__dict__['_clipPaths'] = []
|
||||
self.__dict__['configPIL'] = configPIL
|
||||
self.__dict__['_dpi'] = dpi
|
||||
self.ctm = self._baseCTM
|
||||
|
||||
def _drawTimeResize(self,w,h,bg=None):
|
||||
if bg is None: bg = self._bg
|
||||
self._drawing.width, self._drawing.height = w, h
|
||||
A = {'ctm':None, 'strokeWidth':None, 'strokeColor':None, 'lineCap':None, 'lineJoin':None, 'dashArray':None, 'fillColor':None}
|
||||
gs = self._gs
|
||||
fN,fS = gs.fontName, gs.fontSize
|
||||
for k in A.keys():
|
||||
A[k] = getattr(gs,k)
|
||||
del gs, self._gs
|
||||
gs = self.__dict__['_gs'] = _renderPM.gstate(w,h,bg=bg)
|
||||
for k in A.keys():
|
||||
setattr(self,k,A[k])
|
||||
gs.setFont(fN,fS)
|
||||
|
||||
def toPIL(self):
|
||||
im = _getImage().new('RGB', size=(self._gs.width, self._gs.height))
|
||||
im.fromstring(self._gs.pixBuf)
|
||||
return im
|
||||
|
||||
def saveToFile(self,fn,fmt=None):
|
||||
im = self.toPIL()
|
||||
if fmt is None:
|
||||
if type(fn) is not StringType:
|
||||
raise ValueError, "Invalid type '%s' for fn when fmt is None" % type(fn)
|
||||
fmt = os.path.splitext(fn)[1]
|
||||
if fmt.startswith('.'): fmt = fmt[1:]
|
||||
configPIL = self.configPIL or {}
|
||||
fmt = string.upper(fmt)
|
||||
if fmt in ['GIF']:
|
||||
im = _convert2pilp(im)
|
||||
elif fmt in ['PCT','PICT']:
|
||||
return _saveAsPICT(im,fn,fmt,transparent=configPIL.get('transparent',None))
|
||||
elif fmt in ['PNG','TIFF','BMP', 'PPM', 'TIF']:
|
||||
if fmt=='TIF': fmt = 'TIFF'
|
||||
if fmt=='PNG':
|
||||
try:
|
||||
from PIL import PngImagePlugin
|
||||
except ImportError:
|
||||
import PngImagePlugin
|
||||
elif fmt=='BMP':
|
||||
try:
|
||||
from PIL import BmpImagePlugin
|
||||
except ImportError:
|
||||
import BmpImagePlugin
|
||||
elif fmt in ('JPG','JPEG'):
|
||||
fmt = 'JPEG'
|
||||
else:
|
||||
raise RenderPMError,"Unknown image kind %s" % fmt
|
||||
if fmt=='TIFF':
|
||||
tc = configPIL.get('transparent',None)
|
||||
if tc:
|
||||
from PIL import ImageChops, Image
|
||||
T = 768*[0]
|
||||
for o, c in zip((0,256,512), tc.bitmap_rgb()):
|
||||
T[o+c] = 255
|
||||
#if type(fn) is type(''): ImageChops.invert(im.point(T).convert('L').point(255*[0]+[255])).save(fn+'_mask.gif','GIF')
|
||||
im = Image.merge('RGBA', im.split()+(ImageChops.invert(im.point(T).convert('L').point(255*[0]+[255])),))
|
||||
#if type(fn) is type(''): im.save(fn+'_masked.gif','GIF')
|
||||
for a,d in ('resolution',self._dpi),('resolution unit','inch'):
|
||||
configPIL[a] = configPIL.get(a,d)
|
||||
apply(im.save,(fn,fmt),configPIL)
|
||||
if not hasattr(fn,'write') and os.name=='mac':
|
||||
from reportlab.lib.utils import markfilename
|
||||
markfilename(fn,ext=fmt)
|
||||
|
||||
def saveToString(self,fmt='GIF'):
|
||||
s = getStringIO()
|
||||
self.saveToFile(s,fmt=fmt)
|
||||
return s.getvalue()
|
||||
|
||||
def _saveToBMP(self,f):
|
||||
'''
|
||||
Niki Spahiev, <niki@vintech.bg>, asserts that this is a respectable way to get BMP without PIL
|
||||
f is a file like object to which the BMP is written
|
||||
'''
|
||||
import struct
|
||||
gs = self._gs
|
||||
pix, width, height = gs.pixBuf, gs.width, gs.height
|
||||
f.write(struct.pack('=2sLLLLLLhh24x','BM',len(pix)+54,0,54,40,width,height,1,24))
|
||||
rowb = width * 3
|
||||
for o in range(len(pix),0,-rowb):
|
||||
f.write(pix[o-rowb:o])
|
||||
f.write( '\0' * 14 )
|
||||
|
||||
def setFont(self,fontName,fontSize,leading=None):
|
||||
_setFont(self._gs,fontName,fontSize)
|
||||
|
||||
def __setattr__(self,name,value):
|
||||
setattr(self._gs,name,value)
|
||||
|
||||
def __getattr__(self,name):
|
||||
return getattr(self._gs,name)
|
||||
|
||||
def fillstrokepath(self,stroke=1,fill=1):
|
||||
if fill: self.pathFill()
|
||||
if stroke: self.pathStroke()
|
||||
|
||||
def _bezierArcSegmentCCW(self, cx,cy, rx,ry, theta0, theta1):
|
||||
"""compute the control points for a bezier arc with theta1-theta0 <= 90.
|
||||
Points are computed for an arc with angle theta increasing in the
|
||||
counter-clockwise (CCW) direction. returns a tuple with starting point
|
||||
and 3 control points of a cubic bezier curve for the curvto opertator"""
|
||||
|
||||
# Requires theta1 - theta0 <= 90 for a good approximation
|
||||
assert abs(theta1 - theta0) <= 90
|
||||
cos0 = cos(pi*theta0/180.0)
|
||||
sin0 = sin(pi*theta0/180.0)
|
||||
x0 = cx + rx*cos0
|
||||
y0 = cy + ry*sin0
|
||||
|
||||
cos1 = cos(pi*theta1/180.0)
|
||||
sin1 = sin(pi*theta1/180.0)
|
||||
|
||||
x3 = cx + rx*cos1
|
||||
y3 = cy + ry*sin1
|
||||
|
||||
dx1 = -rx * sin0
|
||||
dy1 = ry * cos0
|
||||
|
||||
#from pdfgeom
|
||||
halfAng = pi*(theta1-theta0)/(2.0 * 180.0)
|
||||
k = abs(4.0 / 3.0 * (1.0 - cos(halfAng) ) /(sin(halfAng)) )
|
||||
x1 = x0 + dx1 * k
|
||||
y1 = y0 + dy1 * k
|
||||
|
||||
dx2 = -rx * sin1
|
||||
dy2 = ry * cos1
|
||||
|
||||
x2 = x3 - dx2 * k
|
||||
y2 = y3 - dy2 * k
|
||||
return ((x0,y0), ((x1,y1), (x2,y2), (x3,y3)) )
|
||||
|
||||
def bezierArcCCW(self, cx,cy, rx,ry, theta0, theta1):
|
||||
"""return a set of control points for Bezier approximation to an arc
|
||||
with angle increasing counter clockwise. No requirement on |theta1-theta0| <= 90
|
||||
However, it must be true that theta1-theta0 > 0."""
|
||||
|
||||
# I believe this is also clockwise
|
||||
# pretty much just like Robert Kern's pdfgeom.BezierArc
|
||||
angularExtent = theta1 - theta0
|
||||
# break down the arc into fragments of <=90 degrees
|
||||
if abs(angularExtent) <= 90.0: # we just need one fragment
|
||||
angleList = [(theta0,theta1)]
|
||||
else:
|
||||
Nfrag = int( ceil( abs(angularExtent)/90.) )
|
||||
fragAngle = float(angularExtent)/ Nfrag # this could be negative
|
||||
angleList = []
|
||||
for ii in range(Nfrag):
|
||||
a = theta0 + ii * fragAngle
|
||||
b = a + fragAngle # hmm.. is I wonder if this is precise enought
|
||||
angleList.append((a,b))
|
||||
|
||||
ctrlpts = []
|
||||
for (a,b) in angleList:
|
||||
if not ctrlpts: # first time
|
||||
[(x0,y0), pts] = self._bezierArcSegmentCCW(cx,cy, rx,ry, a,b)
|
||||
ctrlpts.append(pts)
|
||||
else:
|
||||
[(tmpx,tmpy), pts] = self._bezierArcSegmentCCW(cx,cy, rx,ry, a,b)
|
||||
ctrlpts.append(pts)
|
||||
return ((x0,y0), ctrlpts)
|
||||
|
||||
def addEllipsoidalArc(self, cx,cy, rx, ry, ang1, ang2):
|
||||
"""adds an ellisesoidal arc segment to a path, with an ellipse centered
|
||||
on cx,cy and with radii (major & minor axes) rx and ry. The arc is
|
||||
drawn in the CCW direction. Requires: (ang2-ang1) > 0"""
|
||||
|
||||
((x0,y0), ctrlpts) = self.bezierArcCCW(cx,cy, rx,ry,ang1,ang2)
|
||||
|
||||
self.lineTo(x0,y0)
|
||||
for ((x1,y1), (x2,y2),(x3,y3)) in ctrlpts:
|
||||
self.curveTo(x1,y1,x2,y2,x3,y3)
|
||||
|
||||
def drawCentredString(self, x, y, text, text_anchor='middle'):
|
||||
if self.fillColor is not None:
|
||||
textLen = stringWidth(text, self.fontName,self.fontSize)
|
||||
if text_anchor=='end':
|
||||
x = x-textLen
|
||||
elif text_anchor=='middle':
|
||||
x = x - textLen/2
|
||||
self.drawString(x,y,text)
|
||||
|
||||
def drawRightString(self, text, x, y):
|
||||
self.drawCentredString(text,x,y,text_anchor='end')
|
||||
|
||||
def line(self,x1,y1,x2,y2):
|
||||
if self.strokeColor is not None:
|
||||
self.pathBegin()
|
||||
self.moveTo(x1,y1)
|
||||
self.lineTo(x2,y2)
|
||||
self.pathStroke()
|
||||
|
||||
def rect(self,x,y,width,height,stroke=1,fill=1):
|
||||
self.pathBegin()
|
||||
self.moveTo(x, y)
|
||||
self.lineTo(x+width, y)
|
||||
self.lineTo(x+width, y + height)
|
||||
self.lineTo(x, y + height)
|
||||
self.pathClose()
|
||||
self.fillstrokepath(stroke=stroke,fill=fill)
|
||||
|
||||
def roundRect(self, x, y, width, height, rx,ry):
|
||||
"""rect(self, x, y, width, height, rx,ry):
|
||||
Draw a rectangle if rx or rx and ry are specified the corners are
|
||||
rounded with ellipsoidal arcs determined by rx and ry
|
||||
(drawn in the counter-clockwise direction)"""
|
||||
if rx==0: rx = ry
|
||||
if ry==0: ry = rx
|
||||
x2 = x + width
|
||||
y2 = y + height
|
||||
self.pathBegin()
|
||||
self.moveTo(x+rx,y)
|
||||
self.addEllipsoidalArc(x2-rx, y+ry, rx, ry, 270, 360 )
|
||||
self.addEllipsoidalArc(x2-rx, y2-ry, rx, ry, 0, 90)
|
||||
self.addEllipsoidalArc(x+rx, y2-ry, rx, ry, 90, 180)
|
||||
self.addEllipsoidalArc(x+rx, y+ry, rx, ry, 180, 270)
|
||||
self.pathClose()
|
||||
self.fillstrokepath()
|
||||
|
||||
def circle(self, cx, cy, r):
|
||||
"add closed path circle with center cx,cy and axes r: counter-clockwise orientation"
|
||||
self.ellipse(cx,cy,r,r)
|
||||
|
||||
def ellipse(self, cx,cy,rx,ry):
|
||||
"""add closed path ellipse with center cx,cy and axes rx,ry: counter-clockwise orientation
|
||||
(remember y-axis increases downward) """
|
||||
self.pathBegin()
|
||||
# first segment
|
||||
x0 = cx + rx # (x0,y0) start pt
|
||||
y0 = cy
|
||||
|
||||
x3 = cx # (x3,y3) end pt of arc
|
||||
y3 = cy-ry
|
||||
|
||||
x1 = cx+rx
|
||||
y1 = cy-ry*BEZIER_ARC_MAGIC
|
||||
|
||||
x2 = x3 + rx*BEZIER_ARC_MAGIC
|
||||
y2 = y3
|
||||
self.moveTo(x0, y0)
|
||||
self.curveTo(x1,y1,x2,y2,x3,y3)
|
||||
# next segment
|
||||
x0 = x3
|
||||
y0 = y3
|
||||
|
||||
x3 = cx-rx
|
||||
y3 = cy
|
||||
|
||||
x1 = cx-rx*BEZIER_ARC_MAGIC
|
||||
y1 = cy-ry
|
||||
|
||||
x2 = x3
|
||||
y2 = cy- ry*BEZIER_ARC_MAGIC
|
||||
self.curveTo(x1,y1,x2,y2,x3,y3)
|
||||
# next segment
|
||||
x0 = x3
|
||||
y0 = y3
|
||||
|
||||
x3 = cx
|
||||
y3 = cy+ry
|
||||
|
||||
x1 = cx-rx
|
||||
y1 = cy+ry*BEZIER_ARC_MAGIC
|
||||
|
||||
x2 = cx -rx*BEZIER_ARC_MAGIC
|
||||
y2 = cy+ry
|
||||
self.curveTo(x1,y1,x2,y2,x3,y3)
|
||||
#last segment
|
||||
x0 = x3
|
||||
y0 = y3
|
||||
|
||||
x3 = cx+rx
|
||||
y3 = cy
|
||||
|
||||
x1 = cx+rx*BEZIER_ARC_MAGIC
|
||||
y1 = cy+ry
|
||||
|
||||
x2 = cx+rx
|
||||
y2 = cy+ry*BEZIER_ARC_MAGIC
|
||||
self.curveTo(x1,y1,x2,y2,x3,y3)
|
||||
self.pathClose()
|
||||
|
||||
def saveState(self):
|
||||
'''do nothing for compatibility'''
|
||||
pass
|
||||
|
||||
def setFillColor(self,aColor):
|
||||
self.fillColor = Color2Hex(aColor)
|
||||
|
||||
def setStrokeColor(self,aColor):
|
||||
self.strokeColor = Color2Hex(aColor)
|
||||
|
||||
restoreState = saveState
|
||||
|
||||
# compatibility routines
|
||||
def setLineCap(self,cap):
|
||||
self.lineCap = cap
|
||||
|
||||
def setLineWidth(self,width):
|
||||
self.strokeWidth = width
|
||||
|
||||
def drawToPMCanvas(d, dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_):
|
||||
c = PMCanvas(d.width, d.height, dpi=dpi, bg=bg, configPIL=configPIL)
|
||||
draw(d, c, 0, 0, showBoundary=showBoundary)
|
||||
return c
|
||||
|
||||
def drawToPIL(d, dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_):
|
||||
return drawToPMCanvas(d, dpi=dpi, bg=bg, configPIL=configPIL, showBoundary=showBoundary).toPIL()
|
||||
|
||||
def drawToPILP(d, dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_):
|
||||
Image = _getImage()
|
||||
im = drawToPIL(d, dpi=dpi, bg=bg, configPIL=configPIL, showBoundary=showBoundary)
|
||||
return im.convert("P", dither=Image.NONE, palette=Image.ADAPTIVE)
|
||||
|
||||
def drawToFile(d,fn,fmt='GIF', dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_):
|
||||
'''create a pixmap and draw drawing, d to it then save as a file
|
||||
configPIL dict is passed to image save method'''
|
||||
c = drawToPMCanvas(d, dpi=dpi, bg=bg, configPIL=configPIL, showBoundary=showBoundary)
|
||||
c.saveToFile(fn,fmt)
|
||||
|
||||
def drawToString(d,fmt='GIF', dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_):
|
||||
s = getStringIO()
|
||||
drawToFile(d,s,fmt=fmt, dpi=dpi, bg=bg, configPIL=configPIL)
|
||||
return s.getvalue()
|
||||
|
||||
save = drawToFile
|
||||
|
||||
def test():
|
||||
def ext(x):
|
||||
if x=='tiff': x='tif'
|
||||
return x
|
||||
#grab all drawings from the test module and write out.
|
||||
#make a page of links in HTML to assist viewing.
|
||||
import os
|
||||
from reportlab.graphics import testshapes
|
||||
getAllTestDrawings = testshapes.getAllTestDrawings
|
||||
drawings = []
|
||||
if not os.path.isdir('pmout'):
|
||||
os.mkdir('pmout')
|
||||
htmlTop = """<html><head><title>renderPM output results</title></head>
|
||||
<body>
|
||||
<h1>renderPM results of output</h1>
|
||||
"""
|
||||
htmlBottom = """</body>
|
||||
</html>
|
||||
"""
|
||||
html = [htmlTop]
|
||||
|
||||
i = 0
|
||||
#print in a loop, with their doc strings
|
||||
for (drawing, docstring, name) in getAllTestDrawings(doTTF=hasattr(_renderPM,'ft_get_face')):
|
||||
fnRoot = 'renderPM%d' % i
|
||||
if 1 or i==10:
|
||||
w = int(drawing.width)
|
||||
h = int(drawing.height)
|
||||
html.append('<hr><h2>Drawing %s %d</h2>\n<pre>%s</pre>' % (name, i, docstring))
|
||||
|
||||
for k in ['gif','tiff', 'png', 'jpg', 'pct']:
|
||||
if k in ['gif','png','jpg','pct']:
|
||||
html.append('<p>%s format</p>\n' % string.upper(k))
|
||||
try:
|
||||
filename = '%s.%s' % (fnRoot, ext(k))
|
||||
fullpath = os.path.join('pmout', filename)
|
||||
if os.path.isfile(fullpath):
|
||||
os.remove(fullpath)
|
||||
if k=='pct':
|
||||
from reportlab.lib.colors import white
|
||||
drawToFile(drawing,fullpath,fmt=k,configPIL={'transparent':white})
|
||||
else:
|
||||
drawToFile(drawing,fullpath,fmt=k)
|
||||
if k in ['gif','png','jpg']:
|
||||
html.append('<img src="%s" border="1"><br>\n' % filename)
|
||||
print 'wrote',fullpath
|
||||
except AttributeError:
|
||||
print 'Problem drawing %s file'%k
|
||||
raise
|
||||
if os.environ.get('RL_NOEPSPREVIEW','0')=='1': drawing.__dict__['preview'] = 0
|
||||
drawing.save(formats=['eps','pdf'],outDir='pmout',fnRoot=fnRoot)
|
||||
i = i + 1
|
||||
#if i==10: break
|
||||
html.append(htmlBottom)
|
||||
htmlFileName = os.path.join('pmout', 'index.html')
|
||||
open(htmlFileName, 'w').writelines(html)
|
||||
if sys.platform=='mac':
|
||||
from reportlab.lib.utils import markfilename
|
||||
markfilename(htmlFileName,ext='HTML')
|
||||
print 'wrote %s' % htmlFileName
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
|
@ -1,859 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/renderPS.py
|
||||
__version__=''' $Id$ '''
|
||||
import string, types
|
||||
from reportlab.pdfbase.pdfmetrics import getFont, stringWidth # for font info
|
||||
from reportlab.lib.utils import fp_str, getStringIO
|
||||
from reportlab.lib.colors import black
|
||||
from reportlab.graphics.renderbase import StateTracker, getStateDelta
|
||||
from reportlab.graphics.shapes import STATE_DEFAULTS
|
||||
import math
|
||||
from types import StringType
|
||||
from operator import getitem
|
||||
from reportlab import rl_config
|
||||
|
||||
|
||||
# we need to create encoding vectors for each font we use, or they will
|
||||
# come out in Adobe's old StandardEncoding, which NOBODY uses.
|
||||
PS_WinAnsiEncoding="""
|
||||
/RE { %def
|
||||
findfont begin
|
||||
currentdict dup length dict begin
|
||||
{ %forall
|
||||
1 index /FID ne { def } { pop pop } ifelse
|
||||
} forall
|
||||
/FontName exch def dup length 0 ne { %if
|
||||
/Encoding Encoding 256 array copy def
|
||||
0 exch { %forall
|
||||
dup type /nametype eq { %ifelse
|
||||
Encoding 2 index 2 index put
|
||||
pop 1 add
|
||||
}{ %else
|
||||
exch pop
|
||||
} ifelse
|
||||
} forall
|
||||
} if pop
|
||||
currentdict dup end end
|
||||
/FontName get exch definefont pop
|
||||
} bind def
|
||||
|
||||
/WinAnsiEncoding [
|
||||
39/quotesingle 96/grave 128/euro 130/quotesinglbase/florin/quotedblbase
|
||||
/ellipsis/dagger/daggerdbl/circumflex/perthousand
|
||||
/Scaron/guilsinglleft/OE 145/quoteleft/quoteright
|
||||
/quotedblleft/quotedblright/bullet/endash/emdash
|
||||
/tilde/trademark/scaron/guilsinglright/oe/dotlessi
|
||||
159/Ydieresis 164/currency 166/brokenbar 168/dieresis/copyright
|
||||
/ordfeminine 172/logicalnot 174/registered/macron/ring
|
||||
177/plusminus/twosuperior/threesuperior/acute/mu
|
||||
183/periodcentered/cedilla/onesuperior/ordmasculine
|
||||
188/onequarter/onehalf/threequarters 192/Agrave/Aacute
|
||||
/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla
|
||||
/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute
|
||||
/Icircumflex/Idieresis/Eth/Ntilde/Ograve/Oacute
|
||||
/Ocircumflex/Otilde/Odieresis/multiply/Oslash
|
||||
/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn
|
||||
/germandbls/agrave/aacute/acircumflex/atilde/adieresis
|
||||
/aring/ae/ccedilla/egrave/eacute/ecircumflex
|
||||
/edieresis/igrave/iacute/icircumflex/idieresis
|
||||
/eth/ntilde/ograve/oacute/ocircumflex/otilde
|
||||
/odieresis/divide/oslash/ugrave/uacute/ucircumflex
|
||||
/udieresis/yacute/thorn/ydieresis
|
||||
] def
|
||||
|
||||
"""
|
||||
|
||||
class PSCanvas:
|
||||
def __init__(self,size=(300,300), PostScriptLevel=2):
|
||||
self.width, self.height = size
|
||||
self.code = []
|
||||
self._sep = '\n'
|
||||
self._strokeColor = self._fillColor = self._lineWidth = \
|
||||
self._font = self._fontSize = self._lineCap = \
|
||||
self._lineJoin = self._color = None
|
||||
|
||||
|
||||
self._fontsUsed = [] # track them as we go
|
||||
|
||||
self.setFont(STATE_DEFAULTS['fontName'],STATE_DEFAULTS['fontSize'])
|
||||
self.setStrokeColor(STATE_DEFAULTS['strokeColor'])
|
||||
self.setLineCap(2)
|
||||
self.setLineJoin(0)
|
||||
self.setLineWidth(1)
|
||||
|
||||
self.PostScriptLevel=PostScriptLevel
|
||||
|
||||
def comment(self,msg):
|
||||
self.code.append('%'+msg)
|
||||
|
||||
def drawImage(self, image, x1,y1, x2=None,y2=None): # Postscript Level2 version
|
||||
# select between postscript level 1 or level 2
|
||||
if PostScriptLevel==1:
|
||||
self._drawImageLevel1(image, x1,y1, x2=None,y2=None)
|
||||
elif PostScriptLevel == 2 :
|
||||
self._drawImageLevel2(image, x1,y1, x2=None,y2=None)
|
||||
else :
|
||||
raise 'PostScriptLevelException'
|
||||
|
||||
def clear(self):
|
||||
self.code.append('showpage') # ugh, this makes no sense oh well.
|
||||
|
||||
def save(self,f=None):
|
||||
if not hasattr(f,'write'):
|
||||
file = open(f,'wb')
|
||||
else:
|
||||
file = f
|
||||
if self.code[-1]!='showpage': self.clear()
|
||||
self.code.insert(0,'''\
|
||||
%%!PS-Adobe-3.0 EPSF-3.0
|
||||
%%%%BoundingBox: 0 0 %d %d
|
||||
%%%% Initialization:
|
||||
/m {moveto} bind def
|
||||
/l {lineto} bind def
|
||||
/c {curveto} bind def
|
||||
|
||||
%s
|
||||
''' % (self.width,self.height, PS_WinAnsiEncoding))
|
||||
|
||||
# for each font used, reencode the vectors
|
||||
fontReencode = []
|
||||
for fontName in self._fontsUsed:
|
||||
fontReencode.append('WinAnsiEncoding /%s /%s RE' % (fontName, fontName))
|
||||
self.code.insert(1, string.join(fontReencode, self._sep))
|
||||
|
||||
file.write(string.join(self.code,self._sep))
|
||||
if file is not f:
|
||||
file.close()
|
||||
from reportlab.lib.utils import markfilename
|
||||
markfilename(f,creatorcode='XPR3',filetype='EPSF')
|
||||
|
||||
def saveState(self):
|
||||
self.code.append('gsave')
|
||||
|
||||
def restoreState(self):
|
||||
self.code.append('grestore')
|
||||
|
||||
def stringWidth(self, s, font=None, fontSize=None):
|
||||
"""Return the logical width of the string if it were drawn
|
||||
in the current font (defaults to self.font)."""
|
||||
font = font or self._font
|
||||
fontSize = fontSize or self._fontSize
|
||||
return stringWidth(s, font, fontSize)
|
||||
|
||||
def setLineCap(self,v):
|
||||
if self._lineCap!=v:
|
||||
self._lineCap = v
|
||||
self.code.append('%d setlinecap'%v)
|
||||
|
||||
def setLineJoin(self,v):
|
||||
if self._lineJoin!=v:
|
||||
self._lineJoin = v
|
||||
self.code.append('%d setlinejoin'%v)
|
||||
|
||||
def setDash(self, array=[], phase=0):
|
||||
"""Two notations. pass two numbers, or an array and phase"""
|
||||
# copied and modified from reportlab.canvas
|
||||
psoperation = "setdash"
|
||||
if type(array) == types.IntType or type(array) == types.FloatType:
|
||||
self._code.append('[%s %s] 0 %s' % (array, phase, psoperation))
|
||||
elif type(array) == types.ListType or type(array) == types.TupleType:
|
||||
assert phase >= 0, "phase is a length in user space"
|
||||
textarray = string.join(map(str, array))
|
||||
self.code.append('[%s] %s %s' % (textarray, phase, psoperation))
|
||||
|
||||
def setStrokeColor(self, color):
|
||||
self._strokeColor = color
|
||||
self.setColor(color)
|
||||
|
||||
def setColor(self, color):
|
||||
if self._color!=color:
|
||||
self._color = color
|
||||
if color:
|
||||
if hasattr(color, "cyan"):
|
||||
self.code.append('%s setcmykcolor' % fp_str(color.cyan, color.magenta, color.yellow, color.black))
|
||||
else:
|
||||
self.code.append('%s setrgbcolor' % fp_str(color.red, color.green, color.blue))
|
||||
|
||||
def setFillColor(self, color):
|
||||
self._fillColor = color
|
||||
self.setColor(color)
|
||||
|
||||
def setLineWidth(self, width):
|
||||
if width != self._lineWidth:
|
||||
self._lineWidth = width
|
||||
self.code.append('%s setlinewidth' % width)
|
||||
|
||||
def setFont(self,font,fontSize,leading=None):
|
||||
if self._font!=font or self._fontSize!=fontSize:
|
||||
self._fontCodeLoc = len(self.code)
|
||||
self._font = font
|
||||
self._fontSize = fontSize
|
||||
self.code.append('')
|
||||
|
||||
def line(self, x1, y1, x2, y2):
|
||||
if self._strokeColor != None:
|
||||
self.code.append('%s m %s l stroke' % (fp_str(x1, y1), fp_str(x2, y2)))
|
||||
|
||||
def _escape(self, s):
|
||||
'''
|
||||
return a copy of string s with special characters in postscript strings
|
||||
escaped with backslashes.
|
||||
Have not handled characters that are converted normally in python strings
|
||||
i.e. \n -> newline
|
||||
'''
|
||||
str = string.replace(s, chr(0x5C), r'\\' )
|
||||
str = string.replace(str, '(', '\(' )
|
||||
str = string.replace(str, ')', '\)')
|
||||
return str
|
||||
|
||||
def drawString(self, x, y, s, angle=0):
|
||||
if self._fillColor != None:
|
||||
if not self.code[self._fontCodeLoc]:
|
||||
psName = getFont(self._font).face.name
|
||||
self.code[self._fontCodeLoc]='(%s) findfont %s scalefont setfont' % (psName,fp_str(self._fontSize))
|
||||
if psName not in self._fontsUsed:
|
||||
self._fontsUsed.append(psName)
|
||||
self.setColor(self._fillColor)
|
||||
s = self._escape(s)
|
||||
## before inverting...
|
||||
## if angle == 0 : # do special case of angle = 0 first. Avoids a bunch of gsave/grestore ops
|
||||
## self.code.append('%s m 1 -1 scale (%s) show 1 -1 scale' % (fp_str(x,y),s))
|
||||
## else : # general case, rotated text
|
||||
## self.code.append('gsave %s %s translate %s rotate' % (x,y,angle))
|
||||
## self.code.append('0 0 m 1 -1 scale (%s) show' % s)
|
||||
## self.code.append('grestore')
|
||||
if angle == 0 : # do special case of angle = 0 first. Avoids a bunch of gsave/grestore ops
|
||||
self.code.append('%s m (%s) show ' % (fp_str(x,y),s))
|
||||
else : # general case, rotated text
|
||||
self.code.append('gsave %s %s translate %s rotate' % (x,y,angle))
|
||||
self.code.append('0 0 m (%s) show' % s)
|
||||
self.code.append('grestore')
|
||||
|
||||
def drawCentredString(self, x, y, text, text_anchor='middle'):
|
||||
if self.fillColor is not None:
|
||||
textLen = stringWidth(text, self._font,self._fontSize)
|
||||
if text_anchor=='end':
|
||||
x = x-textLen
|
||||
elif text_anchor=='middle':
|
||||
x = x - textLen/2
|
||||
self.drawString(x,y,text)
|
||||
|
||||
def drawRightString(self, text, x, y):
|
||||
self.drawCentredString(text,x,y,text_anchor='end')
|
||||
|
||||
def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4, closed=0):
|
||||
codeline = '%s m %s curveto'
|
||||
data = (fp_str(x1, y1), fp_str(x2, y2, x3, y3, x4, y4))
|
||||
if self._fillColor != None:
|
||||
self.setColor(self._fillColor)
|
||||
self.code.append((codeline % data) + ' eofill')
|
||||
if self._strokeColor != None:
|
||||
self.setColor(self._strokeColor)
|
||||
self.code.append((codeline % data)
|
||||
+ ((closed and ' closepath') or '')
|
||||
+ ' stroke')
|
||||
|
||||
########################################################################################
|
||||
|
||||
def rect(self, x1,y1, x2,y2, stroke=1, fill=1):
|
||||
"Draw a rectangle between x1,y1, and x2,y2"
|
||||
# Path is drawn in counter-clockwise direction"
|
||||
|
||||
x1, x2 = min(x1,x2), max(x1, x2) # from piddle.py
|
||||
y1, y2 = min(y1,y2), max(y1, y2)
|
||||
self.polygon(((x1,y1),(x2,y1),(x2,y2),(x1,y2)), closed=1, stroke=stroke, fill = fill)
|
||||
|
||||
def roundRect(self, x1,y1, x2,y2, rx=8, ry=8):
|
||||
"""Draw a rounded rectangle between x1,y1, and x2,y2,
|
||||
with corners inset as ellipses with x radius rx and y radius ry.
|
||||
These should have x1<x2, y1<y2, rx>0, and ry>0."""
|
||||
# Path is drawn in counter-clockwise direction
|
||||
|
||||
x1, x2 = min(x1,x2), max(x1, x2) # from piddle.py
|
||||
y1, y2 = min(y1,y2), max(y1, y2)
|
||||
|
||||
# Note: arcto command draws a line from current point to beginning of arc
|
||||
# save current matrix, translate to center of ellipse, scale by rx ry, and draw
|
||||
# a circle of unit radius in counterclockwise dir, return to original matrix
|
||||
# arguments are (cx, cy, rx, ry, startAngle, endAngle)
|
||||
ellipsePath = 'matrix currentmatrix %s %s translate %s %s scale 0 0 1 %s %s arc setmatrix'
|
||||
|
||||
# choice between newpath and moveTo beginning of arc
|
||||
# go with newpath for precision, does this violate any assumptions in code???
|
||||
rrCode = ['newpath'] # Round Rect code path
|
||||
# upper left corner ellipse is first
|
||||
rrCode.append(ellipsePath % (x1+rx, y1+ry, rx, -ry, 90, 180))
|
||||
rrCode.append(ellipsePath % (x1+rx, y2-ry, rx, -ry, 180, 270))
|
||||
rrCode.append(ellipsePath % (x2-rx, y2-ry, rx, -ry, 270, 360))
|
||||
rrCode.append(ellipsePath % (x2-rx, y1+ry, rx, -ry, 0, 90) )
|
||||
rrCode.append('closepath')
|
||||
|
||||
self._fillAndStroke(rrCode)
|
||||
|
||||
def ellipse(self, x1,y1, x2,y2):
|
||||
"""Draw an orthogonal ellipse inscribed within the rectangle x1,y1,x2,y2.
|
||||
These should have x1<x2 and y1<y2."""
|
||||
#Just invoke drawArc to actually draw the ellipse
|
||||
self.drawArc(x1,y1, x2,y2)
|
||||
|
||||
def circle(self, xc, yc, r):
|
||||
self.ellipse(xc-r,yc-r, xc+r,yc+r)
|
||||
|
||||
def drawArc(self, x1,y1, x2,y2, startAng=0, extent=360, fromcenter=0):
|
||||
"""Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
|
||||
starting at startAng degrees and covering extent degrees. Angles
|
||||
start with 0 to the right (+x) and increase counter-clockwise.
|
||||
These should have x1<x2 and y1<y2."""
|
||||
#calculate centre of ellipse
|
||||
#print "x1,y1,x2,y2,startAng,extent,fromcenter", x1,y1,x2,y2,startAng,extent,fromcenter
|
||||
cx, cy = (x1+x2)/2.0, (y1+y2)/2.0
|
||||
rx, ry = (x2-x1)/2.0, (y2-y1)/2.0
|
||||
|
||||
codeline = self._genArcCode(x1, y1, x2, y2, startAng, extent)
|
||||
|
||||
startAngleRadians = math.pi*startAng/180.0
|
||||
extentRadians = math.pi*extent/180.0
|
||||
endAngleRadians = startAngleRadians + extentRadians
|
||||
|
||||
codelineAppended = 0
|
||||
|
||||
# fill portion
|
||||
|
||||
if self._fillColor != None:
|
||||
self.setColor(self._fillColor)
|
||||
self.code.append(codeline)
|
||||
codelineAppended = 1
|
||||
if self._strokeColor!=None: self.code.append('gsave')
|
||||
self.lineTo(cx,cy)
|
||||
self.code.append('eofill')
|
||||
if self._strokeColor!=None: self.code.append('grestore')
|
||||
|
||||
# stroke portion
|
||||
if self._strokeColor != None:
|
||||
# this is a bit hacked up. There is certainly a better way...
|
||||
self.setColor(self._strokeColor)
|
||||
(startx, starty) = (cx+rx*math.cos(startAngleRadians), cy+ry*math.sin(startAngleRadians))
|
||||
if not codelineAppended:
|
||||
self.code.append(codeline)
|
||||
if fromcenter:
|
||||
# move to center
|
||||
self.lineTo(cx,cy)
|
||||
self.lineTo(startx, starty)
|
||||
self.code.append('closepath')
|
||||
self.code.append('stroke')
|
||||
|
||||
def _genArcCode(self, x1, y1, x2, y2, startAng, extent):
|
||||
"Calculate the path for an arc inscribed in rectangle defined by (x1,y1),(x2,y2)"
|
||||
#calculate semi-minor and semi-major axes of ellipse
|
||||
xScale = abs((x2-x1)/2.0)
|
||||
yScale = abs((y2-y1)/2.0)
|
||||
#calculate centre of ellipse
|
||||
x, y = (x1+x2)/2.0, (y1+y2)/2.0
|
||||
|
||||
codeline = 'matrix currentmatrix %s %s translate %s %s scale 0 0 1 %s %s %s setmatrix'
|
||||
|
||||
if extent >= 0:
|
||||
arc='arc'
|
||||
else:
|
||||
arc='arcn'
|
||||
data = (x,y, xScale, yScale, startAng, startAng+extent, arc)
|
||||
|
||||
return codeline % data
|
||||
|
||||
def polygon(self, p, closed=0, stroke=1, fill=1):
|
||||
assert len(p) >= 2, 'Polygon must have 2 or more points'
|
||||
|
||||
start = p[0]
|
||||
p = p[1:]
|
||||
|
||||
polyCode = []
|
||||
polyCode.append("%s m" % fp_str(start))
|
||||
for point in p:
|
||||
polyCode.append("%s l" % fp_str(point))
|
||||
if closed:
|
||||
polyCode.append("closepath")
|
||||
|
||||
self._fillAndStroke(polyCode,stroke=stroke,fill=fill)
|
||||
|
||||
def lines(self, lineList, color=None, width=None):
|
||||
if self._strokeColor != None:
|
||||
self._setColor(self._strokeColor)
|
||||
codeline = '%s m %s l stroke'
|
||||
for line in lineList:
|
||||
self.code.append(codeline % (fp_str(line[0]),fp_str(line[1])))
|
||||
|
||||
def moveTo(self,x,y):
|
||||
self.code.append('%s m' % fp_str(x, y))
|
||||
|
||||
def lineTo(self,x,y):
|
||||
self.code.append('%s l' % fp_str(x, y))
|
||||
|
||||
def curveTo(self,x1,y1,x2,y2,x3,y3):
|
||||
self.code.append('%s c' % fp_str(x1,y1,x2,y2,x3,y3))
|
||||
|
||||
def closePath(self):
|
||||
self.code.append('closepath')
|
||||
|
||||
def polyLine(self, p):
|
||||
assert len(p) >= 1, 'Polyline must have 1 or more points'
|
||||
if self._strokeColor != None:
|
||||
self.setColor(self._strokeColor)
|
||||
self.moveTo(p[0][0], p[0][1])
|
||||
for t in p[1:]:
|
||||
self.lineTo(t[0], t[1])
|
||||
self.code.append('stroke')
|
||||
|
||||
|
||||
def drawFigure(self, partList, closed=0):
|
||||
figureCode = []
|
||||
first = 1
|
||||
|
||||
for part in partList:
|
||||
op = part[0]
|
||||
args = list(part[1:])
|
||||
|
||||
if op == figureLine:
|
||||
if first:
|
||||
first = 0
|
||||
figureCode.append("%s m" % fp_str(args[:2]))
|
||||
else:
|
||||
figureCode.append("%s l" % fp_str(args[:2]))
|
||||
figureCode.append("%s l" % fp_str(args[2:]))
|
||||
|
||||
elif op == figureArc:
|
||||
first = 0
|
||||
x1,y1,x2,y2,startAngle,extent = args[:6]
|
||||
figureCode.append(self._genArcCode(x1,y1,x2,y2,startAngle,extent))
|
||||
|
||||
elif op == figureCurve:
|
||||
if first:
|
||||
first = 0
|
||||
figureCode.append("%s m" % fp_str(args[:2]))
|
||||
else:
|
||||
figureCode.append("%s l" % fp_str(args[:2]))
|
||||
figureCode.append("%s curveto" % fp_str(args[2:]))
|
||||
else:
|
||||
raise TypeError, "unknown figure operator: "+op
|
||||
|
||||
if closed:
|
||||
figureCode.append("closepath")
|
||||
self._fillAndStroke(figureCode)
|
||||
|
||||
def _fillAndStroke(self,code,clip=0,fill=1,stroke=1):
|
||||
fill = self._fillColor and fill
|
||||
stroke = self._strokeColor and stroke
|
||||
if fill or stroke or clip:
|
||||
self.code.extend(code)
|
||||
if fill:
|
||||
if stroke or clip: self.code.append("gsave")
|
||||
self.setColor(self._fillColor)
|
||||
self.code.append("eofill")
|
||||
if stroke or clip: self.code.append("grestore")
|
||||
if stroke:
|
||||
if clip: self.code.append("gsave")
|
||||
self.setColor(self._strokeColor)
|
||||
self.code.append("stroke")
|
||||
if clip: self.code.append("grestore")
|
||||
if clip:
|
||||
self.code.append("clip")
|
||||
self.code.append("newpath")
|
||||
|
||||
|
||||
def translate(self,x,y):
|
||||
self.code.append('%s translate' % fp_str(x,y))
|
||||
|
||||
def scale(self,x,y):
|
||||
self.code.append('%s scale' % fp_str(x,y))
|
||||
|
||||
def transform(self,a,b,c,d,e,f):
|
||||
self.code.append('[%s] concat' % fp_str(a,b,c,d,e,f))
|
||||
|
||||
def _drawTimeResize(self,w,h):
|
||||
'''if this is used we're probably in the wrong world'''
|
||||
self.width, self.height = w, h
|
||||
|
||||
############################################################################################
|
||||
# drawImage(self. image, x1, y1, x2=None, y2=None) is now defined by either _drawImageLevel1
|
||||
# ._drawImageLevel2, the choice is made in .__init__ depending on option
|
||||
def _drawImageLevel1(self, image, x1, y1, x2=None,y2=None):
|
||||
# Postscript Level1 version available for fallback mode when Level2 doesn't work
|
||||
"""drawImage(self,image,x1,y1,x2=None,y2=None) : If x2 and y2 are ommitted, they are
|
||||
calculated from image size. (x1,y1) is upper left of image, (x2,y2) is lower right of
|
||||
image in piddle coordinates."""
|
||||
# For now let's start with 24 bit RGB images (following piddlePDF again)
|
||||
print "Trying to drawImage in piddlePS"
|
||||
component_depth = 8
|
||||
myimage = image.convert('RGB')
|
||||
imgwidth, imgheight = myimage.size
|
||||
if not x2:
|
||||
x2 = imgwidth + x1
|
||||
if not y2:
|
||||
y2 = y1 + imgheight
|
||||
drawwidth = x2 - x1
|
||||
drawheight = y2 - y1
|
||||
print 'Image size (%d, %d); Draw size (%d, %d)' % (imgwidth, imgheight, drawwidth, drawheight)
|
||||
# now I need to tell postscript how big image is
|
||||
|
||||
# "image operators assume that they receive sample data from
|
||||
# their data source in x-axis major index order. The coordinate
|
||||
# of the lower-left corner of the first sample is (0,0), of the
|
||||
# second (1,0) and so on" -PS2 ref manual p. 215
|
||||
#
|
||||
# The ImageMatrix maps unit squre of user space to boundary of the source image
|
||||
#
|
||||
|
||||
# The CurrentTransformationMatrix (CTM) maps the unit square of
|
||||
# user space to the rect...on the page that is to receive the
|
||||
# image. A common ImageMatrix is [width 0 0 -height 0 height]
|
||||
# (for a left to right, top to bottom image )
|
||||
|
||||
# first let's map the user coordinates start at offset x1,y1 on page
|
||||
|
||||
self.code.extend([
|
||||
'gsave',
|
||||
'%s %s translate' % (x1,-y1 - drawheight), # need to start are lower left of image
|
||||
'%s %s scale' % (drawwidth,drawheight),
|
||||
'/scanline %d 3 mul string def' % imgwidth # scanline by multiples of image width
|
||||
])
|
||||
|
||||
# now push the dimensions and depth info onto the stack
|
||||
# and push the ImageMatrix to map the source to the target rectangle (see above)
|
||||
# finally specify source (PS2 pp. 225 ) and by exmample
|
||||
self.code.extend([
|
||||
'%s %s %s' % (imgwidth, imgheight, component_depth),
|
||||
'[%s %s %s %s %s %s]' % (imgwidth, 0, 0, -imgheight, 0, imgheight),
|
||||
'{ currentfile scanline readhexstring pop } false 3',
|
||||
'colorimage '
|
||||
])
|
||||
|
||||
# data source output--now we just need to deliver a hex encode
|
||||
# series of lines of the right overall size can follow
|
||||
# piddlePDF again
|
||||
|
||||
rawimage = myimage.tostring()
|
||||
assert(len(rawimage) == imgwidth*imgheight, 'Wrong amount of data for image')
|
||||
#compressed = zlib.compress(rawimage) # no zlib at moment
|
||||
hex_encoded = self._AsciiHexEncode(rawimage)
|
||||
|
||||
# write in blocks of 78 chars per line
|
||||
outstream = getStringIO(hex_encoded)
|
||||
|
||||
dataline = outstream.read(78)
|
||||
while dataline <> "":
|
||||
self.code.append(dataline)
|
||||
dataline= outstream.read(78)
|
||||
self.code.append('% end of image data') # for clarity
|
||||
self.code.append('grestore') # return coordinates to normal
|
||||
|
||||
# end of drawImage
|
||||
def _AsciiHexEncode(self, input): # also based on piddlePDF
|
||||
"Helper function used by images"
|
||||
output = getStringIO()
|
||||
for char in input:
|
||||
output.write('%02x' % ord(char))
|
||||
return output.getvalue()
|
||||
|
||||
def _drawImageLevel2(self, image, x1,y1, x2=None,y2=None): # Postscript Level2 version
|
||||
Image = import_Image()
|
||||
if not Image: return
|
||||
### what sort of image are we to draw
|
||||
if image.mode=='L' :
|
||||
print 'found image.mode= L'
|
||||
imBitsPerComponent = 8
|
||||
imNumComponents = 1
|
||||
myimage = image
|
||||
elif image.mode == '1':
|
||||
print 'found image.mode= 1'
|
||||
myimage = image.convert('L')
|
||||
imNumComponents = 1
|
||||
myimage = image
|
||||
else :
|
||||
myimage = image.convert('RGB')
|
||||
imNumComponents = 3
|
||||
imBitsPerComponent = 8
|
||||
|
||||
imwidth, imheight = myimage.size
|
||||
# print 'imwidth = %s, imheight = %s' % myimage.size
|
||||
if not x2:
|
||||
x2 = imwidth + x1
|
||||
if not y2:
|
||||
y2 = y1 + imheight
|
||||
drawwidth = x2 - x1
|
||||
drawheight = y2 - y1
|
||||
self.code.extend([
|
||||
'gsave',
|
||||
'%s %s translate' % (x1,-y1 - drawheight), # need to start are lower left of image
|
||||
'%s %s scale' % (drawwidth,drawheight)])
|
||||
|
||||
if imNumComponents == 3 :
|
||||
self.code.append('/DeviceRGB setcolorspace')
|
||||
elif imNumComponents == 1 :
|
||||
self.code.append('/DeviceGray setcolorspace')
|
||||
print 'setting colorspace gray'
|
||||
# create the image dictionary
|
||||
self.code.append("""
|
||||
<<
|
||||
/ImageType 1
|
||||
/Width %d /Height %d %% dimensions of source image
|
||||
/BitsPerComponent %d""" % (imwidth, imheight, imBitsPerComponent) )
|
||||
|
||||
if imNumComponents == 1:
|
||||
self.code.append('/Decode [0 1]')
|
||||
if imNumComponents == 3:
|
||||
self.code.append('/Decode [0 1 0 1 0 1] %% decode color values normally')
|
||||
|
||||
self.code.extend([ '/ImageMatrix [%s 0 0 %s 0 %s]' % (imwidth, -imheight, imheight),
|
||||
'/DataSource currentfile /ASCIIHexDecode filter',
|
||||
'>> % End image dictionary',
|
||||
'image'])
|
||||
# after image operator just need to dump image dat to file as hexstring
|
||||
rawimage = myimage.tostring()
|
||||
assert(len(rawimage) == imwidth*imheight, 'Wrong amount of data for image')
|
||||
#compressed = zlib.compress(rawimage) # no zlib at moment
|
||||
hex_encoded = self._AsciiHexEncode(rawimage)
|
||||
|
||||
# write in blocks of 78 chars per line
|
||||
outstream = getStringIO(hex_encoded)
|
||||
|
||||
dataline = outstream.read(78)
|
||||
while dataline <> "":
|
||||
self.code.append(dataline)
|
||||
dataline= outstream.read(78)
|
||||
self.code.append('> % end of image data') # > is EOD for hex encoded filterfor clarity
|
||||
self.code.append('grestore') # return coordinates to normal
|
||||
|
||||
# renderpdf - draws them onto a canvas
|
||||
"""Usage:
|
||||
from reportlab.graphics import renderPS
|
||||
renderPS.draw(drawing, canvas, x, y)
|
||||
Execute the script to see some test drawings."""
|
||||
from shapes import *
|
||||
from renderbase import Renderer
|
||||
|
||||
# hack so we only get warnings once each
|
||||
#warnOnce = WarnOnce()
|
||||
|
||||
# the main entry point for users...
|
||||
def draw(drawing, canvas, x=0, y=0, showBoundary=rl_config.showBoundary):
|
||||
"""As it says"""
|
||||
R = _PSRenderer()
|
||||
R.draw(drawing, canvas, x, y, showBoundary=showBoundary)
|
||||
|
||||
def _pointsFromList(L):
|
||||
'''
|
||||
given a list of coordinates [x0, y0, x1, y1....]
|
||||
produce a list of points [(x0,y0), (y1,y0),....]
|
||||
'''
|
||||
P=[]
|
||||
for i in range(0,len(L),2):
|
||||
P.append((L[i],L[i+1]))
|
||||
return P
|
||||
|
||||
class _PSRenderer(Renderer):
|
||||
"""This draws onto a EPS document. It needs to be a class
|
||||
rather than a function, as some EPS-specific state tracking is
|
||||
needed outside of the state info in the SVG model."""
|
||||
|
||||
def __init__(self):
|
||||
self._tracker = StateTracker()
|
||||
|
||||
def drawNode(self, node):
|
||||
"""This is the recursive method called for each node
|
||||
in the tree"""
|
||||
self._canvas.comment('begin node %s'%`node`)
|
||||
color = self._canvas._color
|
||||
if not (isinstance(node, Path) and node.isClipPath):
|
||||
self._canvas.saveState()
|
||||
|
||||
#apply state changes
|
||||
deltas = getStateDelta(node)
|
||||
self._tracker.push(deltas)
|
||||
self.applyStateChanges(deltas, {})
|
||||
|
||||
#draw the object, or recurse
|
||||
self.drawNodeDispatcher(node)
|
||||
|
||||
rDeltas = self._tracker.pop()
|
||||
if not (isinstance(node, Path) and node.isClipPath):
|
||||
self._canvas.restoreState()
|
||||
self._canvas.comment('end node %s'%`node`)
|
||||
self._canvas._color = color
|
||||
|
||||
#restore things we might have lost (without actually doing anything).
|
||||
for k, v in rDeltas.items():
|
||||
if self._restores.has_key(k):
|
||||
setattr(self._canvas,self._restores[k],v)
|
||||
|
||||
## _restores = {'stroke':'_stroke','stroke_width': '_lineWidth','stroke_linecap':'_lineCap',
|
||||
## 'stroke_linejoin':'_lineJoin','fill':'_fill','font_family':'_font',
|
||||
## 'font_size':'_fontSize'}
|
||||
_restores = {'strokeColor':'_strokeColor','strokeWidth': '_lineWidth','strokeLineCap':'_lineCap',
|
||||
'strokeLineJoin':'_lineJoin','fillColor':'_fillColor','fontName':'_font',
|
||||
'fontSize':'_fontSize'}
|
||||
|
||||
def drawRect(self, rect):
|
||||
if rect.rx == rect.ry == 0:
|
||||
#plain old rectangle
|
||||
self._canvas.rect(
|
||||
rect.x, rect.y,
|
||||
rect.x+rect.width, rect.y+rect.height)
|
||||
else:
|
||||
#cheat and assume ry = rx; better to generalize
|
||||
#pdfgen roundRect function. TODO
|
||||
self._canvas.roundRect(
|
||||
rect.x, rect.y,
|
||||
rect.x+rect.width, rect.y+rect.height, rect.rx, rect.ry
|
||||
)
|
||||
|
||||
def drawLine(self, line):
|
||||
if self._canvas._strokeColor:
|
||||
self._canvas.line(line.x1, line.y1, line.x2, line.y2)
|
||||
|
||||
def drawCircle(self, circle):
|
||||
self._canvas.circle( circle.cx, circle.cy, circle.r)
|
||||
|
||||
def drawWedge(self, wedge):
|
||||
yradius, radius1, yradius1 = wedge._xtraRadii()
|
||||
if (radius1==0 or radius1 is None) and (yradius1==0 or yradius1 is None):
|
||||
startangledegrees = wedge.startangledegrees
|
||||
endangledegrees = wedge.endangledegrees
|
||||
centerx= wedge.centerx
|
||||
centery = wedge.centery
|
||||
radius = wedge.radius
|
||||
extent = endangledegrees - startangledegrees
|
||||
self._canvas.drawArc(centerx-radius, centery-yradius, centerx+radius, centery+yradius,
|
||||
startangledegrees, extent, fromcenter=1)
|
||||
else:
|
||||
self.drawPolygon(wedge.asPolygon())
|
||||
|
||||
def drawPolyLine(self, p):
|
||||
if self._canvas._strokeColor:
|
||||
self._canvas.polyLine(_pointsFromList(p.points))
|
||||
|
||||
def drawEllipse(self, ellipse):
|
||||
#need to convert to pdfgen's bounding box representation
|
||||
x1 = ellipse.cx - ellipse.rx
|
||||
x2 = ellipse.cx + ellipse.rx
|
||||
y1 = ellipse.cy - ellipse.ry
|
||||
y2 = ellipse.cy + ellipse.ry
|
||||
self._canvas.ellipse(x1,y1,x2,y2)
|
||||
|
||||
def drawPolygon(self, p):
|
||||
self._canvas.polygon(_pointsFromList(p.points), closed=1)
|
||||
|
||||
def drawString(self, stringObj):
|
||||
if self._canvas._fillColor:
|
||||
S = self._tracker.getState()
|
||||
text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text
|
||||
if not text_anchor in ['start','inherited']:
|
||||
font, fontSize = S['fontName'], S['fontSize']
|
||||
textLen = stringWidth(text, font,fontSize)
|
||||
if text_anchor=='end':
|
||||
x = x-textLen
|
||||
elif text_anchor=='middle':
|
||||
x = x - textLen/2
|
||||
else:
|
||||
raise ValueError, 'bad value for text_anchor '+str(text_anchor)
|
||||
self._canvas.drawString(x,y,text)
|
||||
|
||||
def drawPath(self, path):
|
||||
from reportlab.graphics.shapes import _renderPath
|
||||
c = self._canvas
|
||||
drawFuncs = (c.moveTo, c.lineTo, c.curveTo, c.closePath)
|
||||
isClosed = _renderPath(path, drawFuncs)
|
||||
if not isClosed:
|
||||
c._fillColor = None
|
||||
c._fillAndStroke([], clip=path.isClipPath)
|
||||
|
||||
def applyStateChanges(self, delta, newState):
|
||||
"""This takes a set of states, and outputs the operators
|
||||
needed to set those properties"""
|
||||
for key, value in delta.items():
|
||||
if key == 'transform':
|
||||
self._canvas.transform(value[0], value[1], value[2],
|
||||
value[3], value[4], value[5])
|
||||
elif key == 'strokeColor':
|
||||
#this has different semantics in PDF to SVG;
|
||||
#we always have a color, and either do or do
|
||||
#not apply it; in SVG one can have a 'None' color
|
||||
self._canvas.setStrokeColor(value)
|
||||
elif key == 'strokeWidth':
|
||||
self._canvas.setLineWidth(value)
|
||||
elif key == 'strokeLineCap': #0,1,2
|
||||
self._canvas.setLineCap(value)
|
||||
elif key == 'strokeLineJoin':
|
||||
self._canvas.setLineJoin(value)
|
||||
elif key == 'strokeDashArray':
|
||||
if value:
|
||||
self._canvas.setDash(value)
|
||||
else:
|
||||
self._canvas.setDash()
|
||||
## elif key == 'stroke_opacity':
|
||||
## warnOnce('Stroke Opacity not supported yet')
|
||||
elif key == 'fillColor':
|
||||
#this has different semantics in PDF to SVG;
|
||||
#we always have a color, and either do or do
|
||||
#not apply it; in SVG one can have a 'None' color
|
||||
self._canvas.setFillColor(value)
|
||||
## elif key == 'fill_rule':
|
||||
## warnOnce('Fill rules not done yet')
|
||||
## elif key == 'fill_opacity':
|
||||
## warnOnce('Fill opacity not done yet')
|
||||
elif key in ['fontSize', 'fontName']:
|
||||
# both need setting together in PDF
|
||||
# one or both might be in the deltas,
|
||||
# so need to get whichever is missing
|
||||
fontname = delta.get('fontName', self._canvas._font)
|
||||
fontsize = delta.get('fontSize', self._canvas._fontSize)
|
||||
self._canvas.setFont(fontname, fontsize)
|
||||
|
||||
def drawToFile(d,fn, showBoundary=rl_config.showBoundary):
|
||||
c = PSCanvas((d.width,d.height))
|
||||
draw(d, c, 0, 0, showBoundary=showBoundary)
|
||||
c.save(fn)
|
||||
|
||||
def drawToString(d, showBoundary=rl_config.showBoundary):
|
||||
"Returns a PS as a string in memory, without touching the disk"
|
||||
s = getStringIO()
|
||||
drawToFile(d, s, showBoundary=showBoundary)
|
||||
return s.getvalue()
|
||||
|
||||
#########################################################
|
||||
#
|
||||
# test code. First, defin a bunch of drawings.
|
||||
# Routine to draw them comes at the end.
|
||||
#
|
||||
#########################################################
|
||||
def test(outdir='epsout'):
|
||||
import os
|
||||
# print all drawings and their doc strings from the test
|
||||
# file
|
||||
if not os.path.isdir(outdir):
|
||||
os.mkdir(outdir)
|
||||
#grab all drawings from the test module
|
||||
import testshapes
|
||||
drawings = []
|
||||
|
||||
for funcname in dir(testshapes):
|
||||
#if funcname[0:11] == 'getDrawing2':
|
||||
# print 'hacked to only show drawing 2'
|
||||
if funcname[0:10] == 'getDrawing':
|
||||
drawing = eval('testshapes.' + funcname + '()') #execute it
|
||||
docstring = eval('testshapes.' + funcname + '.__doc__')
|
||||
drawings.append((drawing, docstring))
|
||||
|
||||
i = 0
|
||||
for (d, docstring) in drawings:
|
||||
filename = outdir + os.sep + 'renderPS_%d.eps'%i
|
||||
drawToFile(d,filename)
|
||||
print 'saved', filename
|
||||
i = i + 1
|
||||
|
||||
if __name__=='__main__':
|
||||
import sys
|
||||
if len(sys.argv)>1:
|
||||
outdir = sys.argv[1]
|
||||
else:
|
||||
outdir = 'epsout'
|
||||
test(outdir)
|
|
@ -1,813 +0,0 @@
|
|||
"""An experimental SVG renderer for the ReportLab graphics framework.
|
||||
|
||||
This will create SVG code from the ReportLab Graphics API (RLG).
|
||||
To read existing SVG code and convert it into ReportLab graphics
|
||||
objects download the svglib module here:
|
||||
|
||||
http://python.net/~gherman/#svglib
|
||||
"""
|
||||
|
||||
import math, string, types, sys, os
|
||||
from types import StringType
|
||||
from operator import getitem
|
||||
|
||||
from reportlab.pdfbase.pdfmetrics import stringWidth # for font info
|
||||
from reportlab.lib.utils import fp_str
|
||||
from reportlab.lib.colors import black
|
||||
from reportlab.graphics.renderbase import StateTracker, getStateDelta, Renderer
|
||||
from reportlab.graphics.shapes import STATE_DEFAULTS, Path, UserNode
|
||||
from reportlab.graphics.shapes import * # (only for test0)
|
||||
from reportlab import rl_config
|
||||
from reportlab.lib.utils import getStringIO
|
||||
|
||||
from xml.dom import getDOMImplementation
|
||||
|
||||
|
||||
### some constants ###
|
||||
|
||||
sin = math.sin
|
||||
cos = math.cos
|
||||
pi = math.pi
|
||||
|
||||
LINE_STYLES = 'stroke-width stroke-linecap stroke fill stroke-dasharray'
|
||||
TEXT_STYLES = 'font-family font-size'
|
||||
|
||||
|
||||
### top-level user function ###
|
||||
|
||||
def drawToString(d, showBoundary=rl_config.showBoundary):
|
||||
"Returns a SVG as a string in memory, without touching the disk"
|
||||
s = getStringIO()
|
||||
drawToFile(d, s, showBoundary=showBoundary)
|
||||
return s.getvalue()
|
||||
|
||||
|
||||
def drawToFile(d, fn, showBoundary=rl_config.showBoundary):
|
||||
c = SVGCanvas((d.width, d.height))
|
||||
draw(d, c, 0, 0, showBoundary=showBoundary)
|
||||
c.save(fn)
|
||||
|
||||
|
||||
def draw(drawing, canvas, x=0, y=0, showBoundary=rl_config.showBoundary):
|
||||
"""As it says."""
|
||||
|
||||
r = _SVGRenderer()
|
||||
r.draw(drawing, canvas, x, y, showBoundary=showBoundary)
|
||||
|
||||
|
||||
### helper functions ###
|
||||
|
||||
def _pointsFromList(L):
|
||||
"""
|
||||
given a list of coordinates [x0, y0, x1, y1....]
|
||||
produce a list of points [(x0,y0), (y1,y0),....]
|
||||
"""
|
||||
|
||||
P=[]
|
||||
for i in range(0,len(L), 2):
|
||||
P.append((L[i], L[i+1]))
|
||||
|
||||
return P
|
||||
|
||||
|
||||
def transformNode(doc, newTag, node=None, **attrDict):
|
||||
"""Transform a DOM node into new node and copy selected attributes.
|
||||
|
||||
Creates a new DOM node with tag name 'newTag' for document 'doc'
|
||||
and copies selected attributes from an existing 'node' as provided
|
||||
in 'attrDict'. The source 'node' can be None. Attribute values will
|
||||
be converted to strings.
|
||||
|
||||
E.g.
|
||||
|
||||
n = transformNode(doc, "node1", x="0", y="1")
|
||||
-> DOM node for <node1 x="0" y="1"/>
|
||||
|
||||
n = transformNode(doc, "node1", x=0, y=1+1)
|
||||
-> DOM node for <node1 x="0" y="2"/>
|
||||
|
||||
n = transformNode(doc, "node1", node0, x="x0", y="x0", zoo=bar())
|
||||
-> DOM node for <node1 x="[node0.x0]" y="[node0.y0]" zoo="[bar()]"/>
|
||||
"""
|
||||
|
||||
newNode = doc.createElement(newTag)
|
||||
for newAttr, attr in attrDict.items():
|
||||
sattr = str(attr)
|
||||
if not node:
|
||||
newNode.setAttribute(newAttr, sattr)
|
||||
else:
|
||||
attrVal = node.getAttribute(sattr)
|
||||
newNode.setAttribute(newAttr, attrVal or sattr)
|
||||
|
||||
return newNode
|
||||
|
||||
|
||||
### classes ###
|
||||
|
||||
class SVGCanvas:
|
||||
def __init__(self, size=(300,300)):
|
||||
self.verbose = 0
|
||||
self.width, self.height = self.size = size
|
||||
# self.height = size[1]
|
||||
self.code = []
|
||||
self.style = {}
|
||||
self.path = ''
|
||||
self._strokeColor = self._fillColor = self._lineWidth = \
|
||||
self._font = self._fontSize = self._lineCap = \
|
||||
self._lineJoin = self._color = None
|
||||
|
||||
implementation = getDOMImplementation('minidom')
|
||||
self.doc = implementation.createDocument(None, "svg", None)
|
||||
self.svg = self.doc.documentElement
|
||||
self.svg.setAttribute("width", str(size[0]))
|
||||
self.svg.setAttribute("height", str(self.height))
|
||||
|
||||
title = self.doc.createElement('title')
|
||||
text = self.doc.createTextNode('...')
|
||||
title.appendChild(text)
|
||||
self.svg.appendChild(title)
|
||||
|
||||
desc = self.doc.createElement('desc')
|
||||
text = self.doc.createTextNode('...')
|
||||
desc.appendChild(text)
|
||||
self.svg.appendChild(desc)
|
||||
|
||||
self.setFont(STATE_DEFAULTS['fontName'], STATE_DEFAULTS['fontSize'])
|
||||
self.setStrokeColor(STATE_DEFAULTS['strokeColor'])
|
||||
self.setLineCap(2)
|
||||
self.setLineJoin(0)
|
||||
self.setLineWidth(1)
|
||||
|
||||
# Add a rectangular clipping path identical to view area.
|
||||
clipPath = transformNode(self.doc, "clipPath", id="clip")
|
||||
clipRect = transformNode(self.doc, "rect", x=0, y=0,
|
||||
width=self.width, height=self.height)
|
||||
clipPath.appendChild(clipRect)
|
||||
self.svg.appendChild(clipPath)
|
||||
|
||||
self.groupTree = transformNode(self.doc, "g",
|
||||
id="group",
|
||||
transform="scale(1,-1) translate(0,-%d)" % self.height,
|
||||
style="clip-path: url(#clip)")
|
||||
self.svg.appendChild(self.groupTree)
|
||||
self.currGroup = self.groupTree
|
||||
|
||||
|
||||
def save(self, f=None):
|
||||
if type(f) is StringType:
|
||||
file = open(f, 'w')
|
||||
else:
|
||||
file = f
|
||||
|
||||
file.write("""\
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20000303 Stylable//EN" "http://www.w3.org/TR/2000/03/WD-SVG-20000303/DTD/svg-20000303-stylable.dtd" >\n""")
|
||||
|
||||
# use = self.doc.createElement('use')
|
||||
# use.setAttribute("xlink:href", "#group")
|
||||
# use.setAttribute("transform", "scale(1, -1)")
|
||||
# self.svg.appendChild(use)
|
||||
|
||||
result = self.svg.toprettyxml(indent=" ")
|
||||
file.write(result)
|
||||
|
||||
if file is not f:
|
||||
file.close()
|
||||
|
||||
|
||||
### helpers ###
|
||||
|
||||
def NOTUSED_stringWidth(self, s, font=None, fontSize=None):
|
||||
"""Return the logical width of the string if it were drawn
|
||||
in the current font (defaults to self.font).
|
||||
"""
|
||||
|
||||
font = font or self._font
|
||||
fontSize = fontSize or self._fontSize
|
||||
|
||||
return stringWidth(s, font, fontSize)
|
||||
|
||||
|
||||
def _formatStyle(self, include=''):
|
||||
str = ''
|
||||
include = string.split(include)
|
||||
keys = self.style.keys()
|
||||
if include:
|
||||
#2.1-safe version of the line below follows:
|
||||
#keys = filter(lambda k: k in include, keys)
|
||||
tmp = []
|
||||
for word in keys:
|
||||
if word in include:
|
||||
tmp.append(word)
|
||||
keys = tmp
|
||||
|
||||
items = []
|
||||
for k in keys:
|
||||
items.append((k, self.style[k]))
|
||||
items = map(lambda i: "%s: %s"%(i[0], i[1]), items)
|
||||
str = string.join(items, '; ') + ';'
|
||||
|
||||
return str
|
||||
|
||||
|
||||
def _escape(self, s):
|
||||
"""
|
||||
return a copy of string s with special characters in postscript strings
|
||||
escaped with backslashes.
|
||||
Have not handled characters that are converted normally in python strings
|
||||
i.e. \n -> newline
|
||||
"""
|
||||
|
||||
str = string.replace(s, chr(0x5C), r'\\' )
|
||||
str = string.replace(str, '(', '\(' )
|
||||
str = string.replace(str, ')', '\)')
|
||||
return str
|
||||
|
||||
|
||||
def _genArcCode(self, x1, y1, x2, y2, startAng, extent):
|
||||
"""Calculate the path for an arc inscribed in rectangle defined
|
||||
by (x1,y1),(x2,y2)."""
|
||||
|
||||
return
|
||||
|
||||
#calculate semi-minor and semi-major axes of ellipse
|
||||
xScale = abs((x2-x1)/2.0)
|
||||
yScale = abs((y2-y1)/2.0)
|
||||
#calculate centre of ellipse
|
||||
x, y = (x1+x2)/2.0, (y1+y2)/2.0
|
||||
|
||||
codeline = 'matrix currentmatrix %s %s translate %s %s scale 0 0 1 %s %s %s setmatrix'
|
||||
|
||||
if extent >= 0:
|
||||
arc='arc'
|
||||
else:
|
||||
arc='arcn'
|
||||
data = (x,y, xScale, yScale, startAng, startAng+extent, arc)
|
||||
|
||||
return codeline % data
|
||||
|
||||
|
||||
def _fillAndStroke(self, code, clip=0):
|
||||
path = transformNode(self.doc, "path",
|
||||
d=self.path, style=self._formatStyle(LINE_STYLES))
|
||||
self.currGroup.appendChild(path)
|
||||
self.path = ''
|
||||
|
||||
return
|
||||
|
||||
"""
|
||||
if self._fillColor or self._strokeColor or clip:
|
||||
self.code.extend(code)
|
||||
if self._fillColor:
|
||||
if self._strokeColor or clip:
|
||||
self.code.append("gsave")
|
||||
self.setColor(self._fillColor)
|
||||
self.code.append("eofill")
|
||||
if self._strokeColor or clip:
|
||||
self.code.append("grestore")
|
||||
if self._strokeColor != None:
|
||||
if clip: self.code.append("gsave")
|
||||
self.setColor(self._strokeColor)
|
||||
self.code.append("stroke")
|
||||
if clip: self.code.append("grestore")
|
||||
if clip:
|
||||
self.code.append("clip")
|
||||
self.code.append("newpath")
|
||||
"""
|
||||
|
||||
|
||||
### styles ###
|
||||
|
||||
def setLineCap(self, v):
|
||||
vals = {0:'butt', 1:'round', 2:'square'}
|
||||
if self._lineCap != v:
|
||||
self._lineCap = v
|
||||
self.style['stroke-linecap'] = vals[v]
|
||||
|
||||
|
||||
def setLineJoin(self, v):
|
||||
vals = {0:'miter', 1:'round', 2:'bevel'}
|
||||
if self._lineJoin != v:
|
||||
self._lineJoin = v
|
||||
self.style['stroke-linecap'] = vals[v]
|
||||
|
||||
|
||||
def setDash(self, array=[], phase=0):
|
||||
"""Two notations. Pass two numbers, or an array and phase."""
|
||||
|
||||
join = string.join
|
||||
if type(array) in (types.IntType, types.FloatType):
|
||||
self.style['stroke-dasharray'] = join(map(str, ([array, phase])), ', ')
|
||||
elif type(array) in (types.ListType, types.TupleType) and len(array) > 0:
|
||||
assert phase >= 0, "phase is a length in user space"
|
||||
self.style['stroke-dasharray'] = join(map(str, (array+[phase])), ', ')
|
||||
|
||||
|
||||
def setStrokeColor(self, color):
|
||||
self._strokeColor = color
|
||||
self.setColor(color)
|
||||
if color == None:
|
||||
self.style['stroke'] = 'none'
|
||||
else:
|
||||
r, g, b = color.red, color.green, color.blue
|
||||
self.style['stroke'] = 'rgb(%d%%,%d%%,%d%%)' % (r*100, g*100, b*100)
|
||||
|
||||
|
||||
def setColor(self, color):
|
||||
if self._color != color:
|
||||
self._color = color
|
||||
|
||||
|
||||
def setFillColor(self, color):
|
||||
self._fillColor = color
|
||||
self.setColor(color)
|
||||
if color == None:
|
||||
self.style['fill'] = 'none'
|
||||
else:
|
||||
r, g, b = color.red, color.green, color.blue
|
||||
self.style['fill'] = 'rgb(%d%%,%d%%,%d%%)' % (r*100, g*100, b*100)
|
||||
|
||||
|
||||
def setLineWidth(self, width):
|
||||
if width != self._lineWidth:
|
||||
self._lineWidth = width
|
||||
self.style['stroke-width'] = width
|
||||
|
||||
|
||||
def setFont(self, font, fontSize):
|
||||
if self._font != font or self._fontSize != fontSize:
|
||||
self._font, self._fontSize = (font, fontSize)
|
||||
self.style['font-family'] = font
|
||||
self.style['font-size'] = fontSize
|
||||
|
||||
|
||||
### shapes ###
|
||||
|
||||
def rect(self, x1,y1, x2,y2, rx=8, ry=8):
|
||||
"Draw a rectangle between x1,y1 and x2,y2."
|
||||
|
||||
if self.verbose: print "+++ SVGCanvas.rect"
|
||||
|
||||
rect = transformNode(self.doc, "rect",
|
||||
x=x1, y=y1, width=x2-x1, height=y2-y1,
|
||||
style=self._formatStyle(LINE_STYLES))
|
||||
|
||||
self.currGroup.appendChild(rect)
|
||||
|
||||
|
||||
def roundRect(self, x1,y1, x2,y2, rx=8, ry=8):
|
||||
"""Draw a rounded rectangle between x1,y1 and x2,y2.
|
||||
|
||||
Corners inset as ellipses with x-radius rx and y-radius ry.
|
||||
These should have x1<x2, y1<y2, rx>0, and ry>0.
|
||||
"""
|
||||
|
||||
rect = transformNode(self.doc, "rect",
|
||||
x=x1, y=y1, width=x2-x1, height=y2-y1, rx=rx, ry=ry,
|
||||
style=self._formatStyle(LINE_STYLES))
|
||||
|
||||
self.currGroup.appendChild(rect)
|
||||
|
||||
|
||||
def drawString(self, s, x, y, angle=0):
|
||||
if self.verbose: print "+++ SVGCanvas.drawString"
|
||||
|
||||
if self._fillColor != None:
|
||||
self.setColor(self._fillColor)
|
||||
s = self._escape(s)
|
||||
st = self._formatStyle(TEXT_STYLES)
|
||||
if angle != 0:
|
||||
st = st + " rotate(%f %f %f);" % (angle, x, y)
|
||||
st = st + " fill: %s;" % self.style['fill']
|
||||
text = transformNode(self.doc, "text",
|
||||
x=x, y=y, style=st,
|
||||
transform="translate(0,%d) scale(1,-1)" % (2*y))
|
||||
content = self.doc.createTextNode(s)
|
||||
text.appendChild(content)
|
||||
|
||||
self.currGroup.appendChild(text)
|
||||
|
||||
|
||||
def comment(self, data):
|
||||
"Add a comment."
|
||||
|
||||
comment = self.doc.createComment(data)
|
||||
# self.currGroup.appendChild(comment)
|
||||
|
||||
|
||||
def drawImage(self, image, x1, y1, x2=None, y2=None):
|
||||
pass
|
||||
|
||||
|
||||
def line(self, x1, y1, x2, y2):
|
||||
if self._strokeColor != None:
|
||||
if 0: # something is wrong with line in my SVG viewer...
|
||||
line = transformNode(self.doc, "line",
|
||||
x=x1, y=y1, x2=x2, y2=y2,
|
||||
style=self._formatStyle(LINE_STYLES))
|
||||
self.currGroup.appendChild(line)
|
||||
path = transformNode(self.doc, "path",
|
||||
d="M %f,%f L %f,%f Z" % (x1,y1,x2,y2),
|
||||
style=self._formatStyle(LINE_STYLES))
|
||||
self.currGroup.appendChild(path)
|
||||
|
||||
|
||||
def ellipse(self, x1, y1, x2, y2):
|
||||
"""Draw an orthogonal ellipse inscribed within the rectangle x1,y1,x2,y2.
|
||||
|
||||
These should have x1<x2 and y1<y2.
|
||||
"""
|
||||
|
||||
ellipse = transformNode(self.doc, "ellipse",
|
||||
cx=(x1+x2)/2.0, cy=(y1+y2)/2.0, rx=(x2-x1)/2.0, ry=(y2-y1)/2.0,
|
||||
style=self._formatStyle(LINE_STYLES))
|
||||
|
||||
self.currGroup.appendChild(ellipse)
|
||||
|
||||
|
||||
def circle(self, xc, yc, r):
|
||||
circle = transformNode(self.doc, "circle",
|
||||
cx=xc, cy=yc, r=r,
|
||||
style=self._formatStyle(LINE_STYLES))
|
||||
|
||||
self.currGroup.appendChild(circle)
|
||||
|
||||
|
||||
def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4, closed=0):
|
||||
pass
|
||||
return
|
||||
|
||||
codeline = '%s m %s curveto'
|
||||
data = (fp_str(x1, y1), fp_str(x2, y2, x3, y3, x4, y4))
|
||||
if self._fillColor != None:
|
||||
self.setColor(self._fillColor)
|
||||
self.code.append((codeline % data) + ' eofill')
|
||||
if self._strokeColor != None:
|
||||
self.setColor(self._strokeColor)
|
||||
self.code.append((codeline % data)
|
||||
+ ((closed and ' closepath') or '')
|
||||
+ ' stroke')
|
||||
|
||||
|
||||
def drawArc(self, x1,y1, x2,y2, startAng=0, extent=360, fromcenter=0):
|
||||
"""Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2.
|
||||
|
||||
Starting at startAng degrees and covering extent degrees. Angles
|
||||
start with 0 to the right (+x) and increase counter-clockwise.
|
||||
These should have x1<x2 and y1<y2.
|
||||
"""
|
||||
|
||||
cx, cy = (x1+x2)/2.0, (y1+y2)/2.0
|
||||
rx, ry = (x2-x1)/2.0, (y2-y1)/2.0
|
||||
mx = rx * cos(startAng*pi/180) + cx
|
||||
my = ry * sin(startAng*pi/180) + cy
|
||||
ax = rx * cos((startAng+extent)*pi/180) + cx
|
||||
ay = ry * sin((startAng+extent)*pi/180) + cy
|
||||
|
||||
str = ''
|
||||
if fromcenter:
|
||||
str = str + "M %f, %f L %f, %f " % (cx, cy, ax, ay)
|
||||
|
||||
if fromcenter:
|
||||
str = str + "A %f, %f %d %d %d %f, %f " % \
|
||||
(rx, ry, 0, extent>=180, 0, mx, my)
|
||||
else:
|
||||
str = str + "M %f, %f A %f, %f %d %d %d %f, %f Z " % \
|
||||
(mx, my, rx, ry, 0, extent>=180, 0, mx, my)
|
||||
|
||||
if fromcenter:
|
||||
str = str + "L %f, %f Z " % (cx, cy)
|
||||
|
||||
path = transformNode(self.doc, "path",
|
||||
d=str, style=self._formatStyle())
|
||||
self.currGroup.appendChild(path)
|
||||
|
||||
|
||||
def polygon(self, points, closed=0):
|
||||
assert len(points) >= 2, 'Polygon must have 2 or more points'
|
||||
|
||||
if self._strokeColor != None:
|
||||
self.setColor(self._strokeColor)
|
||||
pairs = []
|
||||
for i in xrange(len(points)):
|
||||
pairs.append("%f %f" % (points[i]))
|
||||
pts = string.join(pairs, ', ')
|
||||
polyline = transformNode(self.doc, "polygon",
|
||||
points=pts, style=self._formatStyle(LINE_STYLES))
|
||||
self.currGroup.appendChild(polyline)
|
||||
|
||||
# self._fillAndStroke(polyCode)
|
||||
|
||||
|
||||
def lines(self, lineList, color=None, width=None):
|
||||
# print "### lineList", lineList
|
||||
return
|
||||
|
||||
if self._strokeColor != None:
|
||||
self._setColor(self._strokeColor)
|
||||
codeline = '%s m %s l stroke'
|
||||
for line in lineList:
|
||||
self.code.append(codeline % (fp_str(line[0]), fp_str(line[1])))
|
||||
|
||||
|
||||
def polyLine(self, points):
|
||||
assert len(points) >= 1, 'Polyline must have 1 or more points'
|
||||
|
||||
if self._strokeColor != None:
|
||||
self.setColor(self._strokeColor)
|
||||
pairs = []
|
||||
for i in xrange(len(points)):
|
||||
pairs.append("%f %f" % (points[i]))
|
||||
pts = string.join(pairs, ', ')
|
||||
polyline = transformNode(self.doc, "polyline",
|
||||
points=pts, style=self._formatStyle(LINE_STYLES))
|
||||
self.currGroup.appendChild(polyline)
|
||||
|
||||
|
||||
### groups ###
|
||||
|
||||
def startGroup(self):
|
||||
if self.verbose: print "+++ begin SVGCanvas.startGroup"
|
||||
currGroup, group = self.currGroup, transformNode(self.doc, "g", transform="")
|
||||
currGroup.appendChild(group)
|
||||
self.currGroup = group
|
||||
if self.verbose: print "+++ end SVGCanvas.startGroup"
|
||||
return currGroup
|
||||
|
||||
def endGroup(self,currGroup):
|
||||
if self.verbose: print "+++ begin SVGCanvas.endGroup"
|
||||
self.currGroup = currGroup
|
||||
if self.verbose: print "+++ end SVGCanvas.endGroup"
|
||||
|
||||
|
||||
def transform(self, a, b, c, d, e, f):
|
||||
if self.verbose: print "!!! begin SVGCanvas.transform", a, b, c, d, e, f
|
||||
tr = self.currGroup.getAttribute("transform")
|
||||
t = 'matrix(%f, %f, %f, %f, %f, %f)' % (a,b,c,d,e,f)
|
||||
if (a, b, c, d, e, f) != (1, 0, 0, 1, 0, 0):
|
||||
self.currGroup.setAttribute("transform", "%s %s" % (tr, t))
|
||||
|
||||
|
||||
def translate(self, x, y):
|
||||
# probably never used
|
||||
print "!!! begin SVGCanvas.translate"
|
||||
return
|
||||
|
||||
tr = self.currGroup.getAttribute("transform")
|
||||
t = 'translate(%f, %f)' % (x, y)
|
||||
self.currGroup.setAttribute("transform", "%s %s" % (tr, t))
|
||||
|
||||
|
||||
def scale(self, x, y):
|
||||
# probably never used
|
||||
print "!!! begin SVGCanvas.scale"
|
||||
return
|
||||
|
||||
tr = self.groups[-1].getAttribute("transform")
|
||||
t = 'scale(%f, %f)' % (x, y)
|
||||
self.currGroup.setAttribute("transform", "%s %s" % (tr, t))
|
||||
|
||||
|
||||
### paths ###
|
||||
|
||||
def moveTo(self, x, y):
|
||||
self.path = self.path + 'M %f %f ' % (x, y)
|
||||
|
||||
|
||||
def lineTo(self, x, y):
|
||||
self.path = self.path + 'L %f %f ' % (x, y)
|
||||
|
||||
|
||||
def curveTo(self, x1, y1, x2, y2, x3, y3):
|
||||
self.path = self.path + 'C %f %f %f %f %f %f ' % (x1, y1, x2, y2, x3, y3)
|
||||
|
||||
|
||||
def closePath(self):
|
||||
self.path = self.path + 'Z '
|
||||
|
||||
def saveState(self):
|
||||
pass
|
||||
|
||||
def restoreState(self):
|
||||
pass
|
||||
|
||||
class _SVGRenderer(Renderer):
|
||||
"""This draws onto an SVG document.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._tracker = StateTracker()
|
||||
self.verbose = 0
|
||||
|
||||
def drawNode(self, node):
|
||||
"""This is the recursive method called for each node in the tree.
|
||||
"""
|
||||
|
||||
if self.verbose: print "### begin _SVGRenderer.drawNode"
|
||||
|
||||
self._canvas.comment('begin node %s'%`node`)
|
||||
color = self._canvas._color
|
||||
if not (isinstance(node, Path) and node.isClipPath):
|
||||
pass # self._canvas.saveState()
|
||||
|
||||
#apply state changes
|
||||
deltas = getStateDelta(node)
|
||||
self._tracker.push(deltas)
|
||||
self.applyStateChanges(deltas, {})
|
||||
|
||||
#draw the object, or recurse
|
||||
self.drawNodeDispatcher(node)
|
||||
|
||||
rDeltas = self._tracker.pop()
|
||||
if not (isinstance(node, Path) and node.isClipPath):
|
||||
pass # self._canvas.restoreState()
|
||||
self._canvas.comment('end node %s'%`node`)
|
||||
self._canvas._color = color
|
||||
|
||||
#restore things we might have lost (without actually doing anything).
|
||||
for k, v in rDeltas.items():
|
||||
if self._restores.has_key(k):
|
||||
setattr(self._canvas,self._restores[k],v)
|
||||
|
||||
if self.verbose: print "### end _SVGRenderer.drawNode"
|
||||
|
||||
_restores = {'strokeColor':'_strokeColor','strokeWidth': '_lineWidth','strokeLineCap':'_lineCap',
|
||||
'strokeLineJoin':'_lineJoin','fillColor':'_fillColor','fontName':'_font',
|
||||
'fontSize':'_fontSize'}
|
||||
|
||||
|
||||
def drawGroup(self, group):
|
||||
if self.verbose: print "### begin _SVGRenderer.drawGroup"
|
||||
|
||||
currGroup = self._canvas.startGroup()
|
||||
a, b, c, d, e, f = self._tracker.getCTM()
|
||||
for childNode in group.getContents():
|
||||
if isinstance(childNode, UserNode):
|
||||
node2 = childNode.provideNode()
|
||||
else:
|
||||
node2 = childNode
|
||||
self.drawNode(node2)
|
||||
self._canvas.transform(a, b, c, d, e, f)
|
||||
self._canvas.endGroup(currGroup)
|
||||
|
||||
if self.verbose: print "### end _SVGRenderer.drawGroup"
|
||||
|
||||
|
||||
def drawRect(self, rect):
|
||||
if rect.rx == rect.ry == 0:
|
||||
#plain old rectangle
|
||||
self._canvas.rect(
|
||||
rect.x, rect.y,
|
||||
rect.x+rect.width, rect.y+rect.height)
|
||||
else:
|
||||
#cheat and assume ry = rx; better to generalize
|
||||
#pdfgen roundRect function. TODO
|
||||
self._canvas.roundRect(
|
||||
rect.x, rect.y,
|
||||
rect.x+rect.width, rect.y+rect.height,
|
||||
rect.rx, rect.ry
|
||||
)
|
||||
|
||||
|
||||
def drawString(self, stringObj):
|
||||
if self._canvas._fillColor:
|
||||
S = self._tracker.getState()
|
||||
text_anchor, x, y, text = S['textAnchor'], stringObj.x, stringObj.y, stringObj.text
|
||||
if not text_anchor in ['start', 'inherited']:
|
||||
font, fontSize = S['fontName'], S['fontSize']
|
||||
textLen = stringWidth(text, font,fontSize)
|
||||
if text_anchor=='end':
|
||||
x = x-textLen
|
||||
elif text_anchor=='middle':
|
||||
x = x - textLen/2
|
||||
else:
|
||||
raise ValueError, 'bad value for text_anchor ' + str(text_anchor)
|
||||
self._canvas.drawString(text,x,y)
|
||||
|
||||
|
||||
def drawLine(self, line):
|
||||
if self._canvas._strokeColor:
|
||||
self._canvas.line(line.x1, line.y1, line.x2, line.y2)
|
||||
|
||||
|
||||
def drawCircle(self, circle):
|
||||
self._canvas.circle( circle.cx, circle.cy, circle.r)
|
||||
|
||||
|
||||
def drawWedge(self, wedge):
|
||||
centerx, centery, radius, startangledegrees, endangledegrees = \
|
||||
wedge.centerx, wedge.centery, wedge.radius, wedge.startangledegrees, wedge.endangledegrees
|
||||
yradius = wedge.yradius or wedge.radius
|
||||
(x1, y1) = (centerx-radius, centery-yradius)
|
||||
(x2, y2) = (centerx+radius, centery+yradius)
|
||||
extent = endangledegrees - startangledegrees
|
||||
self._canvas.drawArc(x1, y1, x2, y2, startangledegrees, extent, fromcenter=1)
|
||||
|
||||
|
||||
def drawPolyLine(self, p):
|
||||
if self._canvas._strokeColor:
|
||||
self._canvas.polyLine(_pointsFromList(p.points))
|
||||
|
||||
|
||||
def drawEllipse(self, ellipse):
|
||||
#need to convert to pdfgen's bounding box representation
|
||||
x1 = ellipse.cx - ellipse.rx
|
||||
x2 = ellipse.cx + ellipse.rx
|
||||
y1 = ellipse.cy - ellipse.ry
|
||||
y2 = ellipse.cy + ellipse.ry
|
||||
self._canvas.ellipse(x1,y1,x2,y2)
|
||||
|
||||
|
||||
def drawPolygon(self, p):
|
||||
self._canvas.polygon(_pointsFromList(p.points), closed=1)
|
||||
|
||||
|
||||
def drawPath(self, path):
|
||||
# print "### drawPath", path.points
|
||||
from reportlab.graphics.shapes import _renderPath
|
||||
c = self._canvas
|
||||
drawFuncs = (c.moveTo, c.lineTo, c.curveTo, c.closePath)
|
||||
isClosed = _renderPath(path, drawFuncs)
|
||||
if not isClosed:
|
||||
c._fillColor = None
|
||||
c._fillAndStroke([], clip=path.isClipPath)
|
||||
|
||||
|
||||
def applyStateChanges(self, delta, newState):
|
||||
"""This takes a set of states, and outputs the operators
|
||||
needed to set those properties"""
|
||||
|
||||
for key, value in delta.items():
|
||||
if key == 'transform':
|
||||
pass
|
||||
#self._canvas.transform(value[0], value[1], value[2], value[3], value[4], value[5])
|
||||
elif key == 'strokeColor':
|
||||
self._canvas.setStrokeColor(value)
|
||||
elif key == 'strokeWidth':
|
||||
self._canvas.setLineWidth(value)
|
||||
elif key == 'strokeLineCap': #0,1,2
|
||||
self._canvas.setLineCap(value)
|
||||
elif key == 'strokeLineJoin':
|
||||
self._canvas.setLineJoin(value)
|
||||
elif key == 'strokeDashArray':
|
||||
if value:
|
||||
self._canvas.setDash(value)
|
||||
else:
|
||||
self._canvas.setDash()
|
||||
elif key == 'fillColor':
|
||||
self._canvas.setFillColor(value)
|
||||
elif key in ['fontSize', 'fontName']:
|
||||
fontname = delta.get('fontName', self._canvas._font)
|
||||
fontsize = delta.get('fontSize', self._canvas._fontSize)
|
||||
self._canvas.setFont(fontname, fontsize)
|
||||
|
||||
|
||||
|
||||
|
||||
def test0(outdir='svgout'):
|
||||
# print all drawings and their doc strings from the test
|
||||
# file
|
||||
if not os.path.isdir(outdir):
|
||||
os.mkdir(outdir)
|
||||
#grab all drawings from the test module
|
||||
from reportlab.graphics import testshapes
|
||||
drawings = []
|
||||
|
||||
for funcname in dir(testshapes):
|
||||
#if funcname[0:11] == 'getDrawing2':
|
||||
# print 'hacked to only show drawing 2'
|
||||
if funcname[0:10] == 'getDrawing':
|
||||
drawing = eval('testshapes.' + funcname + '()')
|
||||
docstring = eval('testshapes.' + funcname + '.__doc__')
|
||||
drawings.append((drawing, docstring))
|
||||
|
||||
# return
|
||||
|
||||
i = 0
|
||||
for (d, docstring) in drawings:
|
||||
filename = outdir + os.sep + 'renderSVG_%d.svg' % i
|
||||
drawToFile(d, filename)
|
||||
# print 'saved', filename
|
||||
i = i + 1
|
||||
|
||||
|
||||
def test1():
|
||||
from reportlab.graphics.testshapes import getDrawing01
|
||||
d = getDrawing01()
|
||||
drawToFile(d, "svgout/test.svg")
|
||||
|
||||
|
||||
def test2():
|
||||
from reportlab.lib.corp import RL_CorpLogo
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
|
||||
rl = RL_CorpLogo()
|
||||
d = Drawing(rl.width,rl.height)
|
||||
d.add(rl)
|
||||
drawToFile(d, "svgout/corplogo.svg")
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
test0()
|
||||
test1()
|
||||
test2()
|
|
@ -1,315 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/renderbase.py
|
||||
"""
|
||||
Superclass for renderers to factor out common functionality and default implementations.
|
||||
"""
|
||||
|
||||
|
||||
__version__=''' $Id $ '''
|
||||
|
||||
from reportlab.graphics.shapes import *
|
||||
from reportlab import rl_config
|
||||
|
||||
def inverse(A):
|
||||
"For A affine 2D represented as 6vec return 6vec version of A**(-1)"
|
||||
# I checked this RGB
|
||||
det = float(A[0]*A[3] - A[2]*A[1])
|
||||
R = [A[3]/det, -A[1]/det, -A[2]/det, A[0]/det]
|
||||
return tuple(R+[-R[0]*A[4]-R[2]*A[5],-R[1]*A[4]-R[3]*A[5]])
|
||||
|
||||
def mmult(A, B):
|
||||
"A postmultiplied by B"
|
||||
# I checked this RGB
|
||||
# [a0 a2 a4] [b0 b2 b4]
|
||||
# [a1 a3 a5] * [b1 b3 b5]
|
||||
# [ 1 ] [ 1 ]
|
||||
#
|
||||
return (A[0]*B[0] + A[2]*B[1],
|
||||
A[1]*B[0] + A[3]*B[1],
|
||||
A[0]*B[2] + A[2]*B[3],
|
||||
A[1]*B[2] + A[3]*B[3],
|
||||
A[0]*B[4] + A[2]*B[5] + A[4],
|
||||
A[1]*B[4] + A[3]*B[5] + A[5])
|
||||
|
||||
|
||||
def getStateDelta(shape):
|
||||
"""Used to compute when we need to change the graphics state.
|
||||
For example, if we have two adjacent red shapes we don't need
|
||||
to set the pen color to red in between. Returns the effect
|
||||
the given shape would have on the graphics state"""
|
||||
delta = {}
|
||||
for (prop, value) in shape.getProperties().items():
|
||||
if STATE_DEFAULTS.has_key(prop):
|
||||
delta[prop] = value
|
||||
return delta
|
||||
|
||||
|
||||
class StateTracker:
|
||||
"""Keeps a stack of transforms and state
|
||||
properties. It can contain any properties you
|
||||
want, but the keys 'transform' and 'ctm' have
|
||||
special meanings. The getCTM()
|
||||
method returns the current transformation
|
||||
matrix at any point, without needing to
|
||||
invert matrixes when you pop."""
|
||||
def __init__(self, defaults=None):
|
||||
# one stack to keep track of what changes...
|
||||
self.__deltas = []
|
||||
|
||||
# and another to keep track of cumulative effects. Last one in
|
||||
# list is the current graphics state. We put one in to simplify
|
||||
# loops below.
|
||||
self.__combined = []
|
||||
if defaults is None:
|
||||
defaults = STATE_DEFAULTS.copy()
|
||||
#ensure that if we have a transform, we have a CTM
|
||||
if defaults.has_key('transform'):
|
||||
defaults['ctm'] = defaults['transform']
|
||||
self.__combined.append(defaults)
|
||||
|
||||
def push(self,delta):
|
||||
"""Take a new state dictionary of changes and push it onto
|
||||
the stack. After doing this, the combined state is accessible
|
||||
through getState()"""
|
||||
|
||||
newstate = self.__combined[-1].copy()
|
||||
for (key, value) in delta.items():
|
||||
if key == 'transform': #do cumulative matrix
|
||||
newstate['transform'] = delta['transform']
|
||||
newstate['ctm'] = mmult(self.__combined[-1]['ctm'], delta['transform'])
|
||||
#print 'statetracker transform = (%0.2f, %0.2f, %0.2f, %0.2f, %0.2f, %0.2f)' % tuple(newstate['transform'])
|
||||
#print 'statetracker ctm = (%0.2f, %0.2f, %0.2f, %0.2f, %0.2f, %0.2f)' % tuple(newstate['ctm'])
|
||||
|
||||
else: #just overwrite it
|
||||
newstate[key] = value
|
||||
|
||||
self.__combined.append(newstate)
|
||||
self.__deltas.append(delta)
|
||||
|
||||
def pop(self):
|
||||
"""steps back one, and returns a state dictionary with the
|
||||
deltas to reverse out of wherever you are. Depending
|
||||
on your back end, you may not need the return value,
|
||||
since you can get the complete state afterwards with getState()"""
|
||||
del self.__combined[-1]
|
||||
newState = self.__combined[-1]
|
||||
lastDelta = self.__deltas[-1]
|
||||
del self.__deltas[-1]
|
||||
#need to diff this against the last one in the state
|
||||
reverseDelta = {}
|
||||
#print 'pop()...'
|
||||
for key, curValue in lastDelta.items():
|
||||
#print ' key=%s, value=%s' % (key, curValue)
|
||||
prevValue = newState[key]
|
||||
if prevValue <> curValue:
|
||||
#print ' state popping "%s"="%s"' % (key, curValue)
|
||||
if key == 'transform':
|
||||
reverseDelta[key] = inverse(lastDelta['transform'])
|
||||
else: #just return to previous state
|
||||
reverseDelta[key] = prevValue
|
||||
return reverseDelta
|
||||
|
||||
def getState(self):
|
||||
"returns the complete graphics state at this point"
|
||||
return self.__combined[-1]
|
||||
|
||||
def getCTM(self):
|
||||
"returns the current transformation matrix at this point"""
|
||||
return self.__combined[-1]['ctm']
|
||||
|
||||
def __getitem__(self,key):
|
||||
"returns the complete graphics state value of key at this point"
|
||||
return self.__combined[-1][key]
|
||||
|
||||
def __setitem__(self,key,value):
|
||||
"sets the complete graphics state value of key to value"
|
||||
self.__combined[-1][key] = value
|
||||
|
||||
def testStateTracker():
|
||||
print 'Testing state tracker'
|
||||
defaults = {'fillColor':None, 'strokeColor':None,'fontName':None, 'transform':[1,0,0,1,0,0]}
|
||||
deltas = [
|
||||
{'fillColor':'red'},
|
||||
{'fillColor':'green', 'strokeColor':'blue','fontName':'Times-Roman'},
|
||||
{'transform':[0.5,0,0,0.5,0,0]},
|
||||
{'transform':[0.5,0,0,0.5,2,3]},
|
||||
{'strokeColor':'red'}
|
||||
]
|
||||
|
||||
st = StateTracker(defaults)
|
||||
print 'initial:', st.getState()
|
||||
print
|
||||
for delta in deltas:
|
||||
print 'pushing:', delta
|
||||
st.push(delta)
|
||||
print 'state: ',st.getState(),'\n'
|
||||
|
||||
for delta in deltas:
|
||||
print 'popping:',st.pop()
|
||||
print 'state: ',st.getState(),'\n'
|
||||
|
||||
|
||||
def _expandUserNode(node,canvas):
|
||||
if isinstance(node, UserNode):
|
||||
try:
|
||||
if hasattr(node,'_canvas'):
|
||||
ocanvas = 1
|
||||
else:
|
||||
node._canvas = canvas
|
||||
ocanvas = None
|
||||
onode = node
|
||||
node = node.provideNode()
|
||||
finally:
|
||||
if not ocanvas: del onode._canvas
|
||||
return node
|
||||
|
||||
class Renderer:
|
||||
"""Virtual superclass for graphics renderers."""
|
||||
|
||||
def __init__(self):
|
||||
self._tracker = StateTracker()
|
||||
|
||||
def undefined(self, operation):
|
||||
raise ValueError, "%s operation not defined at superclass class=%s" %(operation, self.__class__)
|
||||
|
||||
def draw(self, drawing, canvas, x=0, y=0, showBoundary=rl_config._unset_):
|
||||
"""This is the top level function, which draws the drawing at the given
|
||||
location. The recursive part is handled by drawNode."""
|
||||
#stash references for ease of communication
|
||||
if showBoundary is rl_config._unset_: showBoundary=rl_config.showBoundary
|
||||
self._canvas = canvas
|
||||
canvas.__dict__['_drawing'] = self._drawing = drawing
|
||||
drawing._parent = None
|
||||
try:
|
||||
#bounding box
|
||||
if showBoundary: canvas.rect(x, y, drawing.width, drawing.height)
|
||||
canvas.saveState()
|
||||
self.initState(x,y)
|
||||
self.drawNode(drawing)
|
||||
self.pop()
|
||||
canvas.restoreState()
|
||||
finally:
|
||||
#remove any circular references
|
||||
del self._canvas, self._drawing, canvas._drawing, drawing._parent
|
||||
|
||||
def initState(self,x,y):
|
||||
deltas = STATE_DEFAULTS.copy()
|
||||
deltas['transform'] = [1,0,0,1,x,y]
|
||||
self._tracker.push(deltas)
|
||||
self.applyStateChanges(deltas, {})
|
||||
|
||||
def pop(self):
|
||||
self._tracker.pop()
|
||||
|
||||
def drawNode(self, node):
|
||||
"""This is the recursive method called for each node
|
||||
in the tree"""
|
||||
# Undefined here, but with closer analysis probably can be handled in superclass
|
||||
self.undefined("drawNode")
|
||||
|
||||
def drawNodeDispatcher(self, node):
|
||||
"""dispatch on the node's (super) class: shared code"""
|
||||
|
||||
canvas = getattr(self,'_canvas',None)
|
||||
# replace UserNode with its contents
|
||||
|
||||
try:
|
||||
node = _expandUserNode(node,canvas)
|
||||
if hasattr(node,'_canvas'):
|
||||
ocanvas = 1
|
||||
else:
|
||||
node._canvas = canvas
|
||||
ocanvas = None
|
||||
|
||||
#draw the object, or recurse
|
||||
if isinstance(node, Line):
|
||||
self.drawLine(node)
|
||||
elif isinstance(node, Image):
|
||||
self.drawImage(node)
|
||||
elif isinstance(node, Rect):
|
||||
self.drawRect(node)
|
||||
elif isinstance(node, Circle):
|
||||
self.drawCircle(node)
|
||||
elif isinstance(node, Ellipse):
|
||||
self.drawEllipse(node)
|
||||
elif isinstance(node, PolyLine):
|
||||
self.drawPolyLine(node)
|
||||
elif isinstance(node, Polygon):
|
||||
self.drawPolygon(node)
|
||||
elif isinstance(node, Path):
|
||||
self.drawPath(node)
|
||||
elif isinstance(node, String):
|
||||
self.drawString(node)
|
||||
elif isinstance(node, Group):
|
||||
self.drawGroup(node)
|
||||
elif isinstance(node, Wedge):
|
||||
self.drawWedge(node)
|
||||
else:
|
||||
print 'DrawingError','Unexpected element %s in drawing!' % str(node)
|
||||
finally:
|
||||
if not ocanvas: del node._canvas
|
||||
|
||||
_restores = {'stroke':'_stroke','stroke_width': '_lineWidth','stroke_linecap':'_lineCap',
|
||||
'stroke_linejoin':'_lineJoin','fill':'_fill','font_family':'_font',
|
||||
'font_size':'_fontSize'}
|
||||
|
||||
def drawGroup(self, group):
|
||||
# just do the contents. Some renderers might need to override this
|
||||
# if they need a flipped transform
|
||||
canvas = getattr(self,'_canvas',None)
|
||||
for node in group.getContents():
|
||||
node = _expandUserNode(node,canvas)
|
||||
try:
|
||||
if hasattr(node,'_canvas'):
|
||||
ocanvas = 1
|
||||
else:
|
||||
node._canvas = canvas
|
||||
ocanvas = None
|
||||
node._parent = group
|
||||
self.drawNode(node)
|
||||
finally:
|
||||
del node._parent
|
||||
if not ocanvas: del node._canvas
|
||||
|
||||
def drawWedge(self, wedge):
|
||||
# by default ask the wedge to make a polygon of itself and draw that!
|
||||
#print "drawWedge"
|
||||
polygon = wedge.asPolygon()
|
||||
self.drawPolygon(polygon)
|
||||
|
||||
def drawPath(self, path):
|
||||
polygons = path.asPolygons()
|
||||
for polygon in polygons:
|
||||
self.drawPolygon(polygon)
|
||||
|
||||
def drawRect(self, rect):
|
||||
# could be implemented in terms of polygon
|
||||
self.undefined("drawRect")
|
||||
|
||||
def drawLine(self, line):
|
||||
self.undefined("drawLine")
|
||||
|
||||
def drawCircle(self, circle):
|
||||
self.undefined("drawCircle")
|
||||
|
||||
def drawPolyLine(self, p):
|
||||
self.undefined("drawPolyLine")
|
||||
|
||||
def drawEllipse(self, ellipse):
|
||||
self.undefined("drawEllipse")
|
||||
|
||||
def drawPolygon(self, p):
|
||||
self.undefined("drawPolygon")
|
||||
|
||||
def drawString(self, stringObj):
|
||||
self.undefined("drawString")
|
||||
|
||||
def applyStateChanges(self, delta, newState):
|
||||
"""This takes a set of states, and outputs the operators
|
||||
needed to set those properties"""
|
||||
self.undefined("applyStateChanges")
|
||||
|
||||
if __name__=='__main__':
|
||||
print "this file has no script interpretation"
|
||||
print __doc__
|
|
@ -1,73 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from reportlab.graphics.charts.lineplots import ScatterPlot
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from excelcolors import *
|
||||
|
||||
class Bubble(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,ScatterPlot(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 115
|
||||
self.chart.height = 80
|
||||
self.chart.x = 30
|
||||
self.chart.y = 40
|
||||
self.chart.lines[0].strokeColor = color01
|
||||
self.chart.lines[1].strokeColor = color02
|
||||
self.chart.lines[2].strokeColor = color03
|
||||
self.chart.lines[3].strokeColor = color04
|
||||
self.chart.lines[4].strokeColor = color05
|
||||
self.chart.lines[5].strokeColor = color06
|
||||
self.chart.lines[6].strokeColor = color07
|
||||
self.chart.lines[7].strokeColor = color08
|
||||
self.chart.lines[8].strokeColor = color09
|
||||
self.chart.lines[9].strokeColor = color10
|
||||
self.chart.lines.symbol.kind ='Circle'
|
||||
self.chart.lines.symbol.size = 15
|
||||
self.chart.fillColor = backgroundGrey
|
||||
self.chart.lineLabels.fontName = 'Helvetica'
|
||||
self.chart.xValueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.xValueAxis.labels.fontSize = 7
|
||||
self.chart.xValueAxis.forceZero = 0
|
||||
self.chart.data = [((100,100), (200,200), (250,210), (300,300), (350,450))]
|
||||
self.chart.xValueAxis.avoidBoundFrac = 1
|
||||
self.chart.xValueAxis.gridEnd = 115
|
||||
self.chart.xValueAxis.tickDown = 3
|
||||
self.chart.xValueAxis.visibleGrid = 1
|
||||
self.chart.yValueAxis.tickLeft = 3
|
||||
self.chart.yValueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.yValueAxis.labels.fontSize = 7
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'Widgets')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 153
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self.chart.lineLabelFormat = None
|
||||
self.chart.xLabel = 'X Axis'
|
||||
self.chart.y = 30
|
||||
self.chart.yLabel = 'Y Axis'
|
||||
self.chart.yValueAxis.labelTextFormat = '%d'
|
||||
self.chart.yValueAxis.forceZero = 1
|
||||
self.chart.xValueAxis.forceZero = 1
|
||||
|
||||
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
Bubble().save(formats=['pdf'],outDir=None,fnRoot='bubble')
|
|
@ -1,84 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from excelcolors import *
|
||||
from reportlab.graphics.charts.barcharts import HorizontalBarChart
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
|
||||
class ClusteredBar(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,HorizontalBarChart(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 115
|
||||
self.chart.height = 80
|
||||
self.chart.x = 30
|
||||
self.chart.y = 40
|
||||
self.chart.bars[0].fillColor = color01
|
||||
self.chart.bars[1].fillColor = color02
|
||||
self.chart.bars[2].fillColor = color03
|
||||
self.chart.bars[3].fillColor = color04
|
||||
self.chart.bars[4].fillColor = color05
|
||||
self.chart.bars[5].fillColor = color06
|
||||
self.chart.bars[6].fillColor = color07
|
||||
self.chart.bars[7].fillColor = color08
|
||||
self.chart.bars[8].fillColor = color09
|
||||
self.chart.bars[9].fillColor = color10
|
||||
self.chart.fillColor = backgroundGrey
|
||||
self.chart.barLabels.fontName = 'Helvetica'
|
||||
self.chart.valueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.valueAxis.labels.fontSize = 6
|
||||
self.chart.valueAxis.forceZero = 1
|
||||
self.chart.data = [(100, 150, 180), (125, 180, 200)]
|
||||
self.chart.groupSpacing = 15
|
||||
self.chart.valueAxis.avoidBoundFrac = 1
|
||||
self.chart.valueAxis.gridEnd = 80
|
||||
self.chart.valueAxis.tickDown = 3
|
||||
self.chart.valueAxis.visibleGrid = 1
|
||||
self.chart.categoryAxis.categoryNames = ['North', 'South', 'Central']
|
||||
self.chart.categoryAxis.tickLeft = 3
|
||||
self.chart.categoryAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.categoryAxis.labels.fontSize = 6
|
||||
self.chart.categoryAxis.labels.dx = -3
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 153
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis")
|
||||
self.XLabel.fontName = 'Helvetica'
|
||||
self.XLabel.fontSize = 7
|
||||
self.XLabel.x = 85
|
||||
self.XLabel.y = 10
|
||||
self.XLabel.textAnchor ='middle'
|
||||
self.XLabel.maxWidth = 100
|
||||
self.XLabel.height = 20
|
||||
self.XLabel._text = "X Axis"
|
||||
self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis")
|
||||
self.YLabel.fontName = 'Helvetica'
|
||||
self.YLabel.fontSize = 7
|
||||
self.YLabel.x = 12
|
||||
self.YLabel.y = 80
|
||||
self.YLabel.angle = 90
|
||||
self.YLabel.textAnchor ='middle'
|
||||
self.YLabel.maxWidth = 100
|
||||
self.YLabel.height = 20
|
||||
self.YLabel._text = "Y Axis"
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
ClusteredBar().save(formats=['pdf'],outDir=None,fnRoot='clustered_bar')
|
|
@ -1,83 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from excelcolors import *
|
||||
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
|
||||
class ClusteredColumn(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,VerticalBarChart(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 115
|
||||
self.chart.height = 80
|
||||
self.chart.x = 30
|
||||
self.chart.y = 40
|
||||
self.chart.bars[0].fillColor = color01
|
||||
self.chart.bars[1].fillColor = color02
|
||||
self.chart.bars[2].fillColor = color03
|
||||
self.chart.bars[3].fillColor = color04
|
||||
self.chart.bars[4].fillColor = color05
|
||||
self.chart.bars[5].fillColor = color06
|
||||
self.chart.bars[6].fillColor = color07
|
||||
self.chart.bars[7].fillColor = color08
|
||||
self.chart.bars[8].fillColor = color09
|
||||
self.chart.bars[9].fillColor = color10
|
||||
self.chart.fillColor = backgroundGrey
|
||||
self.chart.barLabels.fontName = 'Helvetica'
|
||||
self.chart.valueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.valueAxis.labels.fontSize = 7
|
||||
self.chart.valueAxis.forceZero = 1
|
||||
self.chart.data = [(100, 150, 180), (125, 180, 200)]
|
||||
self.chart.groupSpacing = 15
|
||||
self.chart.valueAxis.avoidBoundFrac = 1
|
||||
self.chart.valueAxis.gridEnd = 115
|
||||
self.chart.valueAxis.tickLeft = 3
|
||||
self.chart.valueAxis.visibleGrid = 1
|
||||
self.chart.categoryAxis.categoryNames = ['North', 'South', 'Central']
|
||||
self.chart.categoryAxis.tickDown = 3
|
||||
self.chart.categoryAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.categoryAxis.labels.fontSize = 7
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 153
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis")
|
||||
self.XLabel.fontName = 'Helvetica'
|
||||
self.XLabel.fontSize = 7
|
||||
self.XLabel.x = 85
|
||||
self.XLabel.y = 10
|
||||
self.XLabel.textAnchor ='middle'
|
||||
self.XLabel.maxWidth = 100
|
||||
self.XLabel.height = 20
|
||||
self.XLabel._text = "X Axis"
|
||||
self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis")
|
||||
self.YLabel.fontName = 'Helvetica'
|
||||
self.YLabel.fontSize = 7
|
||||
self.YLabel.x = 12
|
||||
self.YLabel.y = 80
|
||||
self.YLabel.angle = 90
|
||||
self.YLabel.textAnchor ='middle'
|
||||
self.YLabel.maxWidth = 100
|
||||
self.YLabel.height = 20
|
||||
self.YLabel._text = "Y Axis"
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
ClusteredColumn().save(formats=['pdf'],outDir=None,fnRoot='clustered_column')
|
|
@ -1,45 +0,0 @@
|
|||
# define standard colors to mimic those used by Microsoft Excel
|
||||
from reportlab.lib.colors import CMYKColor, PCMYKColor
|
||||
|
||||
#colour names as comments at the end of each line are as a memory jogger ONLY
|
||||
#NOT HTML named colours!
|
||||
|
||||
#Main colours as used for bars etc
|
||||
color01 = PCMYKColor(40,40,0,0) # Lavender
|
||||
color02 = PCMYKColor(0,66,33,39) # Maroon
|
||||
color03 = PCMYKColor(0,0,20,0) # Yellow
|
||||
color04 = PCMYKColor(20,0,0,0) # Cyan
|
||||
color05 = PCMYKColor(0,100,0,59) # Purple
|
||||
color06 = PCMYKColor(0,49,49,0) # Salmon
|
||||
color07 = PCMYKColor(100,49,0,19) # Blue
|
||||
color08 = PCMYKColor(20,20,0,0) # PaleLavender
|
||||
color09 = PCMYKColor(100,100,0,49) # NavyBlue
|
||||
color10 = PCMYKColor(0,100,0,0) # Purple
|
||||
|
||||
#Highlight colors - eg for the tops of bars
|
||||
color01Light = PCMYKColor(39,39,0,25) # Light Lavender
|
||||
color02Light = PCMYKColor(0,66,33,54) # Light Maroon
|
||||
color03Light = PCMYKColor(0,0,19,25) # Light Yellow
|
||||
color04Light = PCMYKColor(19,0,0,25) # Light Cyan
|
||||
color05Light = PCMYKColor(0,100,0,69) # Light Purple
|
||||
color06Light = PCMYKColor(0,49,49,25) # Light Salmon
|
||||
color07Light = PCMYKColor(100,49,0,39) # Light Blue
|
||||
color08Light = PCMYKColor(19,19,0,25) # Light PaleLavender
|
||||
color09Light = PCMYKColor(100,100,0,62) # Light NavyBlue
|
||||
color10Light = PCMYKColor(0,100,0,25) # Light Purple
|
||||
|
||||
#Lowlight colors - eg for the sides of bars
|
||||
color01Dark = PCMYKColor(39,39,0,49) # Dark Lavender
|
||||
color02Dark = PCMYKColor(0,66,33,69) # Dark Maroon
|
||||
color03Dark = PCMYKColor(0,0,20,49) # Dark Yellow
|
||||
color04Dark = PCMYKColor(20,0,0,49) # Dark Cyan
|
||||
color05Dark = PCMYKColor(0,100,0,80) # Dark Purple
|
||||
color06Dark = PCMYKColor(0,50,50,49) # Dark Salmon
|
||||
color07Dark = PCMYKColor(100,50,0,59) # Dark Blue
|
||||
color08Dark = PCMYKColor(20,20,0,49) # Dark PaleLavender
|
||||
color09Dark = PCMYKColor(100,100,0,79) # Dark NavyBlue
|
||||
color10Dark = PCMYKColor(0,100,0,49) # Dark Purple
|
||||
|
||||
#for standard grey backgrounds
|
||||
backgroundGrey = PCMYKColor(0,0,0,24)
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.piecharts import Pie
|
||||
from excelcolors import *
|
||||
from reportlab.graphics.widgets.grids import ShadedRect
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
|
||||
class ExplodedPie(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,Pie(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 100
|
||||
self.chart.height = 100
|
||||
self.chart.x = 25
|
||||
self.chart.y = 25
|
||||
self.chart.slices[0].fillColor = color01
|
||||
self.chart.slices[1].fillColor = color02
|
||||
self.chart.slices[2].fillColor = color03
|
||||
self.chart.slices[3].fillColor = color04
|
||||
self.chart.slices[4].fillColor = color05
|
||||
self.chart.slices[5].fillColor = color06
|
||||
self.chart.slices[6].fillColor = color07
|
||||
self.chart.slices[7].fillColor = color08
|
||||
self.chart.slices[8].fillColor = color09
|
||||
self.chart.slices[9].fillColor = color10
|
||||
self.chart.data = (100, 150, 180)
|
||||
self.chart.startAngle = -90
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'North'), (color02, 'South'), (color03, 'Central')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 160
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self.Legend.columnMaximum = 10
|
||||
self.chart.slices.strokeWidth = 1
|
||||
self.chart.slices.fontName = 'Helvetica'
|
||||
self.background = ShadedRect()
|
||||
self.background.fillColorStart = backgroundGrey
|
||||
self.background.fillColorEnd = backgroundGrey
|
||||
self.background.numShades = 1
|
||||
self.background.strokeWidth = 0.5
|
||||
self.background.x = 20
|
||||
self.background.y = 20
|
||||
self.chart.slices.popout = 5
|
||||
self.background.height = 110
|
||||
self.background.width = 110
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
ExplodedPie().save(formats=['pdf'],outDir=None,fnRoot='exploded_pie')
|
|
@ -1,54 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from reportlab.graphics.charts.spider import SpiderChart
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from excelcolors import *
|
||||
|
||||
class FilledRadarChart(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,SpiderChart(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 90
|
||||
self.chart.height = 90
|
||||
self.chart.x = 45
|
||||
self.chart.y = 25
|
||||
self.chart.strands[0].fillColor = color01
|
||||
self.chart.strands[1].fillColor = color02
|
||||
self.chart.strands[2].fillColor = color03
|
||||
self.chart.strands[3].fillColor = color04
|
||||
self.chart.strands[4].fillColor = color05
|
||||
self.chart.strands[5].fillColor = color06
|
||||
self.chart.strands[6].fillColor = color07
|
||||
self.chart.strands[7].fillColor = color08
|
||||
self.chart.strands[8].fillColor = color09
|
||||
self.chart.strands[9].fillColor = color10
|
||||
self.chart.strands.fontName = 'Helvetica'
|
||||
self.chart.strands.fontSize = 6
|
||||
self.chart.fillColor = backgroundGrey
|
||||
self.chart.data = [(125, 180, 200), (100, 150, 180)]
|
||||
self.chart.labels = ['North', 'South', 'Central']
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 153
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
FilledRadarChart().save(formats=['pdf'],outDir=None,fnRoot='filled_radar')
|
|
@ -1,83 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from reportlab.graphics.charts.lineplots import LinePlot
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from excelcolors import *
|
||||
|
||||
class LineChart(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,LinePlot(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 115
|
||||
self.chart.height = 80
|
||||
self.chart.x = 30
|
||||
self.chart.y = 40
|
||||
self.chart.lines[0].strokeColor = color01
|
||||
self.chart.lines[1].strokeColor = color02
|
||||
self.chart.lines[2].strokeColor = color03
|
||||
self.chart.lines[3].strokeColor = color04
|
||||
self.chart.lines[4].strokeColor = color05
|
||||
self.chart.lines[5].strokeColor = color06
|
||||
self.chart.lines[6].strokeColor = color07
|
||||
self.chart.lines[7].strokeColor = color08
|
||||
self.chart.lines[8].strokeColor = color09
|
||||
self.chart.lines[9].strokeColor = color10
|
||||
self.chart.fillColor = backgroundGrey
|
||||
self.chart.lineLabels.fontName = 'Helvetica'
|
||||
self.chart.xValueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.xValueAxis.labels.fontSize = 7
|
||||
self.chart.xValueAxis.forceZero = 0
|
||||
self.chart.data = [((0, 50), (100,100), (200,200), (250,210), (300,300), (400,500)), ((0, 150), (100,200), (200,300), (250,200), (300,400), (400, 600))]
|
||||
self.chart.xValueAxis.avoidBoundFrac = 1
|
||||
self.chart.xValueAxis.gridEnd = 115
|
||||
self.chart.xValueAxis.tickDown = 3
|
||||
self.chart.xValueAxis.visibleGrid = 1
|
||||
self.chart.yValueAxis.tickLeft = 3
|
||||
self.chart.yValueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.yValueAxis.labels.fontSize = 7
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 153
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis")
|
||||
self.XLabel.fontName = 'Helvetica'
|
||||
self.XLabel.fontSize = 7
|
||||
self.XLabel.x = 85
|
||||
self.XLabel.y = 10
|
||||
self.XLabel.textAnchor ='middle'
|
||||
self.XLabel.maxWidth = 100
|
||||
self.XLabel.height = 20
|
||||
self.XLabel._text = "X Axis"
|
||||
self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis")
|
||||
self.YLabel.fontName = 'Helvetica'
|
||||
self.YLabel.fontSize = 7
|
||||
self.YLabel.x = 12
|
||||
self.YLabel.y = 80
|
||||
self.YLabel.angle = 90
|
||||
self.YLabel.textAnchor ='middle'
|
||||
self.YLabel.maxWidth = 100
|
||||
self.YLabel.height = 20
|
||||
self.YLabel._text = "Y Axis"
|
||||
self.chart.yValueAxis.forceZero = 1
|
||||
self.chart.xValueAxis.forceZero = 1
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
LineChart().save(formats=['pdf'],outDir=None,fnRoot='line_chart')
|
|
@ -1,94 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from reportlab.graphics.charts.lineplots import LinePlot
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.widgets.markers import makeMarker
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from excelcolors import *
|
||||
|
||||
class LineChartWithMarkers(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,LinePlot(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 115
|
||||
self.chart.height = 80
|
||||
self.chart.x = 30
|
||||
self.chart.y = 40
|
||||
self.chart.lines[0].strokeColor = color01
|
||||
self.chart.lines[1].strokeColor = color02
|
||||
self.chart.lines[2].strokeColor = color03
|
||||
self.chart.lines[3].strokeColor = color04
|
||||
self.chart.lines[4].strokeColor = color05
|
||||
self.chart.lines[5].strokeColor = color06
|
||||
self.chart.lines[6].strokeColor = color07
|
||||
self.chart.lines[7].strokeColor = color08
|
||||
self.chart.lines[8].strokeColor = color09
|
||||
self.chart.lines[9].strokeColor = color10
|
||||
self.chart.lines[0].symbol = makeMarker('FilledSquare')
|
||||
self.chart.lines[1].symbol = makeMarker('FilledDiamond')
|
||||
self.chart.lines[2].symbol = makeMarker('FilledStarFive')
|
||||
self.chart.lines[3].symbol = makeMarker('FilledTriangle')
|
||||
self.chart.lines[4].symbol = makeMarker('FilledCircle')
|
||||
self.chart.lines[5].symbol = makeMarker('FilledPentagon')
|
||||
self.chart.lines[6].symbol = makeMarker('FilledStarSix')
|
||||
self.chart.lines[7].symbol = makeMarker('FilledHeptagon')
|
||||
self.chart.lines[8].symbol = makeMarker('FilledOctagon')
|
||||
self.chart.lines[9].symbol = makeMarker('FilledCross')
|
||||
self.chart.fillColor = backgroundGrey
|
||||
self.chart.lineLabels.fontName = 'Helvetica'
|
||||
self.chart.xValueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.xValueAxis.labels.fontSize = 7
|
||||
self.chart.xValueAxis.forceZero = 0
|
||||
self.chart.data = [((0, 50), (100,100), (200,200), (250,210), (300,300), (400,500)), ((0, 150), (100,200), (200,300), (250,200), (300,400), (400, 600))]
|
||||
self.chart.xValueAxis.avoidBoundFrac = 1
|
||||
self.chart.xValueAxis.gridEnd = 115
|
||||
self.chart.xValueAxis.tickDown = 3
|
||||
self.chart.xValueAxis.visibleGrid = 1
|
||||
self.chart.yValueAxis.tickLeft = 3
|
||||
self.chart.yValueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.yValueAxis.labels.fontSize = 7
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 153
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis")
|
||||
self.XLabel.fontName = 'Helvetica'
|
||||
self.XLabel.fontSize = 7
|
||||
self.XLabel.x = 85
|
||||
self.XLabel.y = 10
|
||||
self.XLabel.textAnchor ='middle'
|
||||
self.XLabel.maxWidth = 100
|
||||
self.XLabel.height = 20
|
||||
self.XLabel._text = "X Axis"
|
||||
self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis")
|
||||
self.YLabel.fontName = 'Helvetica'
|
||||
self.YLabel.fontSize = 7
|
||||
self.YLabel.x = 12
|
||||
self.YLabel.y = 80
|
||||
self.YLabel.angle = 90
|
||||
self.YLabel.textAnchor ='middle'
|
||||
self.YLabel.maxWidth = 100
|
||||
self.YLabel.height = 20
|
||||
self.YLabel._text = "Y Axis"
|
||||
self.chart.yValueAxis.forceZero = 1
|
||||
self.chart.xValueAxis.forceZero = 1
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
LineChartWithMarkers().save(formats=['pdf'],outDir=None,fnRoot='linechart_with_markers')
|
|
@ -1,66 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from excelcolors import *
|
||||
from reportlab.graphics.charts.spider import SpiderChart
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
|
||||
class RadarChart(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,SpiderChart(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 90
|
||||
self.chart.height = 90
|
||||
self.chart.x = 45
|
||||
self.chart.y = 25
|
||||
self.chart.strands[0].strokeColor= color01
|
||||
self.chart.strands[1].strokeColor= color02
|
||||
self.chart.strands[2].strokeColor= color03
|
||||
self.chart.strands[3].strokeColor= color04
|
||||
self.chart.strands[4].strokeColor= color05
|
||||
self.chart.strands[5].strokeColor= color06
|
||||
self.chart.strands[6].strokeColor= color07
|
||||
self.chart.strands[7].strokeColor= color08
|
||||
self.chart.strands[8].strokeColor= color09
|
||||
self.chart.strands[9].strokeColor= color10
|
||||
self.chart.strands[0].fillColor = None
|
||||
self.chart.strands[1].fillColor = None
|
||||
self.chart.strands[2].fillColor = None
|
||||
self.chart.strands[3].fillColor = None
|
||||
self.chart.strands[4].fillColor = None
|
||||
self.chart.strands[5].fillColor = None
|
||||
self.chart.strands[6].fillColor = None
|
||||
self.chart.strands[7].fillColor = None
|
||||
self.chart.strands[8].fillColor = None
|
||||
self.chart.strands[9].fillColor = None
|
||||
self.chart.strands.strokeWidth = 1
|
||||
self.chart.strands.fontName = 'Helvetica'
|
||||
self.chart.strands.fontSize = 6
|
||||
self.chart.fillColor = backgroundGrey
|
||||
self.chart.data = [(125, 180, 200), (100, 150, 180)]
|
||||
self.chart.labels = ['North', 'South', 'Central']
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 153
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self.chart.strands.strokeWidth = 1
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
RadarChart().save(formats=['pdf'],outDir=None,fnRoot='radar')
|
|
@ -1,59 +0,0 @@
|
|||
# runs all the GUIedit charts in this directory -
|
||||
# makes a PDF sample for eaxh existing chart type
|
||||
import sys
|
||||
import glob
|
||||
import string
|
||||
import inspect
|
||||
import types
|
||||
|
||||
def moduleClasses(mod):
|
||||
def P(obj, m=mod.__name__, CT=types.ClassType):
|
||||
return (type(obj)==CT and obj.__module__==m)
|
||||
try:
|
||||
return inspect.getmembers(mod, P)[0][1]
|
||||
except:
|
||||
return None
|
||||
|
||||
def getclass(f):
|
||||
return moduleClasses(__import__(f))
|
||||
|
||||
def run(format, VERBOSE=0):
|
||||
formats = string.split(format, ',')
|
||||
for i in range(0, len(formats)):
|
||||
formats[i] == string.lower(string.strip(formats[i]))
|
||||
allfiles = glob.glob('*.py')
|
||||
allfiles.sort()
|
||||
for fn in allfiles:
|
||||
f = string.split(fn, '.')[0]
|
||||
c = getclass(f)
|
||||
if c != None:
|
||||
print c.__name__
|
||||
try:
|
||||
for fmt in formats:
|
||||
if fmt:
|
||||
c().save(formats=[fmt],outDir='.',fnRoot=c.__name__)
|
||||
if VERBOSE:
|
||||
print " %s.%s" % (c.__name__, fmt)
|
||||
except:
|
||||
print " COULDN'T CREATE '%s.%s'!" % (c.__name__, format)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) == 1:
|
||||
run('pdf,pict,png')
|
||||
else:
|
||||
try:
|
||||
if sys.argv[1] == "-h":
|
||||
print 'usage: runall.py [FORMAT] [-h]'
|
||||
print ' if format is supplied is should be one or more of pdf,gif,eps,png etc'
|
||||
print ' if format is missing the following formats are assumed: pdf,pict,png'
|
||||
print ' -h prints this message'
|
||||
else:
|
||||
t = sys.argv[1:]
|
||||
for f in t:
|
||||
run(f)
|
||||
except:
|
||||
print 'usage: runall.py [FORMAT][-h]'
|
||||
print ' if format is supplied is should be one or more of pdf,gif,eps,png etc'
|
||||
print ' if format is missing the following formats are assumed: pdf,pict,png'
|
||||
print ' -h prints this message'
|
||||
raise
|
|
@ -1,71 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from reportlab.graphics.charts.lineplots import ScatterPlot
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from excelcolors import *
|
||||
|
||||
class Scatter(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,ScatterPlot(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 115
|
||||
self.chart.height = 80
|
||||
self.chart.x = 30
|
||||
self.chart.y = 40
|
||||
self.chart.lines[0].strokeColor = color01
|
||||
self.chart.lines[1].strokeColor = color02
|
||||
self.chart.lines[2].strokeColor = color03
|
||||
self.chart.lines[3].strokeColor = color04
|
||||
self.chart.lines[4].strokeColor = color05
|
||||
self.chart.lines[5].strokeColor = color06
|
||||
self.chart.lines[6].strokeColor = color07
|
||||
self.chart.lines[7].strokeColor = color08
|
||||
self.chart.lines[8].strokeColor = color09
|
||||
self.chart.lines[9].strokeColor = color10
|
||||
self.chart.fillColor = backgroundGrey
|
||||
self.chart.lineLabels.fontName = 'Helvetica'
|
||||
self.chart.xValueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.xValueAxis.labels.fontSize = 7
|
||||
self.chart.xValueAxis.forceZero = 0
|
||||
self.chart.data = [((100,100), (200,200), (250,210), (300,300), (400,500)), ((100,200), (200,300), (250,200), (300,400), (400, 600))]
|
||||
self.chart.xValueAxis.avoidBoundFrac = 1
|
||||
self.chart.xValueAxis.gridEnd = 115
|
||||
self.chart.xValueAxis.tickDown = 3
|
||||
self.chart.xValueAxis.visibleGrid = 1
|
||||
self.chart.yValueAxis.tickLeft = 3
|
||||
self.chart.yValueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.yValueAxis.labels.fontSize = 7
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 153
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self.chart.lineLabelFormat = None
|
||||
self.chart.xLabel = 'X Axis'
|
||||
self.chart.y = 30
|
||||
self.chart.yLabel = 'Y Axis'
|
||||
self.chart.yValueAxis.labelTextFormat = '%d'
|
||||
self.chart.yValueAxis.forceZero = 1
|
||||
self.chart.xValueAxis.forceZero = 1
|
||||
|
||||
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
Scatter().save(formats=['pdf'],outDir=None,fnRoot='scatter')
|
|
@ -1,82 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from reportlab.graphics.charts.lineplots import ScatterPlot
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from excelcolors import *
|
||||
|
||||
class ScatterLines(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,ScatterPlot(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 115
|
||||
self.chart.height = 80
|
||||
self.chart.x = 30
|
||||
self.chart.y = 40
|
||||
self.chart.lines[0].strokeColor = color01
|
||||
self.chart.lines[1].strokeColor = color02
|
||||
self.chart.lines[2].strokeColor = color03
|
||||
self.chart.lines[3].strokeColor = color04
|
||||
self.chart.lines[4].strokeColor = color05
|
||||
self.chart.lines[5].strokeColor = color06
|
||||
self.chart.lines[6].strokeColor = color07
|
||||
self.chart.lines[7].strokeColor = color08
|
||||
self.chart.lines[8].strokeColor = color09
|
||||
self.chart.lines[9].strokeColor = color10
|
||||
self.chart.lines[0].symbol = None
|
||||
self.chart.lines[1].symbol = None
|
||||
self.chart.lines[2].symbol = None
|
||||
self.chart.lines[3].symbol = None
|
||||
self.chart.lines[4].symbol = None
|
||||
self.chart.lines[5].symbol = None
|
||||
self.chart.lines[6].symbol = None
|
||||
self.chart.lines[7].symbol = None
|
||||
self.chart.lines[8].symbol = None
|
||||
self.chart.lines[9].symbol = None
|
||||
self.chart.fillColor = backgroundGrey
|
||||
self.chart.lineLabels.fontName = 'Helvetica'
|
||||
self.chart.xValueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.xValueAxis.labels.fontSize = 7
|
||||
self.chart.xValueAxis.forceZero = 0
|
||||
self.chart.data = [((100,100), (200,200), (250,210), (300,300), (400,500)), ((100,200), (200,300), (250,200), (300,400), (400, 600))]
|
||||
self.chart.xValueAxis.avoidBoundFrac = 1
|
||||
self.chart.xValueAxis.gridEnd = 115
|
||||
self.chart.xValueAxis.tickDown = 3
|
||||
self.chart.xValueAxis.visibleGrid = 1
|
||||
self.chart.yValueAxis.tickLeft = 3
|
||||
self.chart.yValueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.yValueAxis.labels.fontSize = 7
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 153
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self.chart.lineLabelFormat = None
|
||||
self.chart.xLabel = 'X Axis'
|
||||
self.chart.y = 30
|
||||
self.chart.yLabel = 'Y Axis'
|
||||
self.chart.yValueAxis.gridEnd = 115
|
||||
self.chart.yValueAxis.visibleGrid = 1
|
||||
self.chart.yValueAxis.labelTextFormat = '%d'
|
||||
self.chart.yValueAxis.forceZero = 1
|
||||
self.chart.xValueAxis.forceZero = 1
|
||||
self.chart.joinedLines = 1
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
ScatterLines().save(formats=['pdf'],outDir=None,fnRoot='scatter_lines')
|
|
@ -1,72 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from reportlab.graphics.charts.lineplots import ScatterPlot
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from excelcolors import *
|
||||
|
||||
class ScatterLinesMarkers(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,ScatterPlot(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 115
|
||||
self.chart.height = 80
|
||||
self.chart.x = 30
|
||||
self.chart.y = 40
|
||||
self.chart.lines[0].strokeColor = color01
|
||||
self.chart.lines[1].strokeColor = color02
|
||||
self.chart.lines[2].strokeColor = color03
|
||||
self.chart.lines[3].strokeColor = color04
|
||||
self.chart.lines[4].strokeColor = color05
|
||||
self.chart.lines[5].strokeColor = color06
|
||||
self.chart.lines[6].strokeColor = color07
|
||||
self.chart.lines[7].strokeColor = color08
|
||||
self.chart.lines[8].strokeColor = color09
|
||||
self.chart.lines[9].strokeColor = color10
|
||||
self.chart.fillColor = backgroundGrey
|
||||
self.chart.lineLabels.fontName = 'Helvetica'
|
||||
self.chart.xValueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.xValueAxis.labels.fontSize = 7
|
||||
self.chart.xValueAxis.forceZero = 0
|
||||
self.chart.data = [((100,100), (200,200), (250,210), (300,300), (400,500)), ((100,200), (200,300), (250,200), (300,400), (400, 600))]
|
||||
self.chart.xValueAxis.avoidBoundFrac = 1
|
||||
self.chart.xValueAxis.gridEnd = 115
|
||||
self.chart.xValueAxis.tickDown = 3
|
||||
self.chart.xValueAxis.visibleGrid = 1
|
||||
self.chart.yValueAxis.tickLeft = 3
|
||||
self.chart.yValueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.yValueAxis.labels.fontSize = 7
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 153
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self.chart.lineLabelFormat = None
|
||||
self.chart.xLabel = 'X Axis'
|
||||
self.chart.y = 30
|
||||
self.chart.yLabel = 'Y Axis'
|
||||
self.chart.yValueAxis.gridEnd = 115
|
||||
self.chart.yValueAxis.visibleGrid = 1
|
||||
self.chart.yValueAxis.labelTextFormat = '%d'
|
||||
self.chart.yValueAxis.forceZero = 1
|
||||
self.chart.xValueAxis.forceZero = 1
|
||||
self.chart.joinedLines = 1
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
ScatterLinesMarkers().save(formats=['pdf'],outDir=None,fnRoot='scatter_lines_markers')
|
|
@ -1,61 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.piecharts import Pie
|
||||
from reportlab.graphics.widgets.grids import ShadedRect
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from excelcolors import *
|
||||
|
||||
class SimplePie(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,Pie(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 100
|
||||
self.chart.height = 100
|
||||
self.chart.x = 25
|
||||
self.chart.y = 25
|
||||
self.chart.slices[0].fillColor = color01
|
||||
self.chart.slices[1].fillColor = color02
|
||||
self.chart.slices[2].fillColor = color03
|
||||
self.chart.slices[3].fillColor = color04
|
||||
self.chart.slices[4].fillColor = color05
|
||||
self.chart.slices[5].fillColor = color06
|
||||
self.chart.slices[6].fillColor = color07
|
||||
self.chart.slices[7].fillColor = color08
|
||||
self.chart.slices[8].fillColor = color09
|
||||
self.chart.slices[9].fillColor = color10
|
||||
self.chart.data = (100, 150, 180)
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'North'), (color02, 'South'),(color03, 'Central')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 160
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self.chart.slices.strokeWidth = 1
|
||||
self.chart.slices.fontName = 'Helvetica'
|
||||
self.background = ShadedRect()
|
||||
self.background.fillColorStart = backgroundGrey
|
||||
self.background.fillColorEnd = backgroundGrey
|
||||
self.background.numShades = 1
|
||||
self.background.strokeWidth = 0.5
|
||||
self.background.x = 25
|
||||
self.background.y = 25
|
||||
self.Legend.columnMaximum = 10
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
SimplePie().save(formats=['pdf'],outDir=None,fnRoot=None)
|
|
@ -1,85 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from reportlab.graphics.charts.barcharts import HorizontalBarChart
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from excelcolors import *
|
||||
|
||||
class StackedBar(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,HorizontalBarChart(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 115
|
||||
self.chart.height = 80
|
||||
self.chart.x = 30
|
||||
self.chart.y = 40
|
||||
self.chart.bars[0].fillColor = color01
|
||||
self.chart.bars[1].fillColor = color02
|
||||
self.chart.bars[2].fillColor = color03
|
||||
self.chart.bars[3].fillColor = color04
|
||||
self.chart.bars[4].fillColor = color05
|
||||
self.chart.bars[5].fillColor = color06
|
||||
self.chart.bars[6].fillColor = color07
|
||||
self.chart.bars[7].fillColor = color08
|
||||
self.chart.bars[8].fillColor = color09
|
||||
self.chart.bars[9].fillColor = color10
|
||||
self.chart.fillColor = backgroundGrey
|
||||
self.chart.barLabels.fontName = 'Helvetica'
|
||||
self.chart.valueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.valueAxis.labels.fontSize = 6
|
||||
self.chart.valueAxis.forceZero = 1
|
||||
self.chart.data = [(100, 150, 180), (125, 180, 200)]
|
||||
self.chart.groupSpacing = 15
|
||||
self.chart.valueAxis.avoidBoundFrac = 1
|
||||
self.chart.valueAxis.gridEnd = 80
|
||||
self.chart.valueAxis.tickDown = 3
|
||||
self.chart.valueAxis.visibleGrid = 1
|
||||
self.chart.categoryAxis.categoryNames = ['North', 'South', 'Central']
|
||||
self.chart.categoryAxis.tickLeft = 3
|
||||
self.chart.categoryAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.categoryAxis.labels.fontSize = 6
|
||||
self.chart.categoryAxis.labels.dx = -3
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 153
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis")
|
||||
self.XLabel.fontName = 'Helvetica'
|
||||
self.XLabel.fontSize = 7
|
||||
self.XLabel.x = 85
|
||||
self.XLabel.y = 10
|
||||
self.XLabel.textAnchor ='middle'
|
||||
self.XLabel.maxWidth = 100
|
||||
self.XLabel.height = 20
|
||||
self.XLabel._text = "X Axis"
|
||||
self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis")
|
||||
self.YLabel.fontName = 'Helvetica'
|
||||
self.YLabel.fontSize = 7
|
||||
self.YLabel.x = 12
|
||||
self.YLabel.y = 80
|
||||
self.YLabel.angle = 90
|
||||
self.YLabel.textAnchor ='middle'
|
||||
self.YLabel.maxWidth = 100
|
||||
self.YLabel.height = 20
|
||||
self.YLabel._text = "Y Axis"
|
||||
self.chart.categoryAxis.style='stacked'
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
StackedBar().save(formats=['pdf'],outDir=None,fnRoot='stacked_bar')
|
|
@ -1,84 +0,0 @@
|
|||
#Autogenerated by ReportLab guiedit do not edit
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
||||
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from excelcolors import *
|
||||
|
||||
class StackedColumn(_DrawingEditorMixin,Drawing):
|
||||
def __init__(self,width=200,height=150,*args,**kw):
|
||||
apply(Drawing.__init__,(self,width,height)+args,kw)
|
||||
self._add(self,VerticalBarChart(),name='chart',validate=None,desc="The main chart")
|
||||
self.chart.width = 115
|
||||
self.chart.height = 80
|
||||
self.chart.x = 30
|
||||
self.chart.y = 40
|
||||
self.chart.bars[0].fillColor = color01
|
||||
self.chart.bars[1].fillColor = color02
|
||||
self.chart.bars[2].fillColor = color03
|
||||
self.chart.bars[3].fillColor = color04
|
||||
self.chart.bars[4].fillColor = color05
|
||||
self.chart.bars[5].fillColor = color06
|
||||
self.chart.bars[6].fillColor = color07
|
||||
self.chart.bars[7].fillColor = color08
|
||||
self.chart.bars[8].fillColor = color09
|
||||
self.chart.bars[9].fillColor = color10
|
||||
self.chart.fillColor = backgroundGrey
|
||||
self.chart.barLabels.fontName = 'Helvetica'
|
||||
self.chart.valueAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.valueAxis.labels.fontSize = 7
|
||||
self.chart.valueAxis.forceZero = 1
|
||||
self.chart.data = [(100, 150, 180), (125, 180, 200)]
|
||||
self.chart.groupSpacing = 15
|
||||
self.chart.valueAxis.avoidBoundFrac = 1
|
||||
self.chart.valueAxis.gridEnd = 115
|
||||
self.chart.valueAxis.tickLeft = 3
|
||||
self.chart.valueAxis.visibleGrid = 1
|
||||
self.chart.categoryAxis.categoryNames = ['North', 'South', 'Central']
|
||||
self.chart.categoryAxis.tickDown = 3
|
||||
self.chart.categoryAxis.labels.fontName = 'Helvetica'
|
||||
self.chart.categoryAxis.labels.fontSize = 7
|
||||
self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart")
|
||||
self.Title.fontName = 'Helvetica-Bold'
|
||||
self.Title.fontSize = 7
|
||||
self.Title.x = 100
|
||||
self.Title.y = 135
|
||||
self.Title._text = 'Chart Title'
|
||||
self.Title.maxWidth = 180
|
||||
self.Title.height = 20
|
||||
self.Title.textAnchor ='middle'
|
||||
self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart")
|
||||
self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')]
|
||||
self.Legend.fontName = 'Helvetica'
|
||||
self.Legend.fontSize = 7
|
||||
self.Legend.x = 153
|
||||
self.Legend.y = 85
|
||||
self.Legend.dxTextSpace = 5
|
||||
self.Legend.dy = 5
|
||||
self.Legend.dx = 5
|
||||
self.Legend.deltay = 5
|
||||
self.Legend.alignment ='right'
|
||||
self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis")
|
||||
self.XLabel.fontName = 'Helvetica'
|
||||
self.XLabel.fontSize = 7
|
||||
self.XLabel.x = 85
|
||||
self.XLabel.y = 10
|
||||
self.XLabel.textAnchor ='middle'
|
||||
self.XLabel.maxWidth = 100
|
||||
self.XLabel.height = 20
|
||||
self.XLabel._text = "X Axis"
|
||||
self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis")
|
||||
self.YLabel.fontName = 'Helvetica'
|
||||
self.YLabel.fontSize = 7
|
||||
self.YLabel.x = 12
|
||||
self.YLabel.y = 80
|
||||
self.YLabel.angle = 90
|
||||
self.YLabel.textAnchor ='middle'
|
||||
self.YLabel.maxWidth = 100
|
||||
self.YLabel.height = 20
|
||||
self.YLabel._text = "Y Axis"
|
||||
self.chart.categoryAxis.style='stacked'
|
||||
self._add(self,0,name='preview',validate=None,desc=None)
|
||||
|
||||
if __name__=="__main__": #NORUNTESTS
|
||||
StackedColumn().save(formats=['pdf'],outDir=None,fnRoot='stacked_column')
|
File diff suppressed because it is too large
Load Diff
|
@ -1,294 +0,0 @@
|
|||
#! /usr/bin/python2.3
|
||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/testdrawings.py
|
||||
__version__=''' $Id $ '''
|
||||
"""This contains a number of routines to generate test drawings
|
||||
for reportlab/graphics. For now they are contrived, but we will expand them
|
||||
to try and trip up any parser. Feel free to add more.
|
||||
|
||||
"""
|
||||
|
||||
from reportlab.graphics.shapes import *
|
||||
from reportlab.lib import colors
|
||||
|
||||
def getDrawing1():
|
||||
"""Hello World, on a rectangular background"""
|
||||
|
||||
D = Drawing(400, 200)
|
||||
D.add(Rect(50, 50, 300, 100, fillColor=colors.yellow)) #round corners
|
||||
D.add(String(180,100, 'Hello World', fillColor=colors.red))
|
||||
|
||||
|
||||
return D
|
||||
|
||||
|
||||
def getDrawing2():
|
||||
"""This demonstrates the basic shapes. There are
|
||||
no groups or references. Each solid shape should have
|
||||
a purple fill."""
|
||||
D = Drawing(400, 200) #, fillColor=colors.purple)
|
||||
|
||||
D.add(Line(10,10,390,190))
|
||||
D.add(Circle(100,100,20, fillColor=colors.purple))
|
||||
D.add(Circle(200,100,20, fillColor=colors.purple))
|
||||
D.add(Circle(300,100,20, fillColor=colors.purple))
|
||||
|
||||
D.add(Wedge(330,100,40, -10,40, fillColor=colors.purple))
|
||||
|
||||
D.add(PolyLine([120,10,130,20,140,10,150,20,160,10,
|
||||
170,20,180,10,190,20,200,10]))
|
||||
|
||||
D.add(Polygon([300,20,350,20,390,80,300,75, 330, 40]))
|
||||
|
||||
D.add(Ellipse(50, 150, 40, 20))
|
||||
|
||||
D.add(Rect(120, 150, 60, 30,
|
||||
strokeWidth=10,
|
||||
strokeColor=colors.red,
|
||||
fillColor=colors.yellow)) #square corners
|
||||
|
||||
D.add(Rect(220, 150, 60, 30, 10, 10)) #round corners
|
||||
|
||||
D.add(String(10,50, 'Basic Shapes', fillColor=colors.black))
|
||||
|
||||
return D
|
||||
|
||||
|
||||
##def getDrawing2():
|
||||
## """This drawing uses groups. Each group has two circles and a comment.
|
||||
## The line style is set at group level and should be red for the left,
|
||||
## bvlue for the right."""
|
||||
## D = Drawing(400, 200)
|
||||
##
|
||||
## Group1 = Group()
|
||||
##
|
||||
## Group1.add(String(50, 50, 'Group 1', fillColor=colors.black))
|
||||
## Group1.add(Circle(75,100,25))
|
||||
## Group1.add(Circle(125,100,25))
|
||||
## D.add(Group1)
|
||||
##
|
||||
## Group2 = Group(
|
||||
## String(250, 50, 'Group 2', fillColor=colors.black),
|
||||
## Circle(275,100,25),
|
||||
## Circle(325,100,25)#,
|
||||
|
||||
|
||||
##def getDrawing2():
|
||||
## """This drawing uses groups. Each group has two circles and a comment.
|
||||
## The line style is set at group level and should be red for the left,
|
||||
## bvlue for the right."""
|
||||
## D = Drawing(400, 200)
|
||||
##
|
||||
## Group1 = Group()
|
||||
##
|
||||
## Group1.add(String(50, 50, 'Group 1', fillColor=colors.black))
|
||||
## Group1.add(Circle(75,100,25))
|
||||
## Group1.add(Circle(125,100,25))
|
||||
## D.add(Group1)
|
||||
##
|
||||
## Group2 = Group(
|
||||
## String(250, 50, 'Group 2', fillColor=colors.black),
|
||||
## Circle(275,100,25),
|
||||
## Circle(325,100,25)#,
|
||||
##
|
||||
## #group attributes
|
||||
## #strokeColor=colors.blue
|
||||
## )
|
||||
## D.add(Group2)
|
||||
|
||||
## return D
|
||||
##
|
||||
##
|
||||
##def getDrawing3():
|
||||
## """This uses a named reference object. The house is a 'subroutine'
|
||||
## the basic brick colored walls are defined, but the roof and window
|
||||
## color are undefined and may be set by the container."""
|
||||
##
|
||||
## D = Drawing(400, 200, fill=colors.bisque)
|
||||
##
|
||||
##
|
||||
## House = Group(
|
||||
## Rect(2,20,36,30, fill=colors.bisque), #walls
|
||||
## Polygon([0,20,40,20,20,5]), #roof
|
||||
## Rect(8, 38, 8, 12), #door
|
||||
## Rect(25, 38, 8, 7), #window
|
||||
## Rect(8, 25, 8, 7), #window
|
||||
## Rect(25, 25, 8, 7) #window
|
||||
##
|
||||
## )
|
||||
## D.addDef('MyHouse', House)
|
||||
##
|
||||
## # one row all the same color
|
||||
## D.add(String(20, 40, 'British Street...',fill=colors.black))
|
||||
## for i in range(6):
|
||||
## x = i * 50
|
||||
## D.add(NamedReference('MyHouse',
|
||||
## House,
|
||||
## transform=translate(x, 40),
|
||||
## fill = colors.brown
|
||||
## )
|
||||
## )
|
||||
##
|
||||
## # now do a row all different
|
||||
## D.add(String(20, 120, 'Mediterranean Street...',fill=colors.black))
|
||||
## x = 0
|
||||
## for color in (colors.blue, colors.yellow, colors.orange,
|
||||
## colors.red, colors.green, colors.chartreuse):
|
||||
## D.add(NamedReference('MyHouse',
|
||||
## House,
|
||||
## transform=translate(x,120),
|
||||
## fill = color,
|
||||
## )
|
||||
## )
|
||||
## x = x + 50
|
||||
## #..by popular demand, the mayor gets a big one at the end
|
||||
## D.add(NamedReference('MyHouse',
|
||||
## House,
|
||||
## transform=mmult(translate(x,110), scale(1.2,1.2)),
|
||||
## fill = color,
|
||||
## )
|
||||
## )
|
||||
##
|
||||
##
|
||||
## return D
|
||||
##
|
||||
##def getDrawing4():
|
||||
## """This tests that attributes are 'unset' correctly when
|
||||
## one steps back out of a drawing node. All the circles are part of a
|
||||
## group setting the line color to blue; the second circle explicitly
|
||||
## sets it to red. Ideally, the third circle should go back to blue."""
|
||||
## D = Drawing(400, 200)
|
||||
##
|
||||
##
|
||||
## G = Group(
|
||||
## Circle(100,100,20),
|
||||
## Circle(200,100,20, stroke=colors.blue),
|
||||
## Circle(300,100,20),
|
||||
## stroke=colors.red,
|
||||
## stroke_width=3,
|
||||
## fill=colors.aqua
|
||||
## )
|
||||
## D.add(G)
|
||||
##
|
||||
##
|
||||
## D.add(String(10,50, 'Stack Unwinding - should be red, blue, red'))
|
||||
##
|
||||
## return D
|
||||
##
|
||||
##
|
||||
##def getDrawing5():
|
||||
## """This Rotates Coordinate Axes"""
|
||||
## 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)
|
||||
## )
|
||||
##
|
||||
## D.addDef('Axes', Axis)
|
||||
##
|
||||
## D.add(NamedReference('Axis', Axis,
|
||||
## transform=translate(10,10)))
|
||||
## D.add(NamedReference('Axis', Axis,
|
||||
## transform=mmult(translate(150,10),rotate(15)))
|
||||
## )
|
||||
## return D
|
||||
##
|
||||
##def getDrawing6():
|
||||
## """This Rotates Text"""
|
||||
## D = Drawing(400, 300, fill=colors.black)
|
||||
##
|
||||
## xform = translate(200,150)
|
||||
## C = (colors.black,colors.red,colors.green,colors.blue,colors.brown,colors.gray, colors.pink,
|
||||
## colors.lavender,colors.lime, colors.mediumblue, colors.magenta, colors.limegreen)
|
||||
##
|
||||
## for i in range(12):
|
||||
## D.add(String(0, 0, ' - - Rotated Text', fill=C[i%len(C)], transform=mmult(xform, rotate(30*i))))
|
||||
##
|
||||
## return D
|
||||
##
|
||||
##def getDrawing7():
|
||||
## """This defines and tests a simple UserNode0 (the trailing zero denotes
|
||||
## an experimental method which is not part of the supported API yet).
|
||||
## Each of the four charts is a subclass of UserNode which generates a random
|
||||
## series when rendered."""
|
||||
##
|
||||
## class MyUserNode(UserNode0):
|
||||
## import whrandom, math
|
||||
##
|
||||
##
|
||||
## def provideNode(self, sender):
|
||||
## """draw a simple chart that changes everytime it's drawn"""
|
||||
## # print "here's a random number %s" % self.whrandom.random()
|
||||
## #print "MyUserNode.provideNode being called by %s" % sender
|
||||
## g = Group()
|
||||
## #g._state = self._state # this is naughty
|
||||
## PingoNode.__init__(g, self._state) # is this less naughty ?
|
||||
## w = 80.0
|
||||
## h = 50.0
|
||||
## g.add(Rect(0,0, w, h, stroke=colors.black))
|
||||
## N = 10.0
|
||||
## x,y = (0,h)
|
||||
## dx = w/N
|
||||
## for ii in range(N):
|
||||
## dy = (h/N) * self.whrandom.random()
|
||||
## g.add(Line(x,y,x+dx, y-dy))
|
||||
## x = x + dx
|
||||
## y = y - dy
|
||||
## return g
|
||||
##
|
||||
## D = Drawing(400,200, fill=colors.white) # AR - same size as others
|
||||
##
|
||||
## D.add(MyUserNode())
|
||||
##
|
||||
## graphcolor= [colors.green, colors.red, colors.brown, colors.purple]
|
||||
## for ii in range(4):
|
||||
## D.add(Group( MyUserNode(stroke=graphcolor[ii], stroke_width=2),
|
||||
## transform=translate(ii*90,0) ))
|
||||
##
|
||||
## #un = MyUserNode()
|
||||
## #print un.provideNode()
|
||||
## return D
|
||||
##
|
||||
##def getDrawing8():
|
||||
## """Test Path operations--lineto, curveTo, etc."""
|
||||
## D = Drawing(400, 200, fill=None, stroke=colors.purple, stroke_width=2)
|
||||
##
|
||||
## xform = translate(200,100)
|
||||
## C = (colors.black,colors.red,colors.green,colors.blue,colors.brown,colors.gray, colors.pink,
|
||||
## colors.lavender,colors.lime, colors.mediumblue, colors.magenta, colors.limegreen)
|
||||
## p = Path(50,50)
|
||||
## p.lineTo(100,100)
|
||||
## p.moveBy(-25,25)
|
||||
## p.curveTo(150,125, 125,125, 200,50)
|
||||
## p.curveTo(175, 75, 175, 98, 62, 87)
|
||||
##
|
||||
##
|
||||
## D.add(p)
|
||||
## D.add(String(10,30, 'Tests of path elements-lines and bezier curves-and text formating'))
|
||||
## D.add(Line(220,150, 220,200, stroke=colors.red))
|
||||
## D.add(String(220,180, "Text should be centered", text_anchor="middle") )
|
||||
##
|
||||
##
|
||||
## return D
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
print __doc__
|
|
@ -1,547 +0,0 @@
|
|||
#! /usr/bin/python2.3
|
||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/testshapes.py
|
||||
|
||||
# testshapes.py - draws shapes onto a PDF canvas.
|
||||
|
||||
"""
|
||||
Execute the script to see some test drawings.
|
||||
|
||||
This contains a number of routines to generate test drawings
|
||||
for reportlab/graphics. For now many of them are contrived,
|
||||
but we will expand them to try and trip up any parser.
|
||||
Feel free to add more.
|
||||
"""
|
||||
|
||||
__version__ = ''' $Id $ '''
|
||||
|
||||
|
||||
import os, sys
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.units import cm
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.pdfbase.pdfmetrics import stringWidth
|
||||
from reportlab.platypus import Flowable
|
||||
from reportlab.graphics.shapes import *
|
||||
from reportlab.graphics.renderPDF import _PDFRenderer
|
||||
import unittest
|
||||
|
||||
_FONTS = ['Times-Roman','Courier','Times-BoldItalic',]
|
||||
|
||||
#########################################################
|
||||
#
|
||||
# Collections of shape drawings.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
|
||||
def getFailedDrawing(funcName):
|
||||
"""Generate a drawing in case something goes really wrong.
|
||||
|
||||
This will create a drawing to be displayed whenever some
|
||||
other drawing could not be executed, because the generating
|
||||
function does something terribly wrong! The box contains
|
||||
an attention triangle, plus some error message.
|
||||
"""
|
||||
|
||||
D = Drawing(400, 200)
|
||||
|
||||
points = [200,170, 140,80, 260,80]
|
||||
D.add(Polygon(points,
|
||||
strokeWidth=0.5*cm,
|
||||
strokeColor=colors.red,
|
||||
fillColor=colors.yellow))
|
||||
|
||||
s = String(200, 40,
|
||||
"Error in generating function '%s'!" % funcName,
|
||||
textAnchor='middle')
|
||||
D.add(s)
|
||||
|
||||
return D
|
||||
|
||||
|
||||
# These are the real drawings to be eye-balled.
|
||||
|
||||
def getDrawing01():
|
||||
"""Hello World, on a rectangular background.
|
||||
|
||||
The rectangle's fillColor is yellow.
|
||||
The string's fillColor is red.
|
||||
"""
|
||||
|
||||
D = Drawing(400, 200)
|
||||
D.add(Rect(50, 50, 300, 100, fillColor=colors.yellow))
|
||||
D.add(String(180,100, 'Hello World', fillColor=colors.red))
|
||||
|
||||
return D
|
||||
|
||||
|
||||
def getDrawing02():
|
||||
"""Various Line shapes.
|
||||
|
||||
The lines are blue and their strokeWidth is 5 mm.
|
||||
One line has a strokeDashArray set to [5, 10, 15].
|
||||
"""
|
||||
|
||||
D = Drawing(400, 200)
|
||||
D.add(Line(50,50, 300,100,
|
||||
strokeColor=colors.blue,
|
||||
strokeWidth=0.5*cm,
|
||||
))
|
||||
D.add(Line(50,100, 300,50,
|
||||
strokeColor=colors.blue,
|
||||
strokeWidth=0.5*cm,
|
||||
strokeDashArray=[5, 10, 15],
|
||||
))
|
||||
|
||||
#x = 1/0 # Comment this to see the actual drawing!
|
||||
|
||||
return D
|
||||
|
||||
|
||||
def getDrawing03():
|
||||
"""Text strings in various sizes and different fonts.
|
||||
|
||||
Font size increases from 12 to 36 and from bottom left
|
||||
to upper right corner. The first ones should be in
|
||||
Times-Roman. Finally, a solitary Courier string at
|
||||
the top right corner.
|
||||
"""
|
||||
|
||||
D = Drawing(400, 200)
|
||||
for size in range(12, 36, 4):
|
||||
D.add(String(10+size*2,
|
||||
10+size*2,
|
||||
'Hello World',
|
||||
fontName=_FONTS[0],
|
||||
fontSize=size))
|
||||
|
||||
D.add(String(150, 150,
|
||||
'Hello World',
|
||||
fontName=_FONTS[1],
|
||||
fontSize=36))
|
||||
return D
|
||||
|
||||
|
||||
def getDrawing04():
|
||||
"""Text strings in various colours.
|
||||
|
||||
Colours are blue, yellow and red from bottom left
|
||||
to upper right.
|
||||
"""
|
||||
|
||||
D = Drawing(400, 200)
|
||||
i = 0
|
||||
for color in (colors.blue, colors.yellow, colors.red):
|
||||
D.add(String(50+i*30, 50+i*30,
|
||||
'Hello World', fillColor=color))
|
||||
i = i + 1
|
||||
|
||||
return D
|
||||
|
||||
|
||||
def getDrawing05():
|
||||
"""Text strings with various anchors (alignments).
|
||||
|
||||
Text alignment conforms to the anchors in the left column.
|
||||
"""
|
||||
|
||||
D = Drawing(400, 200)
|
||||
|
||||
lineX = 250
|
||||
D.add(Line(lineX,10, lineX,190, strokeColor=colors.gray))
|
||||
|
||||
y = 130
|
||||
for anchor in ('start', 'middle', 'end'):
|
||||
D.add(String(lineX, y, 'Hello World', textAnchor=anchor))
|
||||
D.add(String(50, y, anchor + ':'))
|
||||
y = y - 30
|
||||
|
||||
return D
|
||||
|
||||
|
||||
def getDrawing06():
|
||||
"""This demonstrates all the basic shapes at once.
|
||||
|
||||
There are no groups or references.
|
||||
Each solid shape should have a purple fill.
|
||||
"""
|
||||
|
||||
purple = colors.purple
|
||||
purple = colors.green
|
||||
|
||||
D = Drawing(400, 200) #, fillColor=purple)
|
||||
|
||||
D.add(Line(10,10, 390,190))
|
||||
|
||||
D.add(Circle(100,100,20, fillColor=purple))
|
||||
D.add(Circle(200,100,40, fillColor=purple))
|
||||
D.add(Circle(300,100,30, fillColor=purple))
|
||||
|
||||
D.add(Wedge(330,100,40, -10,40, fillColor=purple))
|
||||
|
||||
D.add(PolyLine([120,10, 130,20, 140,10, 150,20, 160,10,
|
||||
170,20, 180,10, 190,20, 200,10], fillColor=purple))
|
||||
|
||||
D.add(Polygon([300,20, 350,20, 390,80, 300,75, 330,40], fillColor=purple))
|
||||
|
||||
D.add(Ellipse(50,150, 40, 20, fillColor=purple))
|
||||
|
||||
D.add(Rect(120,150, 60,30,
|
||||
strokeWidth=10,
|
||||
strokeColor=colors.yellow,
|
||||
fillColor=purple)) #square corners
|
||||
|
||||
D.add(Rect(220, 150, 60, 30, 10, 10, fillColor=purple)) #round corners
|
||||
|
||||
D.add(String(10,50, 'Basic Shapes', fillColor=colors.black))
|
||||
|
||||
return D
|
||||
|
||||
def getDrawing07():
|
||||
"""This tests the ability to translate and rotate groups. The first set of axes should be
|
||||
near the bottom left of the drawing. The second should be rotated counterclockwise
|
||||
by 15 degrees. The third should be rotated by 30 degrees."""
|
||||
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)
|
||||
|
||||
return D
|
||||
|
||||
|
||||
def getDrawing08():
|
||||
"""This tests the ability to scale coordinates. The bottom left set of axes should be
|
||||
near the bottom left of the drawing. The bottom right should be stretched vertically
|
||||
by a factor of 2. The top left one should be stretched horizontally by a factor of 2.
|
||||
The top right should have the vertical axiss leaning over to the right by 30 degrees."""
|
||||
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.scale(1,2)
|
||||
D.add(secondAxisGroup)
|
||||
|
||||
thirdAxisGroup = Group(Axis)
|
||||
thirdAxisGroup.translate(10,125)
|
||||
thirdAxisGroup.scale(2,1)
|
||||
D.add(thirdAxisGroup)
|
||||
|
||||
fourthAxisGroup = Group(Axis)
|
||||
fourthAxisGroup.translate(250,125)
|
||||
fourthAxisGroup.skew(30,0)
|
||||
D.add(fourthAxisGroup)
|
||||
|
||||
|
||||
return D
|
||||
|
||||
def getDrawing09():
|
||||
"""This tests rotated strings
|
||||
|
||||
Some renderers will have a separate mechanism for font drawing. This test
|
||||
just makes sure strings get transformed the same way as regular graphics."""
|
||||
D = Drawing(400, 200)
|
||||
|
||||
fontName = _FONTS[0]
|
||||
fontSize = 12
|
||||
text = "I should be totally horizontal and enclosed in a box"
|
||||
textWidth = stringWidth(text, fontName, fontSize)
|
||||
|
||||
|
||||
g1 = Group(
|
||||
String(20, 20, text, fontName=fontName, fontSize = fontSize),
|
||||
Rect(18, 18, textWidth + 4, fontSize + 4, fillColor=None)
|
||||
)
|
||||
D.add(g1)
|
||||
|
||||
text = "I should slope up by 15 degrees, so my right end is higher than my left"
|
||||
textWidth = stringWidth(text, fontName, fontSize)
|
||||
g2 = Group(
|
||||
String(20, 20, text, fontName=fontName, fontSize = fontSize),
|
||||
Rect(18, 18, textWidth + 4, fontSize + 4, fillColor=None)
|
||||
)
|
||||
g2.translate(0, 50)
|
||||
g2.rotate(15)
|
||||
D.add(g2)
|
||||
|
||||
return D
|
||||
|
||||
def getDrawing10():
|
||||
"""This tests nested groups with multiple levels of coordinate transformation.
|
||||
Each box should be staggered up and to the right, moving by 25 points each time."""
|
||||
D = Drawing(400, 200)
|
||||
|
||||
fontName = _FONTS[0]
|
||||
fontSize = 12
|
||||
|
||||
g1 = Group(
|
||||
Rect(0, 0, 100, 20, fillColor=colors.yellow),
|
||||
String(5, 5, 'Text in the box', fontName=fontName, fontSize = fontSize)
|
||||
)
|
||||
D.add(g1)
|
||||
|
||||
g2 = Group(g1, transform = translate(25,25))
|
||||
D.add(g2)
|
||||
|
||||
g3 = Group(g2, transform = translate(25,25))
|
||||
D.add(g3)
|
||||
|
||||
g4 = Group(g3, transform = translate(25,25))
|
||||
D.add(g4)
|
||||
|
||||
|
||||
return D
|
||||
|
||||
from widgets.signsandsymbols import SmileyFace
|
||||
def getDrawing11():
|
||||
'''test of anchoring'''
|
||||
def makeSmiley(x, y, size, color):
|
||||
"Make a smiley data item representation."
|
||||
d = size
|
||||
s = SmileyFace()
|
||||
s.fillColor = color
|
||||
s.x = x-d
|
||||
s.y = y-d
|
||||
s.size = d*2
|
||||
return s
|
||||
|
||||
D = Drawing(400, 200) #, fillColor=colors.purple)
|
||||
g = Group(transform=(1,0,0,1,0,0))
|
||||
g.add(makeSmiley(100,100,10,colors.red))
|
||||
g.add(Line(90,100,110,100,strokeColor=colors.green))
|
||||
g.add(Line(100,90,100,110,strokeColor=colors.green))
|
||||
D.add(g)
|
||||
g = Group(transform=(2,0,0,2,100,-100))
|
||||
g.add(makeSmiley(100,100,10,colors.blue))
|
||||
g.add(Line(90,100,110,100,strokeColor=colors.green))
|
||||
g.add(Line(100,90,100,110,strokeColor=colors.green))
|
||||
D.add(g)
|
||||
g = Group(transform=(2,0,0,2,0,0))
|
||||
return D
|
||||
|
||||
|
||||
def getDrawing12():
|
||||
"""Text strings in a non-standard font.
|
||||
All that is required is to place the .afm and .pfb files
|
||||
on the font patch given in rl_config.py,
|
||||
for example in reportlab/lib/fonts/.
|
||||
"""
|
||||
faceName = "Wargames-Regular"
|
||||
D = Drawing(400, 200)
|
||||
for size in range(12, 36, 4):
|
||||
D.add(String(10+size*2,
|
||||
10+size*2,
|
||||
'Hello World',
|
||||
fontName=faceName,
|
||||
fontSize=size))
|
||||
return D
|
||||
|
||||
def getDrawing13():
|
||||
'Test Various TTF Fonts'
|
||||
from reportlab.pdfbase import pdfmetrics, ttfonts
|
||||
pdfmetrics.registerFont(ttfonts.TTFont("LuxiSerif", "luxiserif.ttf"))
|
||||
pdfmetrics.registerFont(ttfonts.TTFont("Rina", "rina.ttf"))
|
||||
_FONTS[1] = 'LuxiSerif'
|
||||
_FONTS[2] = 'Rina'
|
||||
F = ['Times-Roman','LuxiSerif', 'Rina']
|
||||
if sys.platform=='win32':
|
||||
for name, ttf in [('Adventurer Light SF','Advlit.ttf'),('ArialMS','ARIAL.TTF'),
|
||||
('Book Antiqua','BKANT.TTF'),
|
||||
('Century Gothic','GOTHIC.TTF'),
|
||||
('Comic Sans MS', 'COMIC.TTF'),
|
||||
('Elementary Heavy SF Bold','Vwagh.ttf'),
|
||||
('Firenze SF','flot.ttf'),
|
||||
('Garamond','GARA.TTF'),
|
||||
('Jagger','Rols.ttf'),
|
||||
('Monotype Corsiva','MTCORSVA.TTF'),
|
||||
('Seabird SF','seag.ttf'),
|
||||
('Tahoma','TAHOMA.TTF'),
|
||||
('VerdanaMS','VERDANA.TTF'),
|
||||
]:
|
||||
for D in ('c:\WINNT','c:\Windows'):
|
||||
fn = os.path.join(D,'Fonts',ttf)
|
||||
if os.path.isfile(fn):
|
||||
try:
|
||||
f = ttfonts.TTFont(name, fn)
|
||||
pdfmetrics.registerFont(f)
|
||||
F.append(name)
|
||||
except:
|
||||
pass
|
||||
|
||||
def drawit(F,w=400,h=200,fontSize=12,slack=2,gap=5):
|
||||
D = Drawing(w,h)
|
||||
th = 2*gap + fontSize*1.2
|
||||
gh = gap + .2*fontSize
|
||||
y = h
|
||||
maxx = 0
|
||||
for fontName in F:
|
||||
y -= th
|
||||
text = fontName+": I should be totally horizontal and enclosed in a box"
|
||||
textWidth = stringWidth(text, fontName, fontSize)
|
||||
maxx = max(maxx,textWidth+20)
|
||||
D.add(
|
||||
Group(Rect(8, y-gh, textWidth + 4, th, strokeColor=colors.red, strokeWidth=.5, fillColor=colors.lightgrey),
|
||||
String(10, y, text, fontName=fontName, fontSize = fontSize)))
|
||||
y -= 5
|
||||
return maxx, h-y+gap, D
|
||||
maxx, maxy, D = drawit(F)
|
||||
if maxx>400 or maxy>200: _,_,D = drawit(F,maxx,maxy)
|
||||
return D
|
||||
|
||||
def getAllFunctionDrawingNames(doTTF=1):
|
||||
"Get a list of drawing function names from somewhere."
|
||||
|
||||
funcNames = []
|
||||
|
||||
# Here we get the names from the global name space.
|
||||
symbols = globals().keys()
|
||||
symbols.sort()
|
||||
for funcName in symbols:
|
||||
if funcName[0:10] == 'getDrawing':
|
||||
if doTTF or funcName!='getDrawing13':
|
||||
funcNames.append(funcName)
|
||||
|
||||
return funcNames
|
||||
|
||||
def _evalFuncDrawing(name, D, l=None, g=None):
|
||||
try:
|
||||
d = eval(name + '()', g or globals(), l or locals())
|
||||
except:
|
||||
d = getFailedDrawing(name)
|
||||
D.append((d, eval(name + '.__doc__'), name[3:]))
|
||||
|
||||
def getAllTestDrawings(doTTF=1):
|
||||
D = []
|
||||
for f in getAllFunctionDrawingNames(doTTF=doTTF):
|
||||
_evalFuncDrawing(f,D)
|
||||
return D
|
||||
|
||||
def writePDF(drawings):
|
||||
"Create and save a PDF file containing some drawings."
|
||||
|
||||
pdfPath = os.path.splitext(sys.argv[0])[0] + '.pdf'
|
||||
c = Canvas(pdfPath)
|
||||
c.setFont(_FONTS[0], 32)
|
||||
c.drawString(80, 750, 'ReportLab Graphics-Shapes Test')
|
||||
|
||||
# Print drawings in a loop, with their doc strings.
|
||||
c.setFont(_FONTS[0], 12)
|
||||
y = 740
|
||||
i = 1
|
||||
for (drawing, docstring, funcname) in drawings:
|
||||
if y < 300: # Allows 5-6 lines of text.
|
||||
c.showPage()
|
||||
y = 740
|
||||
# Draw a title.
|
||||
y = y - 30
|
||||
c.setFont(_FONTS[2],12)
|
||||
c.drawString(80, y, '%s (#%d)' % (funcname, i))
|
||||
c.setFont(_FONTS[0],12)
|
||||
y = y - 14
|
||||
textObj = c.beginText(80, y)
|
||||
textObj.textLines(docstring)
|
||||
c.drawText(textObj)
|
||||
y = textObj.getY()
|
||||
y = y - drawing.height
|
||||
drawing.drawOn(c, 80, y)
|
||||
i = i + 1
|
||||
|
||||
c.save()
|
||||
print 'wrote %s ' % pdfPath
|
||||
|
||||
|
||||
class ShapesTestCase(unittest.TestCase):
|
||||
"Test generating all kinds of shapes."
|
||||
|
||||
def setUp(self):
|
||||
"Prepare some things before the tests start."
|
||||
|
||||
self.funcNames = getAllFunctionDrawingNames()
|
||||
self.drawings = []
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
"Do what has to be done after the tests are over."
|
||||
|
||||
writePDF(self.drawings)
|
||||
|
||||
|
||||
# This should always succeed. If each drawing would be
|
||||
# wrapped in a dedicated test method like this one, it
|
||||
# would be possible to have a count for wrong tests
|
||||
# as well... Something like this is left for later...
|
||||
def testAllDrawings(self):
|
||||
"Make a list of drawings."
|
||||
|
||||
for f in self.funcNames:
|
||||
if f[0:10] == 'getDrawing':
|
||||
# Make an instance and get its doc string.
|
||||
# If that fails, use a default error drawing.
|
||||
_evalFuncDrawing(f,self.drawings)
|
||||
|
||||
|
||||
def makeSuite():
|
||||
"Make a test suite for unit testing."
|
||||
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(ShapesTestCase('testAllDrawings'))
|
||||
return suite
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.TextTestRunner().run(makeSuite())
|
|
@ -1,490 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/widgetbase.py
|
||||
__version__=''' $Id$ '''
|
||||
import string
|
||||
|
||||
from reportlab.graphics import shapes
|
||||
from reportlab import rl_config
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.validators import *
|
||||
from reportlab.lib.attrmap import *
|
||||
|
||||
|
||||
class PropHolder:
|
||||
'''Base for property holders'''
|
||||
|
||||
_attrMap = None
|
||||
|
||||
def verify(self):
|
||||
"""If the _attrMap attribute is not None, this
|
||||
checks all expected attributes are present; no
|
||||
unwanted attributes are present; and (if a
|
||||
checking function is found) checks each
|
||||
attribute has a valid value. Either succeeds
|
||||
or raises an informative exception.
|
||||
"""
|
||||
|
||||
if self._attrMap is not None:
|
||||
for key in self.__dict__.keys():
|
||||
if key[0] <> '_':
|
||||
msg = "Unexpected attribute %s found in %s" % (key, self)
|
||||
assert self._attrMap.has_key(key), msg
|
||||
for (attr, metavalue) in self._attrMap.items():
|
||||
msg = "Missing attribute %s from %s" % (attr, self)
|
||||
assert hasattr(self, attr), msg
|
||||
value = getattr(self, attr)
|
||||
args = (value, attr, self.__class__.__name__)
|
||||
assert metavalue.validate(value), "Invalid value %s for attribute %s in class %s" % args
|
||||
|
||||
if rl_config.shapeChecking:
|
||||
"""This adds the ability to check every attribute assignment
|
||||
as it is made. It slows down shapes but is a big help when
|
||||
developing. It does not get defined if rl_config.shapeChecking = 0.
|
||||
"""
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
"""By default we verify. This could be off
|
||||
in some parallel base classes."""
|
||||
validateSetattr(self,name,value)
|
||||
|
||||
|
||||
def getProperties(self,recur=1):
|
||||
"""Returns a list of all properties which can be edited and
|
||||
which are not marked as private. This may include 'child
|
||||
widgets' or 'primitive shapes'. You are free to override
|
||||
this and provide alternative implementations; the default
|
||||
one simply returns everything without a leading underscore.
|
||||
"""
|
||||
|
||||
from reportlab.lib.validators import isValidChild
|
||||
|
||||
# TODO when we need it, but not before -
|
||||
# expose sequence contents?
|
||||
|
||||
props = {}
|
||||
for name in self.__dict__.keys():
|
||||
if name[0:1] <> '_':
|
||||
component = getattr(self, name)
|
||||
|
||||
if recur and isValidChild(component):
|
||||
# child object, get its properties too
|
||||
childProps = component.getProperties(recur=recur)
|
||||
for (childKey, childValue) in childProps.items():
|
||||
#key might be something indexed like '[2].fillColor'
|
||||
#or simple like 'fillColor'; in the former case we
|
||||
#don't need a '.' between me and my child.
|
||||
if childKey[0] == '[':
|
||||
props['%s%s' % (name, childKey)] = childValue
|
||||
else:
|
||||
props['%s.%s' % (name, childKey)] = childValue
|
||||
else:
|
||||
props[name] = component
|
||||
|
||||
return props
|
||||
|
||||
|
||||
def setProperties(self, propDict):
|
||||
"""Permits bulk setting of properties. These may include
|
||||
child objects e.g. "chart.legend.width = 200".
|
||||
|
||||
All assignments will be validated by the object as if they
|
||||
were set individually in python code.
|
||||
|
||||
All properties of a top-level object are guaranteed to be
|
||||
set before any of the children, which may be helpful to
|
||||
widget designers.
|
||||
"""
|
||||
|
||||
childPropDicts = {}
|
||||
for (name, value) in propDict.items():
|
||||
parts = string.split(name, '.', 1)
|
||||
if len(parts) == 1:
|
||||
#simple attribute, set it now
|
||||
setattr(self, name, value)
|
||||
else:
|
||||
(childName, remains) = parts
|
||||
try:
|
||||
childPropDicts[childName][remains] = value
|
||||
except KeyError:
|
||||
childPropDicts[childName] = {remains: value}
|
||||
|
||||
# now assign to children
|
||||
for (childName, childPropDict) in childPropDicts.items():
|
||||
child = getattr(self, childName)
|
||||
child.setProperties(childPropDict)
|
||||
|
||||
|
||||
def dumpProperties(self, prefix=""):
|
||||
"""Convenience. Lists them on standard output. You
|
||||
may provide a prefix - mostly helps to generate code
|
||||
samples for documentation.
|
||||
"""
|
||||
|
||||
propList = self.getProperties().items()
|
||||
propList.sort()
|
||||
if prefix:
|
||||
prefix = prefix + '.'
|
||||
for (name, value) in propList:
|
||||
print '%s%s = %s' % (prefix, name, value)
|
||||
|
||||
|
||||
class Widget(PropHolder, shapes.UserNode):
|
||||
"""Base for all user-defined widgets. Keep as simple as possible. Does
|
||||
not inherit from Shape so that we can rewrite shapes without breaking
|
||||
widgets and vice versa."""
|
||||
|
||||
def _setKeywords(self,**kw):
|
||||
for k,v in kw.items():
|
||||
if not self.__dict__.has_key(k):
|
||||
setattr(self,k,v)
|
||||
|
||||
def draw(self):
|
||||
msg = "draw() must be implemented for each Widget!"
|
||||
raise shapes.NotImplementedError, msg
|
||||
|
||||
def demo(self):
|
||||
msg = "demo() must be implemented for each Widget!"
|
||||
raise shapes.NotImplementedError, msg
|
||||
|
||||
def provideNode(self):
|
||||
return self.draw()
|
||||
|
||||
def getBounds(self):
|
||||
"Return outer boundary as x1,y1,x2,y2. Can be overridden for efficiency"
|
||||
return self.draw().getBounds()
|
||||
|
||||
_ItemWrapper={}
|
||||
|
||||
class TypedPropertyCollection(PropHolder):
|
||||
"""A container with properties for objects of the same kind.
|
||||
|
||||
This makes it easy to create lists of objects. You initialize
|
||||
it with a class of what it is to contain, and that is all you
|
||||
can add to it. You can assign properties to the collection
|
||||
as a whole, or to a numeric index within it; if so it creates
|
||||
a new child object to hold that data.
|
||||
|
||||
So:
|
||||
wedges = TypedPropertyCollection(WedgeProperties)
|
||||
wedges.strokeWidth = 2 # applies to all
|
||||
wedges.strokeColor = colors.red # applies to all
|
||||
wedges[3].strokeColor = colors.blue # only to one
|
||||
|
||||
The last line should be taken as a prescription of how to
|
||||
create wedge no. 3 if one is needed; no error is raised if
|
||||
there are only two data points.
|
||||
"""
|
||||
|
||||
def __init__(self, exampleClass):
|
||||
#give it same validation rules as what it holds
|
||||
self.__dict__['_value'] = exampleClass()
|
||||
self.__dict__['_children'] = {}
|
||||
|
||||
def __getitem__(self, index):
|
||||
try:
|
||||
return self._children[index]
|
||||
except KeyError:
|
||||
Klass = self._value.__class__
|
||||
if _ItemWrapper.has_key(Klass):
|
||||
WKlass = _ItemWrapper[Klass]
|
||||
else:
|
||||
class WKlass(Klass):
|
||||
def __getattr__(self,name):
|
||||
try:
|
||||
return self.__class__.__bases__[0].__getattr__(self,name)
|
||||
except:
|
||||
if self._index and self._parent._children.has_key(self._index):
|
||||
if self._parent._children[self._index].__dict__.has_key(name):
|
||||
return getattr(self._parent._children[self._index],name)
|
||||
return getattr(self._parent,name)
|
||||
_ItemWrapper[Klass] = WKlass
|
||||
|
||||
child = WKlass()
|
||||
child._parent = self
|
||||
if type(index) in (type(()),type([])):
|
||||
index = tuple(index)
|
||||
if len(index)>1:
|
||||
child._index = tuple(index[:-1])
|
||||
else:
|
||||
child._index = None
|
||||
else:
|
||||
child._index = None
|
||||
for i in filter(lambda x,K=child.__dict__.keys(): x in K,child._attrMap.keys()):
|
||||
del child.__dict__[i]
|
||||
|
||||
self._children[index] = child
|
||||
return child
|
||||
|
||||
def has_key(self,key):
|
||||
if type(key) in (type(()),type([])): key = tuple(key)
|
||||
return self._children.has_key(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
msg = "This collection can only hold objects of type %s" % self._value.__class__.__name__
|
||||
assert isinstance(value, self._value.__class__), msg
|
||||
|
||||
def __len__(self):
|
||||
return len(self._children.keys())
|
||||
|
||||
def getProperties(self,recur=1):
|
||||
# return any children which are defined and whatever
|
||||
# differs from the parent
|
||||
props = {}
|
||||
|
||||
for (key, value) in self._value.getProperties(recur=recur).items():
|
||||
props['%s' % key] = value
|
||||
|
||||
for idx in self._children.keys():
|
||||
childProps = self._children[idx].getProperties(recur=recur)
|
||||
for (key, value) in childProps.items():
|
||||
if not hasattr(self,key) or getattr(self, key)<>value:
|
||||
newKey = '[%s].%s' % (idx, key)
|
||||
props[newKey] = value
|
||||
return props
|
||||
|
||||
def setVector(self,**kw):
|
||||
for name, value in kw.items():
|
||||
for i in xrange(len(value)):
|
||||
setattr(self[i],name,value[i])
|
||||
|
||||
def __getattr__(self,name):
|
||||
return getattr(self._value,name)
|
||||
|
||||
def __setattr__(self,name,value):
|
||||
return setattr(self._value,name,value)
|
||||
|
||||
## No longer needed!
|
||||
class StyleProperties(PropHolder):
|
||||
"""A container class for attributes used in charts and legends.
|
||||
|
||||
Attributes contained can be those for any graphical element
|
||||
(shape?) in the ReportLab graphics package. The idea for this
|
||||
container class is to be useful in combination with legends
|
||||
and/or the individual appearance of data series in charts.
|
||||
|
||||
A legend could be as simple as a wrapper around a list of style
|
||||
properties, where the 'desc' attribute contains a descriptive
|
||||
string and the rest could be used by the legend e.g. to draw
|
||||
something like a color swatch. The graphical presentation of
|
||||
the legend would be its own business, though.
|
||||
|
||||
A chart could be inspecting a legend or, more directly, a list
|
||||
of style properties to pick individual attributes that it knows
|
||||
about in order to render a particular row of the data. A bar
|
||||
chart e.g. could simply use 'strokeColor' and 'fillColor' for
|
||||
drawing the bars while a line chart could also use additional
|
||||
ones like strokeWidth.
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(
|
||||
strokeWidth = AttrMapValue(isNumber),
|
||||
strokeLineCap = AttrMapValue(isNumber),
|
||||
strokeLineJoin = AttrMapValue(isNumber),
|
||||
strokeMiterLimit = AttrMapValue(None),
|
||||
strokeDashArray = AttrMapValue(isListOfNumbersOrNone),
|
||||
strokeOpacity = AttrMapValue(isNumber),
|
||||
strokeColor = AttrMapValue(isColorOrNone),
|
||||
fillColor = AttrMapValue(isColorOrNone),
|
||||
desc = AttrMapValue(isString),
|
||||
)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"Initialize with attributes if any."
|
||||
|
||||
for k, v in kwargs.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
"Verify attribute name and value, before setting it."
|
||||
validateSetattr(self,name,value)
|
||||
|
||||
|
||||
class TwoCircles(Widget):
|
||||
def __init__(self):
|
||||
self.leftCircle = shapes.Circle(100,100,20, fillColor=colors.red)
|
||||
self.rightCircle = shapes.Circle(300,100,20, fillColor=colors.red)
|
||||
|
||||
def draw(self):
|
||||
return shapes.Group(self.leftCircle, self.rightCircle)
|
||||
|
||||
|
||||
class Face(Widget):
|
||||
"""This draws a face with two eyes.
|
||||
|
||||
It exposes a couple of properties
|
||||
to configure itself and hides all other details.
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber),
|
||||
y = AttrMapValue(isNumber),
|
||||
size = AttrMapValue(isNumber),
|
||||
skinColor = AttrMapValue(isColorOrNone),
|
||||
eyeColor = AttrMapValue(isColorOrNone),
|
||||
mood = AttrMapValue(OneOf('happy','sad','ok')),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.x = 10
|
||||
self.y = 10
|
||||
self.size = 80
|
||||
self.skinColor = None
|
||||
self.eyeColor = colors.blue
|
||||
self.mood = 'happy'
|
||||
|
||||
def demo(self):
|
||||
pass
|
||||
|
||||
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))
|
||||
|
||||
# left eye
|
||||
g.add(shapes.Circle(s * 0.35, s * 0.65, s * 0.1, fillColor=colors.white))
|
||||
g.add(shapes.Circle(s * 0.35, s * 0.65, s * 0.05, fillColor=self.eyeColor))
|
||||
|
||||
# right eye
|
||||
g.add(shapes.Circle(s * 0.65, s * 0.65, s * 0.1, fillColor=colors.white))
|
||||
g.add(shapes.Circle(s * 0.65, s * 0.65, s * 0.05, fillColor=self.eyeColor))
|
||||
|
||||
# nose
|
||||
g.add(shapes.Polygon(
|
||||
points=[s * 0.5, s * 0.6, s * 0.4, s * 0.3, s * 0.6, s * 0.3],
|
||||
fillColor=None))
|
||||
|
||||
# mouth
|
||||
if self.mood == 'happy':
|
||||
offset = -0.05
|
||||
elif self.mood == 'sad':
|
||||
offset = +0.05
|
||||
else:
|
||||
offset = 0
|
||||
|
||||
g.add(shapes.Polygon(
|
||||
points = [
|
||||
s * 0.3, s * 0.2, #left of mouth
|
||||
s * 0.7, s * 0.2, #right of mouth
|
||||
s * 0.6, s * (0.2 + offset), # the bit going up or down
|
||||
s * 0.4, s * (0.2 + offset) # the bit going up or down
|
||||
],
|
||||
fillColor = colors.pink,
|
||||
strokeColor = colors.red,
|
||||
strokeWidth = s * 0.03
|
||||
))
|
||||
|
||||
return g
|
||||
|
||||
|
||||
class TwoFaces(Widget):
|
||||
def __init__(self):
|
||||
self.faceOne = Face()
|
||||
self.faceOne.mood = "happy"
|
||||
self.faceTwo = Face()
|
||||
self.faceTwo.x = 100
|
||||
self.faceTwo.mood = "sad"
|
||||
|
||||
def draw(self):
|
||||
"""Just return a group"""
|
||||
return shapes.Group(self.faceOne, self.faceTwo)
|
||||
|
||||
def demo(self):
|
||||
"""The default case already looks good enough,
|
||||
no implementation needed here"""
|
||||
pass
|
||||
|
||||
class Sizer(Widget):
|
||||
"Container to show size of all enclosed objects"
|
||||
|
||||
_attrMap = AttrMap(BASE=shapes.SolidShape,
|
||||
contents = AttrMapValue(isListOfShapes,desc="Contained drawable elements"),
|
||||
)
|
||||
def __init__(self, *elements):
|
||||
self.contents = []
|
||||
self.fillColor = colors.cyan
|
||||
self.strokeColor = colors.magenta
|
||||
|
||||
for elem in elements:
|
||||
self.add(elem)
|
||||
|
||||
def _addNamedNode(self,name,node):
|
||||
'if name is not None add an attribute pointing to node and add to the attrMap'
|
||||
if name:
|
||||
if name not in self._attrMap.keys():
|
||||
self._attrMap[name] = AttrMapValue(isValidChild)
|
||||
setattr(self, name, node)
|
||||
|
||||
def add(self, node, name=None):
|
||||
"""Appends non-None child node to the 'contents' attribute. In addition,
|
||||
if a name is provided, it is subsequently accessible by name
|
||||
"""
|
||||
# propagates properties down
|
||||
if node is not None:
|
||||
assert isValidChild(node), "Can only add Shape or UserNode objects to a Group"
|
||||
self.contents.append(node)
|
||||
self._addNamedNode(name,node)
|
||||
|
||||
def getBounds(self):
|
||||
# get bounds of each object
|
||||
if self.contents:
|
||||
b = []
|
||||
for elem in self.contents:
|
||||
b.append(elem.getBounds())
|
||||
return shapes.getRectsBounds(b)
|
||||
else:
|
||||
return (0,0,0,0)
|
||||
|
||||
def draw(self):
|
||||
g = shapes.Group()
|
||||
(x1, y1, x2, y2) = self.getBounds()
|
||||
r = shapes.Rect(
|
||||
x = x1,
|
||||
y = y1,
|
||||
width = x2-x1,
|
||||
height = y2-y1,
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = self.strokeColor
|
||||
)
|
||||
g.add(r)
|
||||
for elem in self.contents:
|
||||
g.add(elem)
|
||||
return g
|
||||
|
||||
def test():
|
||||
from reportlab.graphics.charts.piecharts import WedgeProperties
|
||||
wedges = TypedPropertyCollection(WedgeProperties)
|
||||
wedges.fillColor = colors.red
|
||||
wedges.setVector(fillColor=(colors.blue,colors.green,colors.white))
|
||||
print len(_ItemWrapper)
|
||||
|
||||
d = shapes.Drawing(400, 200)
|
||||
tc = TwoCircles()
|
||||
d.add(tc)
|
||||
import renderPDF
|
||||
renderPDF.drawToFile(d, 'sample_widget.pdf', 'A Sample Widget')
|
||||
print 'saved sample_widget.pdf'
|
||||
|
||||
d = shapes.Drawing(400, 200)
|
||||
f = Face()
|
||||
f.skinColor = colors.yellow
|
||||
f.mood = "sad"
|
||||
d.add(f, name='theFace')
|
||||
print 'drawing 1 properties:'
|
||||
d.dumpProperties()
|
||||
renderPDF.drawToFile(d, 'face.pdf', 'A Sample Widget')
|
||||
print 'saved face.pdf'
|
||||
|
||||
d2 = d.expandUserNodes()
|
||||
renderPDF.drawToFile(d2, 'face_copy.pdf', 'An expanded drawing')
|
||||
print 'saved face_copy.pdf'
|
||||
print 'drawing 2 properties:'
|
||||
d2.dumpProperties()
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
|
@ -1,4 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/widgets/__init__.py
|
||||
__version__=''' $Id$ '''
|
|
@ -1,303 +0,0 @@
|
|||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/widgets/eventcal.py
|
||||
# Event Calendar widget
|
||||
# author: Andy Robinson
|
||||
"""This file is a
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.validators import *
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.graphics.shapes import Line, Rect, Polygon, Drawing, Group, String, Circle, Wedge
|
||||
from reportlab.graphics.charts.textlabels import Label
|
||||
from reportlab.graphics.widgetbase import Widget
|
||||
from reportlab.graphics import renderPDF
|
||||
|
||||
|
||||
|
||||
|
||||
class EventCalendar(Widget):
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.width = 300
|
||||
self.height = 150
|
||||
self.timeColWidth = None # if declared, use it; otherwise auto-size.
|
||||
self.trackRowHeight = 20
|
||||
self.data = [] # list of Event objects
|
||||
self.trackNames = None
|
||||
|
||||
self.startTime = None #displays ALL data on day if not set
|
||||
self.endTime = None # displays ALL data on day if not set
|
||||
self.day = 0
|
||||
|
||||
|
||||
# we will keep any internal geometry variables
|
||||
# here. These are computed by computeSize(),
|
||||
# which is the first thing done when drawing.
|
||||
self._talksVisible = [] # subset of data which will get plotted, cache
|
||||
self._startTime = None
|
||||
self._endTime = None
|
||||
self._trackCount = 0
|
||||
self._colWidths = []
|
||||
self._colLeftEdges = [] # left edge of each column
|
||||
|
||||
def computeSize(self):
|
||||
"Called at start of draw. Sets various column widths"
|
||||
self._talksVisible = self.getRelevantTalks(self.data)
|
||||
self._trackCount = len(self.getAllTracks())
|
||||
self.computeStartAndEndTimes()
|
||||
self._colLeftEdges = [self.x]
|
||||
if self.timeColWidth is None:
|
||||
w = self.width / (1 + self._trackCount)
|
||||
self._colWidths = [w] * (1+ self._trackCount)
|
||||
for i in range(self._trackCount):
|
||||
self._colLeftEdges.append(self._colLeftEdges[-1] + w)
|
||||
else:
|
||||
self._colWidths = [self.timeColWidth]
|
||||
w = (self.width - self.timeColWidth) / self._trackCount
|
||||
for i in range(self._trackCount):
|
||||
self._colWidths.append(w)
|
||||
self._colLeftEdges.append(self._colLeftEdges[-1] + w)
|
||||
|
||||
|
||||
|
||||
def computeStartAndEndTimes(self):
|
||||
"Work out first and last times to display"
|
||||
if self.startTime:
|
||||
self._startTime = self.startTime
|
||||
else:
|
||||
for (title, speaker, trackId, day, start, duration) in self._talksVisible:
|
||||
|
||||
if self._startTime is None: #first one
|
||||
self._startTime = start
|
||||
else:
|
||||
if start < self._startTime:
|
||||
self._startTime = start
|
||||
|
||||
if self.endTime:
|
||||
self._endTime = self.endTime
|
||||
else:
|
||||
for (title, speaker, trackId, day, start, duration) in self._talksVisible:
|
||||
if self._endTime is None: #first one
|
||||
self._endTime = start + duration
|
||||
else:
|
||||
if start + duration > self._endTime:
|
||||
self._endTime = start + duration
|
||||
|
||||
|
||||
|
||||
|
||||
def getAllTracks(self):
|
||||
tracks = []
|
||||
for (title, speaker, trackId, day, hours, duration) in self.data:
|
||||
if trackId is not None:
|
||||
if trackId not in tracks:
|
||||
tracks.append(trackId)
|
||||
tracks.sort()
|
||||
return tracks
|
||||
|
||||
def getRelevantTalks(self, talkList):
|
||||
"Scans for tracks actually used"
|
||||
used = []
|
||||
for talk in talkList:
|
||||
(title, speaker, trackId, day, hours, duration) = talk
|
||||
assert trackId <> 0, "trackId must be None or 1,2,3... zero not allowed!"
|
||||
if day == self.day:
|
||||
if (((self.startTime is None) or ((hours + duration) >= self.startTime))
|
||||
and ((self.endTime is None) or (hours <= self.endTime))):
|
||||
used.append(talk)
|
||||
return used
|
||||
|
||||
def scaleTime(self, theTime):
|
||||
"Return y-value corresponding to times given"
|
||||
axisHeight = self.height - self.trackRowHeight
|
||||
# compute fraction between 0 and 1, 0 is at start of period
|
||||
proportionUp = ((theTime - self._startTime) / (self._endTime - self._startTime))
|
||||
y = self.y + axisHeight - (axisHeight * proportionUp)
|
||||
return y
|
||||
|
||||
|
||||
def getTalkRect(self, startTime, duration, trackId, text):
|
||||
"Return shapes for a specific talk"
|
||||
g = Group()
|
||||
y_bottom = self.scaleTime(startTime + duration)
|
||||
y_top = self.scaleTime(startTime)
|
||||
y_height = y_top - y_bottom
|
||||
|
||||
if trackId is None:
|
||||
#spans all columns
|
||||
x = self._colLeftEdges[1]
|
||||
width = self.width - self._colWidths[0]
|
||||
else:
|
||||
#trackId is 1-based and these arrays have the margin info in column
|
||||
#zero, so no need to add 1
|
||||
x = self._colLeftEdges[trackId]
|
||||
width = self._colWidths[trackId]
|
||||
|
||||
lab = Label()
|
||||
lab.setText(text)
|
||||
lab.setOrigin(x + 0.5*width, y_bottom+0.5*y_height)
|
||||
lab.boxAnchor = 'c'
|
||||
lab.width = width
|
||||
lab.height = y_height
|
||||
lab.fontSize = 6
|
||||
|
||||
r = Rect(x, y_bottom, width, y_height, fillColor=colors.cyan)
|
||||
g.add(r)
|
||||
g.add(lab)
|
||||
|
||||
#now for a label
|
||||
# would expect to color-code and add text
|
||||
return g
|
||||
|
||||
def draw(self):
|
||||
self.computeSize()
|
||||
g = Group()
|
||||
|
||||
# time column
|
||||
g.add(Rect(self.x, self.y, self._colWidths[0], self.height - self.trackRowHeight, fillColor=colors.cornsilk))
|
||||
|
||||
# track headers
|
||||
x = self.x + self._colWidths[0]
|
||||
y = self.y + self.height - self.trackRowHeight
|
||||
for trk in range(self._trackCount):
|
||||
wid = self._colWidths[trk+1]
|
||||
r = Rect(x, y, wid, self.trackRowHeight, fillColor=colors.yellow)
|
||||
s = String(x + 0.5*wid, y, 'Track %d' % trk, align='middle')
|
||||
g.add(r)
|
||||
g.add(s)
|
||||
x = x + wid
|
||||
|
||||
for talk in self._talksVisible:
|
||||
(title, speaker, trackId, day, start, duration) = talk
|
||||
r = self.getTalkRect(start, duration, trackId, title + '\n' + speaker)
|
||||
g.add(r)
|
||||
|
||||
|
||||
return g
|
||||
|
||||
|
||||
|
||||
|
||||
def test():
|
||||
"Make a conference event for day 1 of UP Python 2003"
|
||||
|
||||
|
||||
d = Drawing(400,200)
|
||||
|
||||
cal = EventCalendar()
|
||||
cal.x = 50
|
||||
cal.y = 25
|
||||
cal.data = [
|
||||
# these might be better as objects instead of tuples, since I
|
||||
# predict a large number of "optionsl" variables to affect
|
||||
# formatting in future.
|
||||
|
||||
#title, speaker, track id, day, start time (hrs), duration (hrs)
|
||||
# track ID is 1-based not zero-based!
|
||||
('Keynote: Why design another programming language?', 'Guido van Rossum', None, 1, 9.0, 1.0),
|
||||
|
||||
('Siena Web Service Architecture', 'Marc-Andre Lemburg', 1, 1, 10.5, 1.5),
|
||||
('Extreme Programming in Python', 'Chris Withers', 2, 1, 10.5, 1.5),
|
||||
('Pattern Experiences in C++', 'Mark Radford', 3, 1, 10.5, 1.5),
|
||||
('What is the Type of std::toupper()', 'Gabriel Dos Reis', 4, 1, 10.5, 1.5),
|
||||
('Linguistic Variables: Clear Thinking with Fuzzy Logic ', 'Walter Banks', 5, 1, 10.5, 1.5),
|
||||
|
||||
('lunch, short presentations, vendor presentations', '', None, 1, 12.0, 2.0),
|
||||
|
||||
("CORBA? Isn't that obsolete", 'Duncan Grisby', 1, 1, 14.0, 1.5),
|
||||
("Python Design Patterns", 'Duncan Booth', 2, 1, 14.0, 1.5),
|
||||
("Inside Security Checks and Safe Exceptions", 'Brandon Bray', 3, 1, 14.0, 1.5),
|
||||
("Studying at a Distance", 'Panel Discussion, Panel to include Alan Lenton & Francis Glassborow', 4, 1, 14.0, 1.5),
|
||||
("Coding Standards - Given the ANSI C Standard why do I still need a coding Standard", 'Randy Marques', 5, 1, 14.0, 1.5),
|
||||
|
||||
("RESTful Python", 'Hamish Lawson', 1, 1, 16.0, 1.5),
|
||||
("Parsing made easier - a radical old idea", 'Andrew Koenig', 2, 1, 16.0, 1.5),
|
||||
("C++ & Multimethods", 'Julian Smith', 3, 1, 16.0, 1.5),
|
||||
("C++ Threading", 'Kevlin Henney', 4, 1, 16.0, 1.5),
|
||||
("The Organisation Strikes Back", 'Alan Griffiths & Sarah Lees', 5, 1, 16.0, 1.5),
|
||||
|
||||
('Birds of a Feather meeting', '', None, 1, 17.5, 2.0),
|
||||
|
||||
('Keynote: In the Spirit of C', 'Greg Colvin', None, 2, 9.0, 1.0),
|
||||
|
||||
('The Infinite Filing Cabinet - object storage in Python', 'Jacob Hallen', 1, 2, 10.5, 1.5),
|
||||
('Introduction to Python and Jython for C++ and Java Programmers', 'Alex Martelli', 2, 2, 10.5, 1.5),
|
||||
('Template metaprogramming in Haskell', 'Simon Peyton Jones', 3, 2, 10.5, 1.5),
|
||||
('Plenty People Programming: C++ Programming in a Group, Workshop with a difference', 'Nico Josuttis', 4, 2, 10.5, 1.5),
|
||||
('Design and Implementation of the Boost Graph Library', 'Jeremy Siek', 5, 2, 10.5, 1.5),
|
||||
|
||||
('lunch, short presentations, vendor presentations', '', None, 2, 12.0, 2.0),
|
||||
|
||||
("Building GUI Applications with PythonCard and PyCrust", 'Andy Todd', 1, 2, 14.0, 1.5),
|
||||
("Integrating Python, C and C++", 'Duncan Booth', 2, 2, 14.0, 1.5),
|
||||
("Secrets and Pitfalls of Templates", 'Nicolai Josuttis & David Vandevoorde', 3, 2, 14.0, 1.5),
|
||||
("Being a Mentor", 'Panel Discussion, Panel to include Alan Lenton & Francis Glassborow', 4, 2, 14.0, 1.5),
|
||||
("The Embedded C Extensions to C", 'Willem Wakker', 5, 2, 14.0, 1.5),
|
||||
|
||||
("Lightning Talks", 'Paul Brian', 1, 2, 16.0, 1.5),
|
||||
("Scripting Java Applications with Jython", 'Anthony Eden', 2, 2, 16.0, 1.5),
|
||||
("Metaprogramming and the Boost Metaprogramming Library", 'David Abrahams', 3, 2, 16.0, 1.5),
|
||||
("A Common Vendor ABI for C++ -- GCC's why, what and not", 'Nathan Sidwell & Gabriel Dos Reis', 4, 2, 16.0, 1.5),
|
||||
("The Timing and Cost of Choices", 'Hubert Matthews', 5, 2, 16.0, 1.5),
|
||||
|
||||
('Birds of a Feather meeting', '', None, 2, 17.5, 2.0),
|
||||
|
||||
('Keynote: The Cost of C & C++ Compatibility', 'Andy Koenig', None, 3, 9.0, 1.0),
|
||||
|
||||
('Prying Eyes: Generic Observer Implementations in C++', 'Andrei Alexandrescu', 1, 2, 10.5, 1.5),
|
||||
('The Roadmap to Generative Programming With C++', 'Ulrich Eisenecker', 2, 2, 10.5, 1.5),
|
||||
('Design Patterns in C++ and C# for the Common Language Runtime', 'Brandon Bray', 3, 2, 10.5, 1.5),
|
||||
('Extreme Hour (XH): (workshop) - Jutta Eckstein and Nico Josuttis', 'Jutta Ecstein', 4, 2, 10.5, 1.5),
|
||||
('The Lambda Library : Unnamed Functions for C++', 'Jaako Jarvi', 5, 2, 10.5, 1.5),
|
||||
|
||||
('lunch, short presentations, vendor presentations', '', None, 3, 12.0, 2.0),
|
||||
|
||||
('Reflective Metaprogramming', 'Daveed Vandevoorde', 1, 3, 14.0, 1.5),
|
||||
('Advanced Template Issues and Solutions (double session)', 'Herb Sutter',2, 3, 14.0, 3),
|
||||
('Concurrent Programming in Java (double session)', 'Angelika Langer', 3, 3, 14.0, 3),
|
||||
('What can MISRA-C (2nd Edition) do for us?', 'Chris Hills', 4, 3, 14.0, 1.5),
|
||||
('C++ Metaprogramming Concepts and Results', 'Walter E Brown', 5, 3, 14.0, 1.5),
|
||||
|
||||
('Binding C++ to Python with the Boost Python Library', 'David Abrahams', 1, 3, 16.0, 1.5),
|
||||
('Using Aspect Oriented Programming for Enterprise Application Integration', 'Arno Schmidmeier', 4, 3, 16.0, 1.5),
|
||||
('Defective C++', 'Marc Paterno', 5, 3, 16.0, 1.5),
|
||||
|
||||
("Speakers' Banquet & Birds of a Feather meeting", '', None, 3, 17.5, 2.0),
|
||||
|
||||
('Keynote: The Internet, Software and Computers - A Report Card', 'Alan Lenton', None, 4, 9.0, 1.0),
|
||||
|
||||
('Multi-Platform Software Development; Lessons from the Boost libraries', 'Beman Dawes', 1, 5, 10.5, 1.5),
|
||||
('The Stability of the C++ ABI', 'Steve Clamage', 2, 5, 10.5, 1.5),
|
||||
('Generic Build Support - A Pragmatic Approach to the Software Build Process', 'Randy Marques', 3, 5, 10.5, 1.5),
|
||||
('How to Handle Project Managers: a survival guide', 'Barb Byro', 4, 5, 10.5, 1.5),
|
||||
|
||||
('lunch, ACCU AGM', '', None, 5, 12.0, 2.0),
|
||||
|
||||
('Sauce: An OO recursive descent parser; its design and implementation.', 'Jon Jagger', 1, 5, 14.0, 1.5),
|
||||
('GNIRTS ESAC REWOL - Bringing the UNIX filters to the C++ iostream library.', 'JC van Winkel', 2, 5, 14.0, 1.5),
|
||||
('Pattern Writing: Live and Direct', 'Frank Buschmann & Kevlin Henney', 3, 5, 14.0, 3.0),
|
||||
('The Future of Programming Languages - A Goldfish Bowl', 'Francis Glassborow and friends', 3, 5, 14.0, 1.5),
|
||||
|
||||
('Honey, I Shrunk the Threads: Compile-time checked multithreaded transactions in C++', 'Andrei Alexandrescu', 1, 5, 16.0, 1.5),
|
||||
('Fun and Functionality with Functors', 'Lois Goldthwaite', 2, 5, 16.0, 1.5),
|
||||
('Agile Enough?', 'Alan Griffiths', 4, 5, 16.0, 1.5),
|
||||
("Conference Closure: A brief plenary session", '', None, 5, 17.5, 0.5),
|
||||
|
||||
]
|
||||
|
||||
#return cal
|
||||
cal.day = 1
|
||||
|
||||
d.add(cal)
|
||||
|
||||
|
||||
for format in ['pdf']:#,'gif','png']:
|
||||
out = d.asString(format)
|
||||
open('eventcal.%s' % format, 'wb').write(out)
|
||||
print 'saved eventcal.%s' % format
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
|
@ -1,879 +0,0 @@
|
|||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/widgets/flags.py
|
||||
# Flag Widgets - a collection of flags as widgets
|
||||
# author: John Precedo (johnp@reportlab.com)
|
||||
"""This file is a collection of flag graphics as widgets.
|
||||
|
||||
All flags are represented at the ratio of 1:2, even where the official ratio for the flag is something else
|
||||
(such as 3:5 for the German national flag). The only exceptions are for where this would look _very_ wrong,
|
||||
such as the Danish flag whose (ratio is 28:37), or the Swiss flag (which is square).
|
||||
|
||||
Unless otherwise stated, these flags are all the 'national flags' of the countries, rather than their
|
||||
state flags, naval flags, ensigns or any other variants. (National flags are the flag flown by civilians
|
||||
of a country and the ones usually used to represent a country abroad. State flags are the variants used by
|
||||
the government and by diplomatic missions overseas).
|
||||
|
||||
To check on how close these are to the 'official' representations of flags, check the World Flag Database at
|
||||
http://www.flags.ndirect.co.uk/
|
||||
|
||||
The flags this file contains are:
|
||||
|
||||
EU Members:
|
||||
United Kingdom, Austria, Belgium, Denmark, Finland, France, Germany, Greece, Ireland, Italy, Luxembourg,
|
||||
Holland (The Netherlands), Spain, Sweden
|
||||
|
||||
Others:
|
||||
USA, Czech Republic, European Union, Switzerland, Turkey, Brazil
|
||||
|
||||
(Brazilian flag contributed by Publio da Costa Melo [publio@planetarium.com.br]).
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.validators import *
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.graphics.shapes import Line, Rect, Polygon, Drawing, Group, String, Circle, Wedge
|
||||
from reportlab.graphics.widgetbase import Widget
|
||||
from reportlab.graphics import renderPDF
|
||||
from signsandsymbols import _Symbol
|
||||
import copy
|
||||
from math import sin, cos, pi
|
||||
|
||||
validFlag=OneOf(None,
|
||||
'UK',
|
||||
'USA',
|
||||
'Afghanistan',
|
||||
'Austria',
|
||||
'Belgium',
|
||||
'China',
|
||||
'Cuba',
|
||||
'Denmark',
|
||||
'Finland',
|
||||
'France',
|
||||
'Germany',
|
||||
'Greece',
|
||||
'Ireland',
|
||||
'Italy',
|
||||
'Japan',
|
||||
'Luxembourg',
|
||||
'Holland',
|
||||
'Palestine',
|
||||
'Portugal',
|
||||
'Russia',
|
||||
'Spain',
|
||||
'Sweden',
|
||||
'Norway',
|
||||
'CzechRepublic',
|
||||
'Turkey',
|
||||
'Switzerland',
|
||||
'EU',
|
||||
'Brazil'
|
||||
)
|
||||
|
||||
_size = 100.
|
||||
|
||||
class Star(_Symbol):
|
||||
"""This draws a 5-pointed star.
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'fillColor', 'strokeColor'
|
||||
|
||||
"""
|
||||
_attrMap = AttrMap(BASE=_Symbol,
|
||||
angle = AttrMapValue(isNumber, desc='angle in degrees'),
|
||||
)
|
||||
_size = 100.
|
||||
|
||||
def __init__(self):
|
||||
_Symbol.__init__(self)
|
||||
self.size = 100
|
||||
self.fillColor = colors.yellow
|
||||
self.strokeColor = None
|
||||
self.angle = 0
|
||||
|
||||
def demo(self):
|
||||
D = Drawing(200, 100)
|
||||
et = Star()
|
||||
et.x=50
|
||||
et.y=0
|
||||
D.add(et)
|
||||
labelFontSize = 10
|
||||
D.add(String(et.x+(et.size/2.0),(et.y-(1.2*labelFontSize)),
|
||||
et.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
return D
|
||||
|
||||
def draw(self):
|
||||
s = float(self.size) #abbreviate as we will use this a lot
|
||||
g = Group()
|
||||
|
||||
# new algorithm from markers.StarFive
|
||||
R = float(self.size)/2
|
||||
r = R*sin(18*(pi/180.0))/cos(36*(pi/180.0))
|
||||
P = []
|
||||
angle = 90
|
||||
for i in xrange(5):
|
||||
for radius in R, r:
|
||||
theta = angle*(pi/180.0)
|
||||
P.append(radius*cos(theta))
|
||||
P.append(radius*sin(theta))
|
||||
angle = angle + 36
|
||||
# star specific bits
|
||||
star = Polygon(P,
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = self.strokeColor,
|
||||
strokeWidth=s/50)
|
||||
g.rotate(self.angle)
|
||||
g.shift(self.x+self.dx,self.y+self.dy)
|
||||
g.add(star)
|
||||
|
||||
return g
|
||||
|
||||
class Flag(_Symbol):
|
||||
"""This is a generic flag class that all the flags in this file use as a basis.
|
||||
|
||||
This class basically provides edges and a tidy-up routine to hide any bits of
|
||||
line that overlap the 'outside' of the flag
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'fillColor'
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(BASE=_Symbol,
|
||||
fillColor = AttrMapValue(isColor, desc='Background color'),
|
||||
border = AttrMapValue(isBoolean, 'Whether a background is drawn'),
|
||||
kind = AttrMapValue(validFlag, desc='Which flag'),
|
||||
)
|
||||
|
||||
_cache = {}
|
||||
|
||||
def __init__(self,**kw):
|
||||
_Symbol.__init__(self)
|
||||
self.kind = None
|
||||
self.size = 100
|
||||
self.fillColor = colors.white
|
||||
self.border=1
|
||||
self.setProperties(kw)
|
||||
|
||||
def availableFlagNames(self):
|
||||
'''return a list of the things we can display'''
|
||||
return filter(lambda x: x is not None, self._attrMap['kind'].validate._enum)
|
||||
|
||||
def _Flag_None(self):
|
||||
s = _size # abbreviate as we will use this a lot
|
||||
g = Group()
|
||||
g.add(Rect(0, 0, s*2, s, fillColor = colors.purple, strokeColor = colors.black, strokeWidth=0))
|
||||
return g
|
||||
|
||||
def _borderDraw(self,f):
|
||||
s = self.size # abbreviate as we will use this a lot
|
||||
g = Group()
|
||||
g.add(f)
|
||||
x, y, sW = self.x+self.dx, self.y+self.dy, self.strokeWidth/2.
|
||||
g.insert(0,Rect(-sW, -sW, width=getattr(self,'_width',2*s)+3*sW, height=getattr(self,'_height',s)+2*sW,
|
||||
fillColor = None, strokeColor = self.strokeColor, strokeWidth=sW*2))
|
||||
g.shift(x,y)
|
||||
g.scale(s/_size, s/_size)
|
||||
return g
|
||||
|
||||
def draw(self):
|
||||
kind = self.kind or 'None'
|
||||
f = self._cache.get(kind)
|
||||
if not f:
|
||||
f = getattr(self,'_Flag_'+kind)()
|
||||
self._cache[kind] = f._explode()
|
||||
return self._borderDraw(f)
|
||||
|
||||
def clone(self):
|
||||
return copy.copy(self)
|
||||
|
||||
def demo(self):
|
||||
D = Drawing(200, 100)
|
||||
name = self.availableFlagNames()
|
||||
import time
|
||||
name = name[int(time.time()) % len(name)]
|
||||
fx = Flag()
|
||||
fx.kind = name
|
||||
fx.x = 0
|
||||
fx.y = 0
|
||||
D.add(fx)
|
||||
labelFontSize = 10
|
||||
D.add(String(fx.x+(fx.size/2),(fx.y-(1.2*labelFontSize)),
|
||||
name, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
labelFontSize = int(fx.size/4)
|
||||
D.add(String(fx.x+(fx.size),(fx.y+((fx.size/2))),
|
||||
"SAMPLE", fillColor=colors.gold, textAnchor='middle',
|
||||
fontSize=labelFontSize, fontName="Helvetica-Bold"))
|
||||
return D
|
||||
|
||||
def _Flag_UK(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
w = s*2
|
||||
g.add(Rect(0, 0, w, s, fillColor = colors.navy, strokeColor = colors.black, strokeWidth=0))
|
||||
g.add(Polygon([0,0, s*.225,0, w,s*(1-.1125), w,s, w-s*.225,s, 0, s*.1125], fillColor = colors.mintcream, strokeColor=None, strokeWidth=0))
|
||||
g.add(Polygon([0,s*(1-.1125), 0, s, s*.225,s, w, s*.1125, w,0, w-s*.225,0], fillColor = colors.mintcream, strokeColor=None, strokeWidth=0))
|
||||
g.add(Polygon([0, s-(s/15), (s-((s/10)*4)), (s*0.65), (s-(s/10)*3), (s*0.65), 0, s], fillColor = colors.red, strokeColor = None, strokeWidth=0))
|
||||
g.add(Polygon([0, 0, (s-((s/10)*3)), (s*0.35), (s-((s/10)*2)), (s*0.35), (s/10), 0], fillColor = colors.red, strokeColor = None, strokeWidth=0))
|
||||
g.add(Polygon([w, s, (s+((s/10)*3)), (s*0.65), (s+((s/10)*2)), (s*0.65), w-(s/10), s], fillColor = colors.red, strokeColor = None, strokeWidth=0))
|
||||
g.add(Polygon([w, (s/15), (s+((s/10)*4)), (s*0.35), (s+((s/10)*3)), (s*0.35), w, 0], fillColor = colors.red, strokeColor = None, strokeWidth=0))
|
||||
g.add(Rect(((s*0.42)*2), 0, width=(0.16*s)*2, height=s, fillColor = colors.mintcream, strokeColor = None, strokeWidth=0))
|
||||
g.add(Rect(0, (s*0.35), width=w, height=s*0.3, fillColor = colors.mintcream, strokeColor = None, strokeWidth=0))
|
||||
g.add(Rect(((s*0.45)*2), 0, width=(0.1*s)*2, height=s, fillColor = colors.red, strokeColor = None, strokeWidth=0))
|
||||
g.add(Rect(0, (s*0.4), width=w, height=s*0.2, fillColor = colors.red, strokeColor = None, strokeWidth=0))
|
||||
return g
|
||||
|
||||
def _Flag_USA(self):
|
||||
s = _size # abbreviate as we will use this a lot
|
||||
g = Group()
|
||||
|
||||
box = Rect(0, 0, s*2, s, fillColor = colors.mintcream, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
for stripecounter in range (13,0, -1):
|
||||
stripeheight = s/13.0
|
||||
if not (stripecounter%2 == 0):
|
||||
stripecolor = colors.red
|
||||
else:
|
||||
stripecolor = colors.mintcream
|
||||
redorwhiteline = Rect(0, (s-(stripeheight*stripecounter)), width=s*2, height=stripeheight,
|
||||
fillColor = stripecolor, strokeColor = None, strokeWidth=20)
|
||||
g.add(redorwhiteline)
|
||||
|
||||
bluebox = Rect(0, (s-(stripeheight*7)), width=0.8*s, height=stripeheight*7,
|
||||
fillColor = colors.darkblue, strokeColor = None, strokeWidth=0)
|
||||
g.add(bluebox)
|
||||
|
||||
lss = s*0.045
|
||||
lss2 = lss/2
|
||||
s9 = s/9
|
||||
s7 = s/7
|
||||
for starxcounter in range(5):
|
||||
for starycounter in range(4):
|
||||
ls = Star()
|
||||
ls.size = lss
|
||||
ls.x = 0-s/22+lss/2+s7+starxcounter*s7
|
||||
ls.fillColor = colors.mintcream
|
||||
ls.y = s-(starycounter+1)*s9+lss2
|
||||
g.add(ls)
|
||||
|
||||
for starxcounter in range(6):
|
||||
for starycounter in range(5):
|
||||
ls = Star()
|
||||
ls.size = lss
|
||||
ls.x = 0-(s/22)+lss/2+s/14+starxcounter*s7
|
||||
ls.fillColor = colors.mintcream
|
||||
ls.y = s-(starycounter+1)*s9+(s/18)+lss2
|
||||
g.add(ls)
|
||||
return g
|
||||
|
||||
def _Flag_Afghanistan(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
|
||||
box = Rect(0, 0, s*2, s,
|
||||
fillColor = colors.mintcream, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
greenbox = Rect(0, ((s/3.0)*2.0), width=s*2.0, height=s/3.0,
|
||||
fillColor = colors.limegreen, strokeColor = None, strokeWidth=0)
|
||||
g.add(greenbox)
|
||||
|
||||
blackbox = Rect(0, 0, width=s*2.0, height=s/3.0,
|
||||
fillColor = colors.black, strokeColor = None, strokeWidth=0)
|
||||
g.add(blackbox)
|
||||
return g
|
||||
|
||||
def _Flag_Austria(self):
|
||||
s = _size # abbreviate as we will use this a lot
|
||||
g = Group()
|
||||
|
||||
box = Rect(0, 0, s*2, s, fillColor = colors.mintcream,
|
||||
strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
|
||||
redbox1 = Rect(0, 0, width=s*2.0, height=s/3.0,
|
||||
fillColor = colors.red, strokeColor = None, strokeWidth=0)
|
||||
g.add(redbox1)
|
||||
|
||||
redbox2 = Rect(0, ((s/3.0)*2.0), width=s*2.0, height=s/3.0,
|
||||
fillColor = colors.red, strokeColor = None, strokeWidth=0)
|
||||
g.add(redbox2)
|
||||
return g
|
||||
|
||||
def _Flag_Belgium(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
|
||||
box = Rect(0, 0, s*2, s,
|
||||
fillColor = colors.black, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
|
||||
box1 = Rect(0, 0, width=(s/3.0)*2.0, height=s,
|
||||
fillColor = colors.black, strokeColor = None, strokeWidth=0)
|
||||
g.add(box1)
|
||||
|
||||
box2 = Rect(((s/3.0)*2.0), 0, width=(s/3.0)*2.0, height=s,
|
||||
fillColor = colors.gold, strokeColor = None, strokeWidth=0)
|
||||
g.add(box2)
|
||||
|
||||
box3 = Rect(((s/3.0)*4.0), 0, width=(s/3.0)*2.0, height=s,
|
||||
fillColor = colors.red, strokeColor = None, strokeWidth=0)
|
||||
g.add(box3)
|
||||
return g
|
||||
|
||||
def _Flag_China(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
self._width = w = s*1.5
|
||||
g.add(Rect(0, 0, w, s, fillColor=colors.red, strokeColor=None, strokeWidth=0))
|
||||
|
||||
def addStar(x,y,size,angle,g=g,w=s/20,x0=0,y0=s/2):
|
||||
s = Star()
|
||||
s.fillColor=colors.yellow
|
||||
s.angle = angle
|
||||
s.size = size*w*2
|
||||
s.x = x*w+x0
|
||||
s.y = y*w+y0
|
||||
g.add(s)
|
||||
|
||||
addStar(5,5,3, 0)
|
||||
addStar(10,1,1,36.86989765)
|
||||
addStar(12,3,1,8.213210702)
|
||||
addStar(12,6,1,16.60154960)
|
||||
addStar(10,8,1,53.13010235)
|
||||
return g
|
||||
|
||||
def _Flag_Cuba(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
|
||||
for i in range(5):
|
||||
stripe = Rect(0, i*s/5, width=s*2, height=s/5,
|
||||
fillColor = [colors.darkblue, colors.mintcream][i%2],
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(stripe)
|
||||
|
||||
redwedge = Polygon(points = [ 0, 0, 4*s/5, (s/2), 0, s],
|
||||
fillColor = colors.red, strokeColor = None, strokeWidth=0)
|
||||
g.add(redwedge)
|
||||
|
||||
star = Star()
|
||||
star.x = 2.5*s/10
|
||||
star.y = s/2
|
||||
star.size = 3*s/10
|
||||
star.fillColor = colors.white
|
||||
g.add(star)
|
||||
|
||||
box = Rect(0, 0, s*2, s,
|
||||
fillColor = None,
|
||||
strokeColor = colors.black,
|
||||
strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
return g
|
||||
|
||||
def _Flag_Denmark(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
self._width = w = s*1.4
|
||||
|
||||
box = Rect(0, 0, w, s,
|
||||
fillColor = colors.red, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
whitebox1 = Rect(((s/5)*2), 0, width=s/6, height=s,
|
||||
fillColor = colors.mintcream, strokeColor = None, strokeWidth=0)
|
||||
g.add(whitebox1)
|
||||
|
||||
whitebox2 = Rect(0, ((s/2)-(s/12)), width=w, height=s/6,
|
||||
fillColor = colors.mintcream, strokeColor = None, strokeWidth=0)
|
||||
g.add(whitebox2)
|
||||
return g
|
||||
|
||||
def _Flag_Finland(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
|
||||
# crossbox specific bits
|
||||
box = Rect(0, 0, s*2, s,
|
||||
fillColor = colors.ghostwhite, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
blueline1 = Rect((s*0.6), 0, width=0.3*s, height=s,
|
||||
fillColor = colors.darkblue, strokeColor = None, strokeWidth=0)
|
||||
g.add(blueline1)
|
||||
|
||||
blueline2 = Rect(0, (s*0.4), width=s*2, height=s*0.3,
|
||||
fillColor = colors.darkblue, strokeColor = None, strokeWidth=0)
|
||||
g.add(blueline2)
|
||||
return g
|
||||
|
||||
def _Flag_France(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
|
||||
box = Rect(0, 0, s*2, s, fillColor = colors.navy, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
bluebox = Rect(0, 0, width=((s/3.0)*2.0), height=s,
|
||||
fillColor = colors.blue, strokeColor = None, strokeWidth=0)
|
||||
g.add(bluebox)
|
||||
|
||||
whitebox = Rect(((s/3.0)*2.0), 0, width=((s/3.0)*2.0), height=s,
|
||||
fillColor = colors.mintcream, strokeColor = None, strokeWidth=0)
|
||||
g.add(whitebox)
|
||||
|
||||
redbox = Rect(((s/3.0)*4.0), 0, width=((s/3.0)*2.0), height=s,
|
||||
fillColor = colors.red,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(redbox)
|
||||
return g
|
||||
|
||||
def _Flag_Germany(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
|
||||
box = Rect(0, 0, s*2, s,
|
||||
fillColor = colors.gold, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
blackbox1 = Rect(0, ((s/3.0)*2.0), width=s*2.0, height=s/3.0,
|
||||
fillColor = colors.black, strokeColor = None, strokeWidth=0)
|
||||
g.add(blackbox1)
|
||||
|
||||
redbox1 = Rect(0, (s/3.0), width=s*2.0, height=s/3.0,
|
||||
fillColor = colors.orangered, strokeColor = None, strokeWidth=0)
|
||||
g.add(redbox1)
|
||||
return g
|
||||
|
||||
def _Flag_Greece(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
|
||||
box = Rect(0, 0, s*2, s, fillColor = colors.gold,
|
||||
strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
for stripecounter in range (9,0, -1):
|
||||
stripeheight = s/9.0
|
||||
if not (stripecounter%2 == 0):
|
||||
stripecolor = colors.deepskyblue
|
||||
else:
|
||||
stripecolor = colors.mintcream
|
||||
|
||||
blueorwhiteline = Rect(0, (s-(stripeheight*stripecounter)), width=s*2, height=stripeheight,
|
||||
fillColor = stripecolor, strokeColor = None, strokeWidth=20)
|
||||
g.add(blueorwhiteline)
|
||||
|
||||
bluebox1 = Rect(0, ((s)-stripeheight*5), width=(stripeheight*5), height=stripeheight*5,
|
||||
fillColor = colors.deepskyblue, strokeColor = None, strokeWidth=0)
|
||||
g.add(bluebox1)
|
||||
|
||||
whiteline1 = Rect(0, ((s)-stripeheight*3), width=stripeheight*5, height=stripeheight,
|
||||
fillColor = colors.mintcream, strokeColor = None, strokeWidth=0)
|
||||
g.add(whiteline1)
|
||||
|
||||
whiteline2 = Rect((stripeheight*2), ((s)-stripeheight*5), width=stripeheight, height=stripeheight*5,
|
||||
fillColor = colors.mintcream, strokeColor = None, strokeWidth=0)
|
||||
g.add(whiteline2)
|
||||
|
||||
return g
|
||||
|
||||
def _Flag_Ireland(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
|
||||
box = Rect(0, 0, s*2, s,
|
||||
fillColor = colors.forestgreen, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
whitebox = Rect(((s*2.0)/3.0), 0, width=(2.0*(s*2.0)/3.0), height=s,
|
||||
fillColor = colors.mintcream, strokeColor = None, strokeWidth=0)
|
||||
g.add(whitebox)
|
||||
|
||||
orangebox = Rect(((2.0*(s*2.0)/3.0)), 0, width=(s*2.0)/3.0, height=s,
|
||||
fillColor = colors.darkorange, strokeColor = None, strokeWidth=0)
|
||||
g.add(orangebox)
|
||||
return g
|
||||
|
||||
def _Flag_Italy(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
g.add(Rect(0,0,s*2,s,fillColor=colors.forestgreen,strokeColor=None, strokeWidth=0))
|
||||
g.add(Rect((2*s)/3, 0, width=(s*4)/3, height=s, fillColor = colors.mintcream, strokeColor = None, strokeWidth=0))
|
||||
g.add(Rect((4*s)/3, 0, width=(s*2)/3, height=s, fillColor = colors.red, strokeColor = None, strokeWidth=0))
|
||||
return g
|
||||
|
||||
def _Flag_Japan(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
w = self._width = s*1.5
|
||||
g.add(Rect(0,0,w,s,fillColor=colors.mintcream,strokeColor=None, strokeWidth=0))
|
||||
g.add(Circle(cx=w/2,cy=s/2,r=0.3*w,fillColor=colors.red,strokeColor=None, strokeWidth=0))
|
||||
return g
|
||||
|
||||
def _Flag_Luxembourg(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
|
||||
box = Rect(0, 0, s*2, s,
|
||||
fillColor = colors.mintcream, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
redbox = Rect(0, ((s/3.0)*2.0), width=s*2.0, height=s/3.0,
|
||||
fillColor = colors.red, strokeColor = None, strokeWidth=0)
|
||||
g.add(redbox)
|
||||
|
||||
bluebox = Rect(0, 0, width=s*2.0, height=s/3.0,
|
||||
fillColor = colors.dodgerblue, strokeColor = None, strokeWidth=0)
|
||||
g.add(bluebox)
|
||||
return g
|
||||
|
||||
def _Flag_Holland(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
|
||||
box = Rect(0, 0, s*2, s,
|
||||
fillColor = colors.mintcream, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
redbox = Rect(0, ((s/3.0)*2.0), width=s*2.0, height=s/3.0,
|
||||
fillColor = colors.red, strokeColor = None, strokeWidth=0)
|
||||
g.add(redbox)
|
||||
|
||||
bluebox = Rect(0, 0, width=s*2.0, height=s/3.0,
|
||||
fillColor = colors.darkblue, strokeColor = None, strokeWidth=0)
|
||||
g.add(bluebox)
|
||||
return g
|
||||
|
||||
def _Flag_Portugal(self):
|
||||
return Group()
|
||||
|
||||
def _Flag_Russia(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
w = self._width = s*1.5
|
||||
t = s/3
|
||||
g.add(Rect(0, 0, width=w, height=t, fillColor = colors.red, strokeColor = None, strokeWidth=0))
|
||||
g.add(Rect(0, t, width=w, height=t, fillColor = colors.blue, strokeColor = None, strokeWidth=0))
|
||||
g.add(Rect(0, 2*t, width=w, height=t, fillColor = colors.mintcream, strokeColor = None, strokeWidth=0))
|
||||
return g
|
||||
|
||||
def _Flag_Spain(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
w = self._width = s*1.5
|
||||
g.add(Rect(0, 0, width=w, height=s, fillColor = colors.red, strokeColor = None, strokeWidth=0))
|
||||
g.add(Rect(0, (s/4), width=w, height=s/2, fillColor = colors.yellow, strokeColor = None, strokeWidth=0))
|
||||
return g
|
||||
|
||||
def _Flag_Sweden(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
self._width = s*1.4
|
||||
box = Rect(0, 0, self._width, s,
|
||||
fillColor = colors.dodgerblue, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
box1 = Rect(((s/5)*2), 0, width=s/6, height=s,
|
||||
fillColor = colors.gold, strokeColor = None, strokeWidth=0)
|
||||
g.add(box1)
|
||||
|
||||
box2 = Rect(0, ((s/2)-(s/12)), width=self._width, height=s/6,
|
||||
fillColor = colors.gold,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(box2)
|
||||
return g
|
||||
|
||||
def _Flag_Norway(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
self._width = s*1.4
|
||||
|
||||
box = Rect(0, 0, self._width, s,
|
||||
fillColor = colors.red, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
box = Rect(0, 0, self._width, s,
|
||||
fillColor = colors.red, strokeColor = colors.black, strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
whiteline1 = Rect(((s*0.2)*2), 0, width=s*0.2, height=s,
|
||||
fillColor = colors.ghostwhite, strokeColor = None, strokeWidth=0)
|
||||
g.add(whiteline1)
|
||||
|
||||
whiteline2 = Rect(0, (s*0.4), width=self._width, height=s*0.2,
|
||||
fillColor = colors.ghostwhite, strokeColor = None, strokeWidth=0)
|
||||
g.add(whiteline2)
|
||||
|
||||
blueline1 = Rect(((s*0.225)*2), 0, width=0.1*s, height=s,
|
||||
fillColor = colors.darkblue, strokeColor = None, strokeWidth=0)
|
||||
g.add(blueline1)
|
||||
|
||||
blueline2 = Rect(0, (s*0.45), width=self._width, height=s*0.1,
|
||||
fillColor = colors.darkblue, strokeColor = None, strokeWidth=0)
|
||||
g.add(blueline2)
|
||||
return g
|
||||
|
||||
def _Flag_CzechRepublic(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
box = Rect(0, 0, s*2, s,
|
||||
fillColor = colors.mintcream,
|
||||
strokeColor = colors.black,
|
||||
strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
redbox = Rect(0, 0, width=s*2, height=s/2,
|
||||
fillColor = colors.red,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(redbox)
|
||||
|
||||
bluewedge = Polygon(points = [ 0, 0, s, (s/2), 0, s],
|
||||
fillColor = colors.darkblue, strokeColor = None, strokeWidth=0)
|
||||
g.add(bluewedge)
|
||||
return g
|
||||
|
||||
def _Flag_Palestine(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
box = Rect(0, s/3, s*2, s/3,
|
||||
fillColor = colors.mintcream,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
greenbox = Rect(0, 0, width=s*2, height=s/3,
|
||||
fillColor = colors.limegreen,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(greenbox)
|
||||
|
||||
blackbox = Rect(0, 2*s/3, width=s*2, height=s/3,
|
||||
fillColor = colors.black,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(blackbox)
|
||||
|
||||
redwedge = Polygon(points = [ 0, 0, 2*s/3, (s/2), 0, s],
|
||||
fillColor = colors.red, strokeColor = None, strokeWidth=0)
|
||||
g.add(redwedge)
|
||||
return g
|
||||
|
||||
def _Flag_Turkey(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
|
||||
box = Rect(0, 0, s*2, s,
|
||||
fillColor = colors.red,
|
||||
strokeColor = colors.black,
|
||||
strokeWidth=0)
|
||||
g.add(box)
|
||||
|
||||
whitecircle = Circle(cx=((s*0.35)*2), cy=s/2, r=s*0.3,
|
||||
fillColor = colors.mintcream,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(whitecircle)
|
||||
|
||||
redcircle = Circle(cx=((s*0.39)*2), cy=s/2, r=s*0.24,
|
||||
fillColor = colors.red,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(redcircle)
|
||||
|
||||
ws = Star()
|
||||
ws.angle = 15
|
||||
ws.size = s/5
|
||||
ws.x = (s*0.5)*2+ws.size/2
|
||||
ws.y = (s*0.5)
|
||||
ws.fillColor = colors.mintcream
|
||||
ws.strokeColor = None
|
||||
g.add(ws)
|
||||
return g
|
||||
|
||||
def _Flag_Switzerland(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
self._width = s
|
||||
|
||||
g.add(Rect(0, 0, s, s, fillColor = colors.red, strokeColor = colors.black, strokeWidth=0))
|
||||
g.add(Line((s/2), (s/5.5), (s/2), (s-(s/5.5)),
|
||||
fillColor = colors.mintcream, strokeColor = colors.mintcream, strokeWidth=(s/5)))
|
||||
g.add(Line((s/5.5), (s/2), (s-(s/5.5)), (s/2),
|
||||
fillColor = colors.mintcream, strokeColor = colors.mintcream, strokeWidth=s/5))
|
||||
return g
|
||||
|
||||
def _Flag_EU(self):
|
||||
s = _size
|
||||
g = Group()
|
||||
w = self._width = 1.5*s
|
||||
|
||||
g.add(Rect(0, 0, w, s, fillColor = colors.darkblue, strokeColor = None, strokeWidth=0))
|
||||
centerx=w/2
|
||||
centery=s/2
|
||||
radius=s/3
|
||||
yradius = radius
|
||||
xradius = radius
|
||||
nStars = 12
|
||||
delta = 2*pi/nStars
|
||||
for i in range(nStars):
|
||||
rad = i*delta
|
||||
gs = Star()
|
||||
gs.x=cos(rad)*radius+centerx
|
||||
gs.y=sin(rad)*radius+centery
|
||||
gs.size=s/10
|
||||
gs.fillColor=colors.gold
|
||||
g.add(gs)
|
||||
return g
|
||||
|
||||
def _Flag_Brazil(self):
|
||||
s = _size # abbreviate as we will use this a lot
|
||||
g = Group()
|
||||
|
||||
m = s/14
|
||||
self._width = w = (m * 20)
|
||||
|
||||
def addStar(x,y,size, g=g, w=w, s=s, m=m):
|
||||
st = Star()
|
||||
st.fillColor=colors.mintcream
|
||||
st.size = size*m
|
||||
st.x = (w/2) + (x * (0.35 * m))
|
||||
st.y = (s/2) + (y * (0.35 * m))
|
||||
g.add(st)
|
||||
|
||||
g.add(Rect(0, 0, w, s, fillColor = colors.green, strokeColor = None, strokeWidth=0))
|
||||
g.add(Polygon(points = [ 1.7*m, (s/2), (w/2), s-(1.7*m), w-(1.7*m),(s/2),(w/2), 1.7*m],
|
||||
fillColor = colors.yellow, strokeColor = None, strokeWidth=0))
|
||||
g.add(Circle(cx=w/2, cy=s/2, r=3.5*m,
|
||||
fillColor=colors.blue,strokeColor=None, strokeWidth=0))
|
||||
g.add(Wedge((w/2)-(2*m), 0, 8.5*m, 50, 98.1, 8.5*m,
|
||||
fillColor=colors.mintcream,strokeColor=None, strokeWidth=0))
|
||||
g.add(Wedge((w/2), (s/2), 3.501*m, 156, 352, 3.501*m,
|
||||
fillColor=colors.mintcream,strokeColor=None, strokeWidth=0))
|
||||
g.add(Wedge((w/2)-(2*m), 0, 8*m, 48.1, 100, 8*m,
|
||||
fillColor=colors.blue,strokeColor=None, strokeWidth=0))
|
||||
g.add(Rect(0, 0, w, (s/4) + 1.7*m,
|
||||
fillColor = colors.green, strokeColor = None, strokeWidth=0))
|
||||
g.add(Polygon(points = [ 1.7*m,(s/2), (w/2),s/2 - 2*m, w-(1.7*m),(s/2) , (w/2),1.7*m],
|
||||
fillColor = colors.yellow, strokeColor = None, strokeWidth=0))
|
||||
g.add(Wedge(w/2, s/2, 3.502*m, 166, 342.1, 3.502*m,
|
||||
fillColor=colors.blue,strokeColor=None, strokeWidth=0))
|
||||
|
||||
addStar(3.2,3.5,0.3)
|
||||
addStar(-8.5,1.5,0.3)
|
||||
addStar(-7.5,-3,0.3)
|
||||
addStar(-4,-5.5,0.3)
|
||||
addStar(0,-4.5,0.3)
|
||||
addStar(7,-3.5,0.3)
|
||||
addStar(-3.5,-0.5,0.25)
|
||||
addStar(0,-1.5,0.25)
|
||||
addStar(1,-2.5,0.25)
|
||||
addStar(3,-7,0.25)
|
||||
addStar(5,-6.5,0.25)
|
||||
addStar(6.5,-5,0.25)
|
||||
addStar(7,-4.5,0.25)
|
||||
addStar(-5.5,-3.2,0.25)
|
||||
addStar(-6,-4.2,0.25)
|
||||
addStar(-1,-2.75,0.2)
|
||||
addStar(2,-5.5,0.2)
|
||||
addStar(4,-5.5,0.2)
|
||||
addStar(5,-7.5,0.2)
|
||||
addStar(5,-5.5,0.2)
|
||||
addStar(6,-5.5,0.2)
|
||||
addStar(-8.8,-3.2,0.2)
|
||||
addStar(2.5,0.5,0.2)
|
||||
addStar(-0.2,-3.2,0.14)
|
||||
addStar(-7.2,-2,0.14)
|
||||
addStar(0,-8,0.1)
|
||||
|
||||
sTmp = "ORDEM E PROGRESSO"
|
||||
nTmp = len(sTmp)
|
||||
delta = 0.850848010347/nTmp
|
||||
radius = 7.9 *m
|
||||
centerx = (w/2)-(2*m)
|
||||
centery = 0
|
||||
for i in range(nTmp):
|
||||
rad = 2*pi - i*delta -4.60766922527
|
||||
x=cos(rad)*radius+centerx
|
||||
y=sin(rad)*radius+centery
|
||||
if i == 6:
|
||||
z = 0.35*m
|
||||
else:
|
||||
z= 0.45*m
|
||||
g2 = Group(String(x, y, sTmp[i], fontName='Helvetica-Bold',
|
||||
fontSize = z,strokeColor=None,fillColor=colors.green))
|
||||
g2.rotate(rad)
|
||||
g.add(g2)
|
||||
return g
|
||||
|
||||
def makeFlag(name):
|
||||
flag = Flag()
|
||||
flag.kind = name
|
||||
return flag
|
||||
|
||||
def test():
|
||||
"""This function produces three pdf files with examples of all the signs and symbols from this file.
|
||||
"""
|
||||
# page 1
|
||||
|
||||
labelFontSize = 10
|
||||
|
||||
X = (20,245)
|
||||
|
||||
flags = [
|
||||
'UK',
|
||||
'USA',
|
||||
'Afghanistan',
|
||||
'Austria',
|
||||
'Belgium',
|
||||
'Denmark',
|
||||
'Cuba',
|
||||
'Finland',
|
||||
'France',
|
||||
'Germany',
|
||||
'Greece',
|
||||
'Ireland',
|
||||
'Italy',
|
||||
'Luxembourg',
|
||||
'Holland',
|
||||
'Palestine',
|
||||
'Portugal',
|
||||
'Spain',
|
||||
'Sweden',
|
||||
'Norway',
|
||||
'CzechRepublic',
|
||||
'Turkey',
|
||||
'Switzerland',
|
||||
'EU',
|
||||
'Brazil',
|
||||
]
|
||||
y = Y0 = 530
|
||||
f = 0
|
||||
D = None
|
||||
for name in flags:
|
||||
if not D: D = Drawing(450,650)
|
||||
flag = makeFlag(name)
|
||||
i = flags.index(name)
|
||||
flag.x = X[i%2]
|
||||
flag.y = y
|
||||
D.add(flag)
|
||||
D.add(String(flag.x+(flag.size/2),(flag.y-(1.2*labelFontSize)),
|
||||
name, fillColor=colors.black, textAnchor='middle', fontSize=labelFontSize))
|
||||
if i%2: y = y - 125
|
||||
if (i%2 and y<0) or name==flags[-1]:
|
||||
renderPDF.drawToFile(D, 'flags%02d.pdf'%f, 'flags.py - Page #%d'%(f+1))
|
||||
y = Y0
|
||||
f = f+1
|
||||
D = None
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
|
@ -1,504 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/widgets/grids.py
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.validators import isNumber, isColorOrNone, isBoolean, isListOfNumbers, OneOf, isListOfColors
|
||||
from reportlab.lib.attrmap import AttrMap, AttrMapValue
|
||||
from reportlab.graphics.shapes import Drawing, Group, Line, Rect, LineShape, definePath, EmptyClipPath
|
||||
from reportlab.graphics.widgetbase import Widget
|
||||
|
||||
def frange(start, end=None, inc=None):
|
||||
"A range function, that does accept float increments..."
|
||||
|
||||
if end == None:
|
||||
end = start + 0.0
|
||||
start = 0.0
|
||||
|
||||
if inc == None:
|
||||
inc = 1.0
|
||||
|
||||
L = []
|
||||
end = end - inc*0.0001 #to avoid numrical problems
|
||||
while 1:
|
||||
next = start + len(L) * inc
|
||||
if inc > 0 and next >= end:
|
||||
break
|
||||
elif inc < 0 and next <= end:
|
||||
break
|
||||
L.append(next)
|
||||
|
||||
return L
|
||||
|
||||
|
||||
def makeDistancesList(list):
|
||||
"""Returns a list of distances between adjacent numbers in some input list.
|
||||
|
||||
E.g. [1, 1, 2, 3, 5, 7] -> [0, 1, 1, 2, 2]
|
||||
"""
|
||||
|
||||
d = []
|
||||
for i in range(len(list[:-1])):
|
||||
d.append(list[i+1] - list[i])
|
||||
|
||||
return d
|
||||
|
||||
|
||||
class Grid(Widget):
|
||||
"""This makes a rectangular grid of equidistant stripes.
|
||||
|
||||
The grid contains an outer border rectangle, and stripes
|
||||
inside which can be drawn with lines and/or as solid tiles.
|
||||
The drawing order is: outer rectangle, then lines and tiles.
|
||||
|
||||
The stripes' width is indicated as 'delta'. The sequence of
|
||||
stripes can have an offset named 'delta0'. Both values need
|
||||
to be positive!
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber, desc="The grid's lower-left x position."),
|
||||
y = AttrMapValue(isNumber, desc="The grid's lower-left y position."),
|
||||
width = AttrMapValue(isNumber, desc="The grid's width."),
|
||||
height = AttrMapValue(isNumber, desc="The grid's height."),
|
||||
orientation = AttrMapValue(OneOf(('vertical', 'horizontal')),
|
||||
desc='Determines if stripes are vertical or horizontal.'),
|
||||
useLines = AttrMapValue(OneOf((0, 1)),
|
||||
desc='Determines if stripes are drawn with lines.'),
|
||||
useRects = AttrMapValue(OneOf((0, 1)),
|
||||
desc='Determines if stripes are drawn with solid rectangles.'),
|
||||
delta = AttrMapValue(isNumber,
|
||||
desc='Determines the width/height of the stripes.'),
|
||||
delta0 = AttrMapValue(isNumber,
|
||||
desc='Determines the stripes initial width/height offset.'),
|
||||
deltaSteps = AttrMapValue(isListOfNumbers,
|
||||
desc='List of deltas to be used cyclically.'),
|
||||
stripeColors = AttrMapValue(isListOfColors,
|
||||
desc='Colors applied cyclically in the right or upper direction.'),
|
||||
fillColor = AttrMapValue(isColorOrNone,
|
||||
desc='Background color for entire rectangle.'),
|
||||
strokeColor = AttrMapValue(isColorOrNone,
|
||||
desc='Color used for lines.'),
|
||||
strokeWidth = AttrMapValue(isNumber,
|
||||
desc='Width used for lines.'),
|
||||
rectStrokeColor = AttrMapValue(isColorOrNone, desc='Color for outer rect stroke.'),
|
||||
rectStrokeWidth = AttrMapValue(isColorOrNone, desc='Width for outer rect stroke.'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.width = 100
|
||||
self.height = 100
|
||||
self.orientation = 'vertical'
|
||||
self.useLines = 0
|
||||
self.useRects = 1
|
||||
self.delta = 20
|
||||
self.delta0 = 0
|
||||
self.deltaSteps = []
|
||||
self.fillColor = colors.white
|
||||
self.stripeColors = [colors.red, colors.green, colors.blue]
|
||||
self.strokeColor = colors.black
|
||||
self.strokeWidth = 2
|
||||
|
||||
|
||||
def demo(self):
|
||||
D = Drawing(100, 100)
|
||||
|
||||
g = Grid()
|
||||
D.add(g)
|
||||
|
||||
return D
|
||||
|
||||
def makeOuterRect(self):
|
||||
strokeColor = getattr(self,'rectStrokeColor',self.strokeColor)
|
||||
strokeWidth = getattr(self,'rectStrokeWidth',self.strokeWidth)
|
||||
if self.fillColor or (strokeColor and strokeWidth):
|
||||
rect = Rect(self.x, self.y, self.width, self.height)
|
||||
rect.fillColor = self.fillColor
|
||||
rect.strokeColor = strokeColor
|
||||
rect.strokeWidth = strokeWidth
|
||||
return rect
|
||||
else:
|
||||
return None
|
||||
|
||||
def makeLinePosList(self, start, isX=0):
|
||||
"Returns a list of positions where to place lines."
|
||||
|
||||
w, h = self.width, self.height
|
||||
if isX:
|
||||
length = w
|
||||
else:
|
||||
length = h
|
||||
if self.deltaSteps:
|
||||
r = [start + self.delta0]
|
||||
i = 0
|
||||
while 1:
|
||||
if r[-1] > start + length:
|
||||
del r[-1]
|
||||
break
|
||||
r.append(r[-1] + self.deltaSteps[i % len(self.deltaSteps)])
|
||||
i = i + 1
|
||||
else:
|
||||
r = frange(start + self.delta0, start + length, self.delta)
|
||||
|
||||
r.append(start + length)
|
||||
if self.delta0 != 0:
|
||||
r.insert(0, start)
|
||||
#print 'Grid.makeLinePosList() -> %s' % r
|
||||
return r
|
||||
|
||||
|
||||
def makeInnerLines(self):
|
||||
# inner grid lines
|
||||
group = Group()
|
||||
|
||||
w, h = self.width, self.height
|
||||
|
||||
if self.useLines == 1:
|
||||
if self.orientation == 'vertical':
|
||||
r = self.makeLinePosList(self.x, isX=1)
|
||||
for x in r:
|
||||
line = Line(x, self.y, x, self.y + h)
|
||||
line.strokeColor = self.strokeColor
|
||||
line.strokeWidth = self.strokeWidth
|
||||
group.add(line)
|
||||
elif self.orientation == 'horizontal':
|
||||
r = self.makeLinePosList(self.y, isX=0)
|
||||
for y in r:
|
||||
line = Line(self.x, y, self.x + w, y)
|
||||
line.strokeColor = self.strokeColor
|
||||
line.strokeWidth = self.strokeWidth
|
||||
group.add(line)
|
||||
|
||||
return group
|
||||
|
||||
|
||||
def makeInnerTiles(self):
|
||||
# inner grid lines
|
||||
group = Group()
|
||||
|
||||
w, h = self.width, self.height
|
||||
|
||||
# inner grid stripes (solid rectangles)
|
||||
if self.useRects == 1:
|
||||
cols = self.stripeColors
|
||||
|
||||
if self.orientation == 'vertical':
|
||||
r = self.makeLinePosList(self.x, isX=1)
|
||||
elif self.orientation == 'horizontal':
|
||||
r = self.makeLinePosList(self.y, isX=0)
|
||||
|
||||
dist = makeDistancesList(r)
|
||||
|
||||
i = 0
|
||||
for j in range(len(dist)):
|
||||
if self.orientation == 'vertical':
|
||||
x = r[j]
|
||||
stripe = Rect(x, self.y, dist[j], h)
|
||||
elif self.orientation == 'horizontal':
|
||||
y = r[j]
|
||||
stripe = Rect(self.x, y, w, dist[j])
|
||||
stripe.fillColor = cols[i % len(cols)]
|
||||
stripe.strokeColor = None
|
||||
group.add(stripe)
|
||||
i = i + 1
|
||||
|
||||
return group
|
||||
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
group = Group()
|
||||
|
||||
group.add(self.makeOuterRect())
|
||||
group.add(self.makeInnerTiles())
|
||||
group.add(self.makeInnerLines(),name='_gridLines')
|
||||
|
||||
return group
|
||||
|
||||
|
||||
class DoubleGrid(Widget):
|
||||
"""This combines two ordinary Grid objects orthogonal to each other.
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber, desc="The grid's lower-left x position."),
|
||||
y = AttrMapValue(isNumber, desc="The grid's lower-left y position."),
|
||||
width = AttrMapValue(isNumber, desc="The grid's width."),
|
||||
height = AttrMapValue(isNumber, desc="The grid's height."),
|
||||
grid0 = AttrMapValue(None, desc="The first grid component."),
|
||||
grid1 = AttrMapValue(None, desc="The second grid component."),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.width = 100
|
||||
self.height = 100
|
||||
|
||||
g0 = Grid()
|
||||
g0.x = self.x
|
||||
g0.y = self.y
|
||||
g0.width = self.width
|
||||
g0.height = self.height
|
||||
g0.orientation = 'vertical'
|
||||
g0.useLines = 1
|
||||
g0.useRects = 0
|
||||
g0.delta = 20
|
||||
g0.delta0 = 0
|
||||
g0.deltaSteps = []
|
||||
g0.fillColor = colors.white
|
||||
g0.stripeColors = [colors.red, colors.green, colors.blue]
|
||||
g0.strokeColor = colors.black
|
||||
g0.strokeWidth = 1
|
||||
|
||||
g1 = Grid()
|
||||
g1.x = self.x
|
||||
g1.y = self.y
|
||||
g1.width = self.width
|
||||
g1.height = self.height
|
||||
g1.orientation = 'horizontal'
|
||||
g1.useLines = 1
|
||||
g1.useRects = 0
|
||||
g1.delta = 20
|
||||
g1.delta0 = 0
|
||||
g1.deltaSteps = []
|
||||
g1.fillColor = colors.white
|
||||
g1.stripeColors = [colors.red, colors.green, colors.blue]
|
||||
g1.strokeColor = colors.black
|
||||
g1.strokeWidth = 1
|
||||
|
||||
self.grid0 = g0
|
||||
self.grid1 = g1
|
||||
|
||||
|
||||
## # This gives an AttributeError:
|
||||
## # DoubleGrid instance has no attribute 'grid0'
|
||||
## def __setattr__(self, name, value):
|
||||
## if name in ('x', 'y', 'width', 'height'):
|
||||
## setattr(self.grid0, name, value)
|
||||
## setattr(self.grid1, name, value)
|
||||
|
||||
|
||||
def demo(self):
|
||||
D = Drawing(100, 100)
|
||||
g = DoubleGrid()
|
||||
D.add(g)
|
||||
return D
|
||||
|
||||
|
||||
def draw(self):
|
||||
group = Group()
|
||||
g0, g1 = self.grid0, self.grid1
|
||||
# Order groups to make sure both v and h lines
|
||||
# are visible (works only when there is only
|
||||
# one kind of stripes, v or h).
|
||||
G = g0.useRects == 1 and g1.useRects == 0 and (g0,g1) or (g1,g0)
|
||||
for g in G:
|
||||
group.add(g.makeOuterRect())
|
||||
for g in G:
|
||||
group.add(g.makeInnerTiles())
|
||||
group.add(g.makeInnerLines(),name='_gridLines')
|
||||
|
||||
return group
|
||||
|
||||
|
||||
class ShadedRect(Widget):
|
||||
"""This makes a rectangle with shaded colors between two colors.
|
||||
|
||||
Colors are interpolated linearly between 'fillColorStart'
|
||||
and 'fillColorEnd', both of which appear at the margins.
|
||||
If 'numShades' is set to one, though, only 'fillColorStart'
|
||||
is used.
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber, desc="The grid's lower-left x position."),
|
||||
y = AttrMapValue(isNumber, desc="The grid's lower-left y position."),
|
||||
width = AttrMapValue(isNumber, desc="The grid's width."),
|
||||
height = AttrMapValue(isNumber, desc="The grid's height."),
|
||||
orientation = AttrMapValue(OneOf(('vertical', 'horizontal')), desc='Determines if stripes are vertical or horizontal.'),
|
||||
numShades = AttrMapValue(isNumber, desc='The number of interpolating colors.'),
|
||||
fillColorStart = AttrMapValue(isColorOrNone, desc='Start value of the color shade.'),
|
||||
fillColorEnd = AttrMapValue(isColorOrNone, desc='End value of the color shade.'),
|
||||
strokeColor = AttrMapValue(isColorOrNone, desc='Color used for border line.'),
|
||||
strokeWidth = AttrMapValue(isNumber, desc='Width used for lines.'),
|
||||
cylinderMode = AttrMapValue(isBoolean, desc='True if shading reverses in middle.'),
|
||||
)
|
||||
|
||||
def __init__(self,**kw):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.width = 100
|
||||
self.height = 100
|
||||
self.orientation = 'vertical'
|
||||
self.numShades = 20
|
||||
self.fillColorStart = colors.pink
|
||||
self.fillColorEnd = colors.black
|
||||
self.strokeColor = colors.black
|
||||
self.strokeWidth = 2
|
||||
self.cylinderMode = 0
|
||||
self.setProperties(kw)
|
||||
|
||||
def demo(self):
|
||||
D = Drawing(100, 100)
|
||||
g = ShadedRect()
|
||||
D.add(g)
|
||||
|
||||
return D
|
||||
|
||||
def _flipRectCorners(self):
|
||||
"Flip rectangle's corners if width or height is negative."
|
||||
x, y, width, height, fillColorStart, fillColorEnd = self.x, self.y, self.width, self.height, self.fillColorStart, self.fillColorEnd
|
||||
if width < 0 and height > 0:
|
||||
x = x + width
|
||||
width = -width
|
||||
if self.orientation=='vertical': fillColorStart, fillColorEnd = fillColorEnd, fillColorStart
|
||||
elif height<0 and width>0:
|
||||
y = y + height
|
||||
height = -height
|
||||
if self.orientation=='horizontal': fillColorStart, fillColorEnd = fillColorEnd, fillColorStart
|
||||
elif height < 0 and height < 0:
|
||||
x = x + width
|
||||
width = -width
|
||||
y = y + height
|
||||
height = -height
|
||||
return x, y, width, height, fillColorStart, fillColorEnd
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
group = Group()
|
||||
x, y, w, h, c0, c1 = self._flipRectCorners()
|
||||
numShades = self.numShades
|
||||
if self.cylinderMode:
|
||||
if not numShades%2: numShades = numShades+1
|
||||
halfNumShades = (numShades-1)/2 + 1
|
||||
num = float(numShades) # must make it float!
|
||||
vertical = self.orientation == 'vertical'
|
||||
if vertical:
|
||||
if numShades == 1:
|
||||
V = [x]
|
||||
else:
|
||||
V = frange(x, x + w, w/num)
|
||||
else:
|
||||
if numShades == 1:
|
||||
V = [y]
|
||||
else:
|
||||
V = frange(y, y + h, h/num)
|
||||
|
||||
for v in V:
|
||||
stripe = vertical and Rect(v, y, w/num, h) or Rect(x, v, w, h/num)
|
||||
if self.cylinderMode:
|
||||
if V.index(v)>=halfNumShades:
|
||||
col = colors.linearlyInterpolatedColor(c1,c0,V[halfNumShades],V[-1], v)
|
||||
else:
|
||||
col = colors.linearlyInterpolatedColor(c0,c1,V[0],V[halfNumShades], v)
|
||||
else:
|
||||
col = colors.linearlyInterpolatedColor(c0,c1,V[0],V[-1], v)
|
||||
stripe.fillColor = col
|
||||
stripe.strokeColor = col
|
||||
stripe.strokeWidth = 1
|
||||
group.add(stripe)
|
||||
if self.strokeColor and self.strokeWidth>=0:
|
||||
rect = Rect(x, y, w, h)
|
||||
rect.strokeColor = self.strokeColor
|
||||
rect.strokeWidth = self.strokeWidth
|
||||
rect.fillColor = None
|
||||
group.add(rect)
|
||||
return group
|
||||
|
||||
|
||||
def colorRange(c0, c1, n):
|
||||
"Return a range of intermediate colors between c0 and c1"
|
||||
if n==1: return [c0]
|
||||
|
||||
C = []
|
||||
if n>1:
|
||||
lim = n-1
|
||||
for i in range(n):
|
||||
C.append(colors.linearlyInterpolatedColor(c0,c1,0,lim, i))
|
||||
return C
|
||||
|
||||
|
||||
def centroid(P):
|
||||
'''compute average point of a set of points'''
|
||||
return reduce(lambda x,y, fn=float(len(P)): (x[0]+y[0]/fn,x[1]+y[1]/fn),P,(0,0))
|
||||
|
||||
def rotatedEnclosingRect(P, angle, rect):
|
||||
'''
|
||||
given P a sequence P of x,y coordinate pairs and an angle in degrees
|
||||
find the centroid of P and the axis at angle theta through it
|
||||
find the extreme points of P wrt axis parallel distance and axis
|
||||
orthogonal distance. Then compute the least rectangle that will still
|
||||
enclose P when rotated by angle.
|
||||
|
||||
The class R
|
||||
'''
|
||||
from math import pi, cos, sin, tan
|
||||
x0, y0 = centroid(P)
|
||||
theta = (angle/180.)*pi
|
||||
s,c=sin(theta),cos(theta)
|
||||
def parallelAxisDist((x,y),s=s,c=c,x0=x0,y0=y0):
|
||||
return (s*(y-y0)+c*(x-x0))
|
||||
def orthogonalAxisDist((x,y),s=s,c=c,x0=x0,y0=y0):
|
||||
return (c*(y-y0)+s*(x-x0))
|
||||
L = map(parallelAxisDist,P)
|
||||
L.sort()
|
||||
a0, a1 = L[0], L[-1]
|
||||
L = map(orthogonalAxisDist,P)
|
||||
L.sort()
|
||||
b0, b1 = L[0], L[-1]
|
||||
rect.x, rect.width = a0, a1-a0
|
||||
rect.y, rect.height = b0, b1-b0
|
||||
g = Group(transform=(c,s,-s,c,x0,y0))
|
||||
g.add(rect)
|
||||
return g
|
||||
|
||||
class ShadedPolygon(Widget,LineShape):
|
||||
_attrMap = AttrMap(BASE=LineShape,
|
||||
angle = AttrMapValue(isNumber,desc="Shading angle"),
|
||||
fillColorStart = AttrMapValue(isColorOrNone),
|
||||
fillColorEnd = AttrMapValue(isColorOrNone),
|
||||
numShades = AttrMapValue(isNumber, desc='The number of interpolating colors.'),
|
||||
cylinderMode = AttrMapValue(isBoolean, desc='True if shading reverses in middle.'),
|
||||
points = AttrMapValue(isListOfNumbers),
|
||||
)
|
||||
|
||||
def __init__(self,**kw):
|
||||
self.angle = 90
|
||||
self.fillColorStart = colors.red
|
||||
self.fillColorEnd = colors.green
|
||||
self.cylinderMode = 0
|
||||
self.numShades = 50
|
||||
self.points = [-1,-1,2,2,3,-1]
|
||||
LineShape.__init__(self,kw)
|
||||
|
||||
def draw(self):
|
||||
P = self.points
|
||||
P = map(lambda i, P=P:(P[i],P[i+1]),xrange(0,len(P),2))
|
||||
path = definePath([('moveTo',)+P[0]]+map(lambda x: ('lineTo',)+x,P[1:])+['closePath'],
|
||||
fillColor=None, strokeColor=None)
|
||||
path.isClipPath = 1
|
||||
g = Group()
|
||||
g.add(path)
|
||||
rect = ShadedRect(strokeWidth=0,strokeColor=None)
|
||||
for k in 'fillColorStart', 'fillColorEnd', 'numShades', 'cylinderMode':
|
||||
setattr(rect,k,getattr(self,k))
|
||||
g.add(rotatedEnclosingRect(P, self.angle, rect))
|
||||
g.add(EmptyClipPath)
|
||||
path = path.copy()
|
||||
path.isClipPath = 0
|
||||
path.strokeColor = self.strokeColor
|
||||
path.strokeWidth = self.strokeWidth
|
||||
g.add(path)
|
||||
return g
|
||||
|
||||
if __name__=='__main__': #noruntests
|
||||
from reportlab.lib.colors import blue
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
angle=45
|
||||
D = Drawing(120,120)
|
||||
D.add(ShadedPolygon(points=(10,10,60,60,110,10),strokeColor=None,strokeWidth=1,angle=90,numShades=50,cylinderMode=0))
|
||||
D.save(formats=['gif'],fnRoot='shobj',outDir='/tmp')
|
|
@ -1,228 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/widgets/markers.py
|
||||
"""
|
||||
This modules defines a collection of markers used in charts.
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
from types import FunctionType, ClassType
|
||||
from reportlab.graphics.shapes import Rect, Line, Circle, Polygon, Drawing, Group
|
||||
from reportlab.graphics.widgets.signsandsymbols import SmileyFace
|
||||
from reportlab.graphics.widgetbase import Widget
|
||||
from reportlab.lib.validators import isNumber, isColorOrNone, OneOf, Validator
|
||||
from reportlab.lib.attrmap import AttrMap, AttrMapValue
|
||||
from reportlab.lib.colors import black
|
||||
from reportlab.graphics.widgets.flags import Flag
|
||||
from math import sin, cos, pi
|
||||
import copy, new
|
||||
_toradians = pi/180.0
|
||||
|
||||
class Marker(Widget):
|
||||
'''A polymorphic class of markers'''
|
||||
_attrMap = AttrMap(BASE=Widget,
|
||||
kind = AttrMapValue(
|
||||
OneOf(None, 'Square', 'Diamond', 'Circle', 'Cross', 'Triangle', 'StarSix',
|
||||
'Pentagon', 'Hexagon', 'Heptagon', 'Octagon', 'StarFive',
|
||||
'FilledSquare', 'FilledCircle', 'FilledDiamond', 'FilledCross',
|
||||
'FilledTriangle','FilledStarSix', 'FilledPentagon', 'FilledHexagon',
|
||||
'FilledHeptagon', 'FilledOctagon', 'FilledStarFive',
|
||||
'Smiley'),
|
||||
desc='marker type name'),
|
||||
size = AttrMapValue(isNumber,desc='marker size'),
|
||||
x = AttrMapValue(isNumber,desc='marker x coordinate'),
|
||||
y = AttrMapValue(isNumber,desc='marker y coordinate'),
|
||||
dx = AttrMapValue(isNumber,desc='marker x coordinate adjustment'),
|
||||
dy = AttrMapValue(isNumber,desc='marker y coordinate adjustment'),
|
||||
angle = AttrMapValue(isNumber,desc='marker rotation'),
|
||||
fillColor = AttrMapValue(isColorOrNone, desc='marker fill colour'),
|
||||
strokeColor = AttrMapValue(isColorOrNone, desc='marker stroke colour'),
|
||||
strokeWidth = AttrMapValue(isNumber, desc='marker stroke width'),
|
||||
)
|
||||
|
||||
def __init__(self,*args,**kw):
|
||||
self.kind = None
|
||||
self.strokeColor = black
|
||||
self.strokeWidth = 0.1
|
||||
self.fillColor = None
|
||||
self.size = 5
|
||||
self.x = self.y = self.dx = self.dy = self.angle = 0
|
||||
self.setProperties(kw)
|
||||
|
||||
def clone(self):
|
||||
return new.instance(self.__class__,self.__dict__.copy())
|
||||
|
||||
def _Smiley(self):
|
||||
x, y = self.x+self.dx, self.y+self.dy
|
||||
d = self.size/2.0
|
||||
s = SmileyFace()
|
||||
s.fillColor = self.fillColor
|
||||
s.strokeWidth = self.strokeWidth
|
||||
s.strokeColor = self.strokeColor
|
||||
s.x = x-d
|
||||
s.y = y-d
|
||||
s.size = d*2
|
||||
return s
|
||||
|
||||
def _Square(self):
|
||||
x, y = self.x+self.dx, self.y+self.dy
|
||||
d = self.size/2.0
|
||||
s = Rect(x-d,y-d,2*d,2*d,fillColor=self.fillColor,strokeColor=self.strokeColor,strokeWidth=self.strokeWidth)
|
||||
return s
|
||||
|
||||
def _Diamond(self):
|
||||
d = self.size/2.0
|
||||
return self._doPolygon((-d,0,0,d,d,0,0,-d))
|
||||
|
||||
def _Circle(self):
|
||||
x, y = self.x+self.dx, self.y+self.dy
|
||||
s = Circle(x,y,self.size/2.0,fillColor=self.fillColor,strokeColor=self.strokeColor,strokeWidth=self.strokeWidth)
|
||||
return s
|
||||
|
||||
def _Cross(self):
|
||||
x, y = self.x+self.dx, self.y+self.dy
|
||||
s = float(self.size)
|
||||
h, s = s/2, s/6
|
||||
return self._doPolygon((-s,-h,-s,-s,-h,-s,-h,s,-s,s,-s,h,s,h,s,s,h,s,h,-s,s,-s,s,-h))
|
||||
|
||||
def _Triangle(self):
|
||||
x, y = self.x+self.dx, self.y+self.dy
|
||||
r = float(self.size)/2
|
||||
c = 30*_toradians
|
||||
s = sin(30*_toradians)*r
|
||||
c = cos(c)*r
|
||||
return self._doPolygon((0,r,-c,-s,c,-s))
|
||||
|
||||
def _StarSix(self):
|
||||
r = float(self.size)/2
|
||||
c = 30*_toradians
|
||||
s = sin(c)*r
|
||||
c = cos(c)*r
|
||||
z = s/2
|
||||
g = c/2
|
||||
return self._doPolygon((0,r,-z,s,-c,s,-s,0,-c,-s,-z,-s,0,-r,z,-s,c,-s,s,0,c,s,z,s))
|
||||
|
||||
def _StarFive(self):
|
||||
R = float(self.size)/2
|
||||
r = R*sin(18*_toradians)/cos(36*_toradians)
|
||||
P = []
|
||||
angle = 90
|
||||
for i in xrange(5):
|
||||
for radius in R, r:
|
||||
theta = angle*_toradians
|
||||
P.append(radius*cos(theta))
|
||||
P.append(radius*sin(theta))
|
||||
angle = angle + 36
|
||||
return self._doPolygon(P)
|
||||
|
||||
def _Pentagon(self):
|
||||
return self._doNgon(5)
|
||||
|
||||
def _Hexagon(self):
|
||||
return self._doNgon(6)
|
||||
|
||||
def _Heptagon(self):
|
||||
return self._doNgon(7)
|
||||
|
||||
def _Octagon(self):
|
||||
return self._doNgon(8)
|
||||
|
||||
def _doPolygon(self,P):
|
||||
x, y = self.x+self.dx, self.y+self.dy
|
||||
if x or y: P = map(lambda i,P=P,A=[x,y]: P[i] + A[i&1], range(len(P)))
|
||||
return Polygon(P, strokeWidth =self.strokeWidth, strokeColor=self.strokeColor, fillColor=self.fillColor)
|
||||
|
||||
def _doFill(self):
|
||||
old = self.fillColor
|
||||
if old is None:
|
||||
self.fillColor = self.strokeColor
|
||||
r = (self.kind and getattr(self,'_'+self.kind[6:]) or Group)()
|
||||
self.fillColor = old
|
||||
return r
|
||||
|
||||
def _doNgon(self,n):
|
||||
P = []
|
||||
size = float(self.size)/2
|
||||
for i in xrange(n):
|
||||
r = (2.*i/n+0.5)*pi
|
||||
P.append(size*cos(r))
|
||||
P.append(size*sin(r))
|
||||
return self._doPolygon(P)
|
||||
|
||||
_FilledCircle = _doFill
|
||||
_FilledSquare = _doFill
|
||||
_FilledDiamond = _doFill
|
||||
_FilledCross = _doFill
|
||||
_FilledTriangle = _doFill
|
||||
_FilledStarSix = _doFill
|
||||
_FilledPentagon = _doFill
|
||||
_FilledHexagon = _doFill
|
||||
_FilledHeptagon = _doFill
|
||||
_FilledOctagon = _doFill
|
||||
_FilledStarFive = _doFill
|
||||
|
||||
def draw(self):
|
||||
if self.kind:
|
||||
m = getattr(self,'_'+self.kind)
|
||||
if self.angle:
|
||||
_x, _dx, _y, _dy = self.x, self.dx, self.y, self.dy
|
||||
self.x, self.dx, self.y, self.dy = 0,0,0,0
|
||||
try:
|
||||
m = m()
|
||||
finally:
|
||||
self.x, self.dx, self.y, self.dy = _x, _dx, _y, _dy
|
||||
if not isinstance(m,Group):
|
||||
_m, m = m, Group()
|
||||
m.add(_m)
|
||||
if self.angle: m.rotate(self.angle)
|
||||
x, y = _x+_dx, _y+_dy
|
||||
if x or y: m.shift(x,y)
|
||||
else:
|
||||
m = m()
|
||||
else:
|
||||
m = Group()
|
||||
return m
|
||||
|
||||
def uSymbol2Symbol(uSymbol,x,y,color):
|
||||
if type(uSymbol) == FunctionType:
|
||||
symbol = uSymbol(x, y, 5, color)
|
||||
elif type(uSymbol) == ClassType and issubclass(uSymbol,Widget):
|
||||
size = 10.
|
||||
symbol = uSymbol()
|
||||
symbol.x = x - (size/2)
|
||||
symbol.y = y - (size/2)
|
||||
try:
|
||||
symbol.size = size
|
||||
symbol.color = color
|
||||
except:
|
||||
pass
|
||||
elif isinstance(uSymbol,Marker) or isinstance(uSymbol,Flag):
|
||||
symbol = uSymbol.clone()
|
||||
if isinstance(uSymbol,Marker): symbol.fillColor = symbol.fillColor or color
|
||||
symbol.x, symbol.y = x, y
|
||||
else:
|
||||
symbol = None
|
||||
return symbol
|
||||
|
||||
class _isSymbol(Validator):
|
||||
def test(self,x):
|
||||
return callable(x) or isinstance(x,Marker) or isinstance(x,Flag) \
|
||||
or (type(x)==ClassType and issubclass(x,Widget))
|
||||
|
||||
isSymbol = _isSymbol()
|
||||
|
||||
def makeMarker(name,**kw):
|
||||
if Marker._attrMap['kind'].validate(name):
|
||||
m = apply(Marker,(),kw)
|
||||
m.kind = name
|
||||
elif name[-5:]=='_Flag' and Flag._attrMap['kind'].validate(name[:-5]):
|
||||
m = apply(Flag,(),kw)
|
||||
m.kind = name[:-5]
|
||||
m.size = 10
|
||||
else:
|
||||
raise ValueError, "Invalid marker name %s" % name
|
||||
return m
|
||||
|
||||
if __name__=='__main__':
|
||||
D = Drawing()
|
||||
D.add(Marker())
|
||||
D.save(fnRoot='Marker',formats=['pdf'], outDir='/tmp')
|
|
@ -1,919 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/widgets/signsandsymbols.py
|
||||
# signsandsymbols.py
|
||||
# A collection of new widgets
|
||||
# author: John Precedo (johnp@reportlab.com)
|
||||
"""This file is a collection of widgets to produce some common signs and symbols.
|
||||
|
||||
Widgets include:
|
||||
- ETriangle (an equilateral triangle),
|
||||
- RTriangle (a right angled triangle),
|
||||
- Octagon,
|
||||
- Crossbox,
|
||||
- Tickbox,
|
||||
- SmileyFace,
|
||||
- StopSign,
|
||||
- NoEntry,
|
||||
- NotAllowed (the red roundel from 'no smoking' signs),
|
||||
- NoSmoking,
|
||||
- DangerSign (a black exclamation point in a yellow triangle),
|
||||
- YesNo (returns a tickbox or a crossbox depending on a testvalue),
|
||||
- FloppyDisk,
|
||||
- ArrowOne, and
|
||||
- ArrowTwo
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.validators import *
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.graphics import shapes
|
||||
from reportlab.graphics.widgetbase import Widget
|
||||
from reportlab.graphics import renderPDF
|
||||
|
||||
|
||||
class _Symbol(Widget):
|
||||
"""Abstract base widget
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'fillColor', 'strokeColor'
|
||||
"""
|
||||
_nodoc = 1
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber,desc='symbol x coordinate'),
|
||||
y = AttrMapValue(isNumber,desc='symbol y coordinate'),
|
||||
dx = AttrMapValue(isNumber,desc='symbol x coordinate adjustment'),
|
||||
dy = AttrMapValue(isNumber,desc='symbol x coordinate adjustment'),
|
||||
size = AttrMapValue(isNumber),
|
||||
fillColor = AttrMapValue(isColorOrNone),
|
||||
strokeColor = AttrMapValue(isColorOrNone),
|
||||
strokeWidth = AttrMapValue(isNumber),
|
||||
)
|
||||
def __init__(self):
|
||||
assert self.__class__.__name__!='_Symbol', 'Abstract class _Symbol instantiated'
|
||||
self.x = self.y = self.dx = self.dy = 0
|
||||
self.size = 100
|
||||
self.fillColor = colors.red
|
||||
self.strokeColor = None
|
||||
self.strokeWidth = 0.1
|
||||
|
||||
def demo(self):
|
||||
D = shapes.Drawing(200, 100)
|
||||
s = float(self.size)
|
||||
ob = self.__class__()
|
||||
ob.x=50
|
||||
ob.y=0
|
||||
ob.draw()
|
||||
D.add(ob)
|
||||
D.add(shapes.String(ob.x+(s/2),(ob.y-12),
|
||||
ob.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=10))
|
||||
return D
|
||||
|
||||
class ETriangle(_Symbol):
|
||||
"""This draws an equilateral triangle."""
|
||||
|
||||
def __init__(self):
|
||||
pass #AbstractSymbol
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
|
||||
# Triangle specific bits
|
||||
ae = s*0.125 #(ae = 'an eighth')
|
||||
triangle = shapes.Polygon(points = [
|
||||
self.x, self.y,
|
||||
self.x+s, self.y,
|
||||
self.x+(s/2),self.y+s],
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = self.strokeColor,
|
||||
strokeWidth=s/50.)
|
||||
g.add(triangle)
|
||||
return g
|
||||
|
||||
class RTriangle(_Symbol):
|
||||
"""This draws a right-angled triangle.
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'fillColor', 'strokeColor'
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.fillColor = colors.green
|
||||
self.strokeColor = None
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
|
||||
# Triangle specific bits
|
||||
ae = s*0.125 #(ae = 'an eighth')
|
||||
triangle = shapes.Polygon(points = [
|
||||
self.x, self.y,
|
||||
self.x+s, self.y,
|
||||
self.x,self.y+s],
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = self.strokeColor,
|
||||
strokeWidth=s/50.)
|
||||
g.add(triangle)
|
||||
return g
|
||||
|
||||
class Octagon(_Symbol):
|
||||
"""This widget draws an Octagon.
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'fillColor', 'strokeColor'
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.fillColor = colors.yellow
|
||||
self.strokeColor = None
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
|
||||
# Octagon specific bits
|
||||
athird=s/3
|
||||
|
||||
octagon = shapes.Polygon(points=[self.x+athird, self.y,
|
||||
self.x, self.y+athird,
|
||||
self.x, self.y+(athird*2),
|
||||
self.x+athird, self.y+s,
|
||||
self.x+(athird*2), self.y+s,
|
||||
self.x+s, self.y+(athird*2),
|
||||
self.x+s, self.y+athird,
|
||||
self.x+(athird*2), self.y],
|
||||
strokeColor = self.strokeColor,
|
||||
fillColor = self.fillColor,
|
||||
strokeWidth=10)
|
||||
g.add(octagon)
|
||||
return g
|
||||
|
||||
class Crossbox(_Symbol):
|
||||
"""This draws a black box with a red cross in it - a 'checkbox'.
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'crossColor', 'strokeColor', 'crosswidth'
|
||||
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(BASE=_Symbol,
|
||||
crossColor = AttrMapValue(isColorOrNone),
|
||||
crosswidth = AttrMapValue(isNumber),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.fillColor = colors.white
|
||||
self.crossColor = colors.red
|
||||
self.strokeColor = colors.black
|
||||
self.crosswidth = 10
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
|
||||
# crossbox specific bits
|
||||
box = shapes.Rect(self.x+1, self.y+1, s-2, s-2,
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = self.strokeColor,
|
||||
strokeWidth=2)
|
||||
g.add(box)
|
||||
|
||||
crossLine1 = shapes.Line(self.x+(s*0.15), self.y+(s*0.15), self.x+(s*0.85), self.y+(s*0.85),
|
||||
fillColor = self.crossColor,
|
||||
strokeColor = self.crossColor,
|
||||
strokeWidth = self.crosswidth)
|
||||
g.add(crossLine1)
|
||||
|
||||
crossLine2 = shapes.Line(self.x+(s*0.15), self.y+(s*0.85), self.x+(s*0.85) ,self.y+(s*0.15),
|
||||
fillColor = self.crossColor,
|
||||
strokeColor = self.crossColor,
|
||||
strokeWidth = self.crosswidth)
|
||||
g.add(crossLine2)
|
||||
|
||||
return g
|
||||
|
||||
|
||||
class Tickbox(_Symbol):
|
||||
"""This draws a black box with a red tick in it - another 'checkbox'.
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'tickColor', 'strokeColor', 'tickwidth'
|
||||
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(BASE=_Symbol,
|
||||
tickColor = AttrMapValue(isColorOrNone),
|
||||
tickwidth = AttrMapValue(isNumber),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.tickColor = colors.red
|
||||
self.strokeColor = colors.black
|
||||
self.fillColor = colors.white
|
||||
self.tickwidth = 10
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
|
||||
# tickbox specific bits
|
||||
box = shapes.Rect(self.x+1, self.y+1, s-2, s-2,
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = self.strokeColor,
|
||||
strokeWidth=2)
|
||||
g.add(box)
|
||||
|
||||
tickLine = shapes.PolyLine(points = [self.x+(s*0.15), self.y+(s*0.35), self.x+(s*0.35), self.y+(s*0.15),
|
||||
self.x+(s*0.35), self.y+(s*0.15), self.x+(s*0.85) ,self.y+(s*0.85)],
|
||||
fillColor = self.tickColor,
|
||||
strokeColor = self.tickColor,
|
||||
strokeWidth = self.tickwidth)
|
||||
g.add(tickLine)
|
||||
|
||||
return g
|
||||
|
||||
class SmileyFace(_Symbol):
|
||||
"""This draws a classic smiley face.
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'fillColor'
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
_Symbol.__init__(self)
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.fillColor = colors.yellow
|
||||
self.strokeColor = colors.black
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
|
||||
# SmileyFace specific bits
|
||||
g.add(shapes.Circle(cx=self.x+(s/2), cy=self.y+(s/2), r=s/2,
|
||||
fillColor=self.fillColor, strokeColor=self.strokeColor,
|
||||
strokeWidth=max(s/38.,self.strokeWidth)))
|
||||
|
||||
for i in (1,2):
|
||||
g.add(shapes.Ellipse(self.x+(s/3)*i,self.y+(s/3)*2, s/30, s/10,
|
||||
fillColor=self.strokeColor, strokeColor = self.strokeColor,
|
||||
strokeWidth=max(s/38.,self.strokeWidth)))
|
||||
|
||||
# calculate a pointslist for the mouth
|
||||
# THIS IS A HACK! - don't use if there is a 'shapes.Arc'
|
||||
centerx=self.x+(s/2)
|
||||
centery=self.y+(s/2)
|
||||
radius=s/3
|
||||
yradius = radius
|
||||
xradius = radius
|
||||
startangledegrees=200
|
||||
endangledegrees=340
|
||||
degreedelta = 1
|
||||
pointslist = []
|
||||
a = pointslist.append
|
||||
from math import sin, cos, pi
|
||||
degreestoradians = pi/180.0
|
||||
radiansdelta = degreedelta*degreestoradians
|
||||
startangle = startangledegrees*degreestoradians
|
||||
endangle = endangledegrees*degreestoradians
|
||||
while endangle<startangle:
|
||||
endangle = endangle+2*pi
|
||||
angle = startangle
|
||||
while angle<endangle:
|
||||
x = centerx + cos(angle)*radius
|
||||
y = centery + sin(angle)*yradius
|
||||
a(x); a(y)
|
||||
angle = angle+radiansdelta
|
||||
|
||||
# make the mouth
|
||||
smile = shapes.PolyLine(pointslist,
|
||||
fillColor = self.strokeColor,
|
||||
strokeColor = self.strokeColor,
|
||||
strokeWidth = max(s/38.,self.strokeWidth))
|
||||
g.add(smile)
|
||||
|
||||
return g
|
||||
|
||||
class StopSign(_Symbol):
|
||||
"""This draws a (British) stop sign.
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size'
|
||||
|
||||
"""
|
||||
_attrMap = AttrMap(BASE=_Symbol,
|
||||
stopColor = AttrMapValue(isColorOrNone,desc='color of the word stop'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.strokeColor = colors.black
|
||||
self.fillColor = colors.orangered
|
||||
self.stopColor = colors.ghostwhite
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
|
||||
# stop-sign specific bits
|
||||
athird=s/3
|
||||
|
||||
outerOctagon = shapes.Polygon(points=[self.x+athird, self.y,
|
||||
self.x, self.y+athird,
|
||||
self.x, self.y+(athird*2),
|
||||
self.x+athird, self.y+s,
|
||||
self.x+(athird*2), self.y+s,
|
||||
self.x+s, self.y+(athird*2),
|
||||
self.x+s, self.y+athird,
|
||||
self.x+(athird*2), self.y],
|
||||
strokeColor = self.strokeColor,
|
||||
fillColor = None,
|
||||
strokeWidth=1)
|
||||
g.add(outerOctagon)
|
||||
|
||||
innerOctagon = shapes.Polygon(points=[self.x+athird+(s/75), self.y+(s/75),
|
||||
self.x+(s/75), self.y+athird+(s/75),
|
||||
self.x+(s/75), self.y+(athird*2)-(s/75),
|
||||
self.x+athird+(s/75), self.y+s-(s/75),
|
||||
self.x+(athird*2)-(s/75), (self.y+s)-(s/75),
|
||||
(self.x+s)-(s/75), self.y+(athird*2)-(s/75),
|
||||
(self.x+s)-(s/75), self.y+athird+(s/75),
|
||||
self.x+(athird*2)-(s/75), self.y+(s/75)],
|
||||
strokeColor = None,
|
||||
fillColor = self.fillColor,
|
||||
strokeWidth=0)
|
||||
g.add(innerOctagon)
|
||||
|
||||
if self.stopColor:
|
||||
g.add(shapes.String(self.x+(s*0.5),self.y+(s*0.4),
|
||||
'STOP', fillColor=self.stopColor, textAnchor='middle',
|
||||
fontSize=s/3, fontName="Helvetica-Bold"))
|
||||
|
||||
return g
|
||||
|
||||
|
||||
class NoEntry(_Symbol):
|
||||
"""This draws a (British) No Entry sign - a red circle with a white line on it.
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size'
|
||||
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(BASE=_Symbol,
|
||||
innerBarColor = AttrMapValue(isColorOrNone,desc='color of the inner bar'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.strokeColor = colors.black
|
||||
self.fillColor = colors.orangered
|
||||
self.innerBarColor = colors.ghostwhite
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
|
||||
# no-entry-sign specific bits
|
||||
if self.strokeColor:
|
||||
g.add(shapes.Circle(cx = (self.x+(s/2)), cy = (self.y+(s/2)), r = s/2, fillColor = None, strokeColor = self.strokeColor, strokeWidth=1))
|
||||
|
||||
if self.fillColor:
|
||||
g.add(shapes.Circle(cx = (self.x+(s/2)), cy =(self.y+(s/2)), r = ((s/2)-(s/50)), fillColor = self.fillColor, strokeColor = None, strokeWidth=0))
|
||||
|
||||
innerBarColor = self.innerBarColor
|
||||
if innerBarColor:
|
||||
g.add(shapes.Rect(self.x+(s*0.1), self.y+(s*0.4), width=s*0.8, height=s*0.2, fillColor = innerBarColor, strokeColor = innerBarColor, strokeLineCap = 1, strokeWidth = 0))
|
||||
return g
|
||||
|
||||
class NotAllowed(_Symbol):
|
||||
"""This draws a 'forbidden' roundel (as used in the no-smoking sign).
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size'
|
||||
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(BASE=_Symbol,
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.strokeColor = colors.red
|
||||
self.fillColor = colors.white
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
strokeColor = self.strokeColor
|
||||
|
||||
# not=allowed specific bits
|
||||
outerCircle = shapes.Circle(cx = (self.x+(s/2)), cy = (self.y+(s/2)), r = (s/2)-(s/10), fillColor = self.fillColor, strokeColor = strokeColor, strokeWidth=s/10.)
|
||||
g.add(outerCircle)
|
||||
|
||||
centerx=self.x+s
|
||||
centery=self.y+(s/2)-(s/6)
|
||||
radius=s-(s/6)
|
||||
yradius = radius/2
|
||||
xradius = radius/2
|
||||
startangledegrees=100
|
||||
endangledegrees=-80
|
||||
degreedelta = 90
|
||||
pointslist = []
|
||||
a = pointslist.append
|
||||
from math import sin, cos, pi
|
||||
degreestoradians = pi/180.0
|
||||
radiansdelta = degreedelta*degreestoradians
|
||||
startangle = startangledegrees*degreestoradians
|
||||
endangle = endangledegrees*degreestoradians
|
||||
while endangle<startangle:
|
||||
endangle = endangle+2*pi
|
||||
angle = startangle
|
||||
while angle<endangle:
|
||||
x = centerx + cos(angle)*radius
|
||||
y = centery + sin(angle)*yradius
|
||||
a(x); a(y)
|
||||
angle = angle+radiansdelta
|
||||
crossbar = shapes.PolyLine(pointslist, fillColor = strokeColor, strokeColor = strokeColor, strokeWidth = s/10.)
|
||||
g.add(crossbar)
|
||||
return g
|
||||
|
||||
|
||||
class NoSmoking(NotAllowed):
|
||||
"""This draws a no-smoking sign.
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size'
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
NotAllowed.__init__(self)
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = NotAllowed.draw(self)
|
||||
|
||||
# no-smoking-sign specific bits
|
||||
newx = self.x+(s/2)-(s/3.5)
|
||||
newy = self.y+(s/2)-(s/32)
|
||||
cigarrette1 = shapes.Rect(x = newx, y = newy, width = (s/2), height =(s/16),
|
||||
fillColor = colors.ghostwhite, strokeColor = colors.gray, strokeWidth=0)
|
||||
newx=newx+(s/2)+(s/64)
|
||||
g.insert(-1,cigarrette1)
|
||||
|
||||
cigarrette2 = shapes.Rect(x = newx, y = newy, width = (s/80), height =(s/16),
|
||||
fillColor = colors.orangered, strokeColor = None, strokeWidth=0)
|
||||
newx= newx+(s/35)
|
||||
g.insert(-1,cigarrette2)
|
||||
|
||||
cigarrette3 = shapes.Rect(x = newx, y = newy, width = (s/80), height =(s/16),
|
||||
fillColor = colors.orangered, strokeColor = None, strokeWidth=0)
|
||||
newx= newx+(s/35)
|
||||
g.insert(-1,cigarrette3)
|
||||
|
||||
cigarrette4 = shapes.Rect(x = newx, y = newy, width = (s/80), height =(s/16),
|
||||
fillColor = colors.orangered, strokeColor = None, strokeWidth=0)
|
||||
newx= newx+(s/35)
|
||||
g.insert(-1,cigarrette4)
|
||||
|
||||
return g
|
||||
|
||||
|
||||
class DangerSign(_Symbol):
|
||||
"""This draws a 'danger' sign: a yellow box with a black exclamation point.
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'strokeColor', 'fillColor', 'strokeWidth'
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.strokeColor = colors.black
|
||||
self.fillColor = colors.gold
|
||||
self.strokeWidth = self.size*0.125
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
ew = self.strokeWidth
|
||||
ae = s*0.125 #(ae = 'an eighth')
|
||||
|
||||
|
||||
# danger sign specific bits
|
||||
|
||||
ew = self.strokeWidth
|
||||
ae = s*0.125 #(ae = 'an eighth')
|
||||
|
||||
outerTriangle = shapes.Polygon(points = [
|
||||
self.x, self.y,
|
||||
self.x+s, self.y,
|
||||
self.x+(s/2),self.y+s],
|
||||
fillColor = None,
|
||||
strokeColor = self.strokeColor,
|
||||
strokeWidth=0)
|
||||
g.add(outerTriangle)
|
||||
|
||||
innerTriangle = shapes.Polygon(points = [
|
||||
self.x+(s/50), self.y+(s/75),
|
||||
(self.x+s)-(s/50), self.y+(s/75),
|
||||
self.x+(s/2),(self.y+s)-(s/50)],
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(innerTriangle)
|
||||
|
||||
exmark = shapes.Polygon(points=[
|
||||
((self.x+s/2)-ew/2), self.y+ae*2.5,
|
||||
((self.x+s/2)+ew/2), self.y+ae*2.5,
|
||||
((self.x+s/2)+((ew/2))+(ew/6)), self.y+ae*5.5,
|
||||
((self.x+s/2)-((ew/2))-(ew/6)), self.y+ae*5.5],
|
||||
fillColor = self.strokeColor,
|
||||
strokeColor = None)
|
||||
g.add(exmark)
|
||||
|
||||
exdot = shapes.Polygon(points=[
|
||||
((self.x+s/2)-ew/2), self.y+ae,
|
||||
((self.x+s/2)+ew/2), self.y+ae,
|
||||
((self.x+s/2)+ew/2), self.y+ae*2,
|
||||
((self.x+s/2)-ew/2), self.y+ae*2],
|
||||
fillColor = self.strokeColor,
|
||||
strokeColor = None)
|
||||
g.add(exdot)
|
||||
|
||||
return g
|
||||
|
||||
|
||||
class YesNo(_Symbol):
|
||||
"""This widget draw a tickbox or crossbox depending on 'testValue'.
|
||||
|
||||
If this widget is supplied with a 'True' or 1 as a value for
|
||||
testValue, it will use the tickbox widget. Otherwise, it will
|
||||
produce a crossbox.
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'tickcolor', 'crosscolor', 'testValue'
|
||||
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(BASE=_Symbol,
|
||||
tickcolor = AttrMapValue(isColor),
|
||||
crosscolor = AttrMapValue(isColor),
|
||||
testValue = AttrMapValue(isBoolean),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.tickcolor = colors.green
|
||||
self.crosscolor = colors.red
|
||||
self.testValue = 1
|
||||
|
||||
def draw(self):
|
||||
if self.testValue:
|
||||
yn=Tickbox()
|
||||
yn.tickColor=self.tickcolor
|
||||
else:
|
||||
yn=Crossbox()
|
||||
yn.crossColor=self.crosscolor
|
||||
yn.x=self.x
|
||||
yn.y=self.y
|
||||
yn.size=self.size
|
||||
yn.draw()
|
||||
return yn
|
||||
|
||||
|
||||
def demo(self):
|
||||
D = shapes.Drawing(200, 100)
|
||||
yn = YesNo()
|
||||
yn.x = 15
|
||||
yn.y = 25
|
||||
yn.size = 70
|
||||
yn.testValue = 0
|
||||
yn.draw()
|
||||
D.add(yn)
|
||||
yn2 = YesNo()
|
||||
yn2.x = 120
|
||||
yn2.y = 25
|
||||
yn2.size = 70
|
||||
yn2.testValue = 1
|
||||
yn2.draw()
|
||||
D.add(yn2)
|
||||
labelFontSize = 8
|
||||
D.add(shapes.String(yn.x+(yn.size/2),(yn.y-(1.2*labelFontSize)),
|
||||
'testValue=0', fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
D.add(shapes.String(yn2.x+(yn2.size/2),(yn2.y-(1.2*labelFontSize)),
|
||||
'testValue=1', fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
labelFontSize = 10
|
||||
D.add(shapes.String(yn.x+85,(yn.y-20),
|
||||
self.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
return D
|
||||
|
||||
class FloppyDisk(_Symbol):
|
||||
"""This widget draws an icon of a floppy disk.
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'diskcolor'
|
||||
|
||||
"""
|
||||
|
||||
_attrMap = AttrMap(BASE=_Symbol,
|
||||
diskColor = AttrMapValue(isColor),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.diskColor = colors.black
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
|
||||
|
||||
# floppy disk specific bits
|
||||
diskBody = shapes.Rect(x=self.x, y=self.y+(s/100), width=s, height=s-(s/100),
|
||||
fillColor = self.diskColor,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(diskBody)
|
||||
|
||||
label = shapes.Rect(x=self.x+(s*0.1), y=(self.y+s)-(s*0.5), width=s*0.8, height=s*0.48,
|
||||
fillColor = colors.whitesmoke,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(label)
|
||||
|
||||
labelsplash = shapes.Rect(x=self.x+(s*0.1), y=(self.y+s)-(s*0.1), width=s*0.8, height=s*0.08,
|
||||
fillColor = colors.royalblue,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(labelsplash)
|
||||
|
||||
|
||||
line1 = shapes.Line(x1=self.x+(s*0.15), y1=self.y+(0.6*s), x2=self.x+(s*0.85), y2=self.y+(0.6*s),
|
||||
fillColor = colors.black,
|
||||
strokeColor = colors.black,
|
||||
strokeWidth=0)
|
||||
g.add(line1)
|
||||
|
||||
line2 = shapes.Line(x1=self.x+(s*0.15), y1=self.y+(0.7*s), x2=self.x+(s*0.85), y2=self.y+(0.7*s),
|
||||
fillColor = colors.black,
|
||||
strokeColor = colors.black,
|
||||
strokeWidth=0)
|
||||
g.add(line2)
|
||||
|
||||
line3 = shapes.Line(x1=self.x+(s*0.15), y1=self.y+(0.8*s), x2=self.x+(s*0.85), y2=self.y+(0.8*s),
|
||||
fillColor = colors.black,
|
||||
strokeColor = colors.black,
|
||||
strokeWidth=0)
|
||||
g.add(line3)
|
||||
|
||||
metalcover = shapes.Rect(x=self.x+(s*0.2), y=(self.y), width=s*0.5, height=s*0.35,
|
||||
fillColor = colors.silver,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(metalcover)
|
||||
|
||||
coverslot = shapes.Rect(x=self.x+(s*0.28), y=(self.y)+(s*0.035), width=s*0.12, height=s*0.28,
|
||||
fillColor = self.diskColor,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(coverslot)
|
||||
|
||||
return g
|
||||
|
||||
class ArrowOne(_Symbol):
|
||||
"""This widget draws an arrow (style one).
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'fillColor'
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.fillColor = colors.red
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
|
||||
|
||||
# arrow specific bits
|
||||
body = shapes.Rect(x=self.x, y=(self.y+(s/2))-(s/6), width=2*(s/3), height=(s/3),
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(body)
|
||||
|
||||
head = shapes.Polygon(points = [self.x+(3*(s/6)), (self.y+(s/2)),
|
||||
self.x+(3*(s/6)), self.y+8*(s/10),
|
||||
self.x+s, self.y+(s/2),
|
||||
self.x+(3*(s/6)), self.y+2*(s/10)],
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(head)
|
||||
|
||||
return g
|
||||
|
||||
class ArrowTwo(ArrowOne):
|
||||
"""This widget draws an arrow (style two).
|
||||
|
||||
possible attributes:
|
||||
'x', 'y', 'size', 'fillColor'
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.size = 100
|
||||
self.fillColor = colors.blue
|
||||
|
||||
def draw(self):
|
||||
# general widget bits
|
||||
s = float(self.size) # abbreviate as we will use this a lot
|
||||
g = shapes.Group()
|
||||
|
||||
|
||||
# arrow specific bits
|
||||
body = shapes.Rect(x=self.x, y=(self.y+(s/2))-(s/24), width=9*(s/10), height=(s/12),
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(body)
|
||||
|
||||
head = shapes.Polygon(points = [self.x+(2.5*(s/3)), (self.y+(s/2)),
|
||||
self.x+(4*(s/6)), self.y+4*(s/6),
|
||||
self.x+s, self.y+(s/2),
|
||||
self.x+(4*(s/6)), self.y+2*(s/6)],
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = None,
|
||||
strokeWidth=0)
|
||||
g.add(head)
|
||||
|
||||
return g
|
||||
|
||||
|
||||
def test():
|
||||
"""This function produces a pdf with examples of all the signs and symbols from this file.
|
||||
"""
|
||||
labelFontSize = 10
|
||||
D = shapes.Drawing(450,650)
|
||||
cb = Crossbox()
|
||||
cb.x = 20
|
||||
cb.y = 530
|
||||
D.add(cb)
|
||||
D.add(shapes.String(cb.x+(cb.size/2),(cb.y-(1.2*labelFontSize)),
|
||||
cb.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
|
||||
tb = Tickbox()
|
||||
tb.x = 170
|
||||
tb.y = 530
|
||||
D.add(tb)
|
||||
D.add(shapes.String(tb.x+(tb.size/2),(tb.y-(1.2*labelFontSize)),
|
||||
tb.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
|
||||
|
||||
yn = YesNo()
|
||||
yn.x = 320
|
||||
yn.y = 530
|
||||
D.add(yn)
|
||||
tempstring = yn.__class__.__name__ + '*'
|
||||
D.add(shapes.String(yn.x+(tb.size/2),(yn.y-(1.2*labelFontSize)),
|
||||
tempstring, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
D.add(shapes.String(130,6,
|
||||
"(The 'YesNo' widget returns a tickbox if testvalue=1, and a crossbox if testvalue=0)", fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize*0.75))
|
||||
|
||||
|
||||
ss = StopSign()
|
||||
ss.x = 20
|
||||
ss.y = 400
|
||||
D.add(ss)
|
||||
D.add(shapes.String(ss.x+(ss.size/2), ss.y-(1.2*labelFontSize),
|
||||
ss.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
|
||||
ne = NoEntry()
|
||||
ne.x = 170
|
||||
ne.y = 400
|
||||
D.add(ne)
|
||||
D.add(shapes.String(ne.x+(ne.size/2),(ne.y-(1.2*labelFontSize)),
|
||||
ne.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
|
||||
sf = SmileyFace()
|
||||
sf.x = 320
|
||||
sf.y = 400
|
||||
D.add(sf)
|
||||
D.add(shapes.String(sf.x+(sf.size/2),(sf.y-(1.2*labelFontSize)),
|
||||
sf.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
|
||||
ds = DangerSign()
|
||||
ds.x = 20
|
||||
ds.y = 270
|
||||
D.add(ds)
|
||||
D.add(shapes.String(ds.x+(ds.size/2),(ds.y-(1.2*labelFontSize)),
|
||||
ds.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
|
||||
na = NotAllowed()
|
||||
na.x = 170
|
||||
na.y = 270
|
||||
D.add(na)
|
||||
D.add(shapes.String(na.x+(na.size/2),(na.y-(1.2*labelFontSize)),
|
||||
na.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
|
||||
ns = NoSmoking()
|
||||
ns.x = 320
|
||||
ns.y = 270
|
||||
D.add(ns)
|
||||
D.add(shapes.String(ns.x+(ns.size/2),(ns.y-(1.2*labelFontSize)),
|
||||
ns.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
|
||||
a1 = ArrowOne()
|
||||
a1.x = 20
|
||||
a1.y = 140
|
||||
D.add(a1)
|
||||
D.add(shapes.String(a1.x+(a1.size/2),(a1.y-(1.2*labelFontSize)),
|
||||
a1.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
|
||||
a2 = ArrowTwo()
|
||||
a2.x = 170
|
||||
a2.y = 140
|
||||
D.add(a2)
|
||||
D.add(shapes.String(a2.x+(a2.size/2),(a2.y-(1.2*labelFontSize)),
|
||||
a2.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
|
||||
fd = FloppyDisk()
|
||||
fd.x = 320
|
||||
fd.y = 140
|
||||
D.add(fd)
|
||||
D.add(shapes.String(fd.x+(fd.size/2),(fd.y-(1.2*labelFontSize)),
|
||||
fd.__class__.__name__, fillColor=colors.black, textAnchor='middle',
|
||||
fontSize=labelFontSize))
|
||||
|
||||
renderPDF.drawToFile(D, 'signsandsymbols.pdf', 'signsandsymbols.py')
|
||||
print 'wrote file: signsandsymbols.pdf'
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
|
@ -1,155 +0,0 @@
|
|||
"""Module to analyze Python source code; for syntax coloring tools.
|
||||
|
||||
Interface:
|
||||
tags = fontify(pytext, searchfrom, searchto)
|
||||
|
||||
The 'pytext' argument is a string containing Python source code.
|
||||
The (optional) arguments 'searchfrom' and 'searchto' may contain a slice in pytext.
|
||||
The returned value is a list of tuples, formatted like this:
|
||||
[('keyword', 0, 6, None), ('keyword', 11, 17, None), ('comment', 23, 53, None), etc. ]
|
||||
The tuple contents are always like this:
|
||||
(tag, startindex, endindex, sublist)
|
||||
tag is one of 'keyword', 'string', 'comment' or 'identifier'
|
||||
sublist is not used, hence always None.
|
||||
"""
|
||||
|
||||
# Based on FontText.py by Mitchell S. Chapman,
|
||||
# which was modified by Zachary Roadhouse,
|
||||
# then un-Tk'd by Just van Rossum.
|
||||
# Many thanks for regular expression debugging & authoring are due to:
|
||||
# Tim (the-incredib-ly y'rs) Peters and Cristian Tismer
|
||||
# So, who owns the copyright? ;-) How about this:
|
||||
# Copyright 1996-2001:
|
||||
# Mitchell S. Chapman,
|
||||
# Zachary Roadhouse,
|
||||
# Tim Peters,
|
||||
# Just van Rossum
|
||||
|
||||
__version__ = "0.4"
|
||||
|
||||
import string
|
||||
import re
|
||||
|
||||
# First a little helper, since I don't like to repeat things. (Tismer speaking)
|
||||
import string
|
||||
def replace(where, what, with):
|
||||
return string.join(string.split(where, what), with)
|
||||
|
||||
# This list of keywords is taken from ref/node13.html of the
|
||||
# Python 1.3 HTML documentation. ("access" is intentionally omitted.)
|
||||
keywordsList = [
|
||||
"as", "assert", "exec",
|
||||
"del", "from", "lambda", "return",
|
||||
"and", "elif", "global", "not", "try",
|
||||
"break", "else", "if", "or", "while",
|
||||
"class", "except", "import", "pass",
|
||||
"continue", "finally", "in", "print",
|
||||
"def", "for", "is", "raise", "yield"]
|
||||
|
||||
# Build up a regular expression which will match anything
|
||||
# interesting, including multi-line triple-quoted strings.
|
||||
commentPat = r"#[^\n]*"
|
||||
|
||||
pat = r"q[^\\q\n]*(\\[\000-\377][^\\q\n]*)*q"
|
||||
quotePat = replace(pat, "q", "'") + "|" + replace(pat, 'q', '"')
|
||||
|
||||
# Way to go, Tim!
|
||||
pat = r"""
|
||||
qqq
|
||||
[^\\q]*
|
||||
(
|
||||
( \\[\000-\377]
|
||||
| q
|
||||
( \\[\000-\377]
|
||||
| [^\q]
|
||||
| q
|
||||
( \\[\000-\377]
|
||||
| [^\\q]
|
||||
)
|
||||
)
|
||||
)
|
||||
[^\\q]*
|
||||
)*
|
||||
qqq
|
||||
"""
|
||||
pat = string.join(string.split(pat), '') # get rid of whitespace
|
||||
tripleQuotePat = replace(pat, "q", "'") + "|" + replace(pat, 'q', '"')
|
||||
|
||||
# Build up a regular expression which matches all and only
|
||||
# Python keywords. This will let us skip the uninteresting
|
||||
# identifier references.
|
||||
# nonKeyPat identifies characters which may legally precede
|
||||
# a keyword pattern.
|
||||
nonKeyPat = r"(^|[^a-zA-Z0-9_.\"'])"
|
||||
|
||||
keyPat = nonKeyPat + "(" + string.join(keywordsList, "|") + ")" + nonKeyPat
|
||||
|
||||
matchPat = commentPat + "|" + keyPat + "|" + tripleQuotePat + "|" + quotePat
|
||||
matchRE = re.compile(matchPat)
|
||||
|
||||
idKeyPat = "[ \t]*[A-Za-z_][A-Za-z_0-9.]*" # Ident w. leading whitespace.
|
||||
idRE = re.compile(idKeyPat)
|
||||
|
||||
|
||||
def fontify(pytext, searchfrom = 0, searchto = None):
|
||||
if searchto is None:
|
||||
searchto = len(pytext)
|
||||
# Cache a few attributes for quicker reference.
|
||||
search = matchRE.search
|
||||
idSearch = idRE.search
|
||||
|
||||
tags = []
|
||||
tags_append = tags.append
|
||||
commentTag = 'comment'
|
||||
stringTag = 'string'
|
||||
keywordTag = 'keyword'
|
||||
identifierTag = 'identifier'
|
||||
|
||||
start = 0
|
||||
end = searchfrom
|
||||
while 1:
|
||||
m = search(pytext, end)
|
||||
if m is None:
|
||||
break # EXIT LOOP
|
||||
start = m.start()
|
||||
if start >= searchto:
|
||||
break # EXIT LOOP
|
||||
match = m.group(0)
|
||||
end = start + len(match)
|
||||
c = match[0]
|
||||
if c not in "#'\"":
|
||||
# Must have matched a keyword.
|
||||
if start <> searchfrom:
|
||||
# there's still a redundant char before and after it, strip!
|
||||
match = match[1:-1]
|
||||
start = start + 1
|
||||
else:
|
||||
# this is the first keyword in the text.
|
||||
# Only a space at the end.
|
||||
match = match[:-1]
|
||||
end = end - 1
|
||||
tags_append((keywordTag, start, end, None))
|
||||
# If this was a defining keyword, look ahead to the
|
||||
# following identifier.
|
||||
if match in ["def", "class"]:
|
||||
m = idSearch(pytext, end)
|
||||
if m is not None:
|
||||
start = m.start()
|
||||
if start == end:
|
||||
match = m.group(0)
|
||||
end = start + len(match)
|
||||
tags_append((identifierTag, start, end, None))
|
||||
elif c == "#":
|
||||
tags_append((commentTag, start, end, None))
|
||||
else:
|
||||
tags_append((stringTag, start, end, None))
|
||||
return tags
|
||||
|
||||
|
||||
def test(path):
|
||||
f = open(path)
|
||||
text = f.read()
|
||||
f.close()
|
||||
tags = fontify(text)
|
||||
for tag, start, end, sublist in tags:
|
||||
print tag, `text[start:end]`
|
|
@ -1,7 +0,0 @@
|
|||
#! /usr/bin/python2.3
|
||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/__init__.py
|
||||
__version__=''' $Id$ '''
|
||||
import os
|
||||
RL_DEBUG = os.environ.has_key('RL_DEBUG')
|
|
@ -1,44 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/abag.py
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
class ABag:
|
||||
"""
|
||||
'Attribute Bag' - a trivial BAG class for holding attributes.
|
||||
|
||||
You may initialize with keyword arguments.
|
||||
a = ABag(k0=v0,....,kx=vx,....) ==> getattr(a,'kx')==vx
|
||||
|
||||
c = a.clone(ak0=av0,.....) copy with optional additional attributes.
|
||||
"""
|
||||
def __init__(self,**attr):
|
||||
for k,v in attr.items():
|
||||
setattr(self,k,v)
|
||||
|
||||
def clone(self,**attr):
|
||||
n = apply(ABag,(),self.__dict__)
|
||||
if attr != {}: apply(ABag.__init__,(n,),attr)
|
||||
return n
|
||||
|
||||
def __repr__(self):
|
||||
import string
|
||||
n = self.__class__.__name__
|
||||
L = [n+"("]
|
||||
keys = self.__dict__.keys()
|
||||
for k in keys:
|
||||
v = getattr(self, k)
|
||||
rk = repr(k)
|
||||
rv = repr(v)
|
||||
rk = " "+string.replace(rk, "\n", "\n ")
|
||||
rv = " "+string.replace(rv, "\n", "\n ")
|
||||
L.append(rk)
|
||||
L.append(rv)
|
||||
L.append(") #"+n)
|
||||
return string.join(L, "\n")
|
||||
|
||||
if __name__=="__main__":
|
||||
AB = ABag(a=1, c="hello")
|
||||
CD = AB.clone()
|
||||
print AB
|
||||
print CD
|
|
@ -1,132 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/attrmap.py
|
||||
__version__=''' $Id$ '''
|
||||
from UserDict import UserDict
|
||||
from reportlab.lib.validators import isAnything, _SequenceTypes
|
||||
from reportlab import rl_config
|
||||
|
||||
class CallableValue:
|
||||
'''a class to allow callable initial values'''
|
||||
def __init__(self,func,*args,**kw):
|
||||
#assert iscallable(func)
|
||||
self.func = func
|
||||
self.args = args
|
||||
self.kw = kw
|
||||
|
||||
def __call__(self):
|
||||
return apply(self.func,self.args,self.kw)
|
||||
|
||||
class AttrMapValue:
|
||||
'''Simple multi-value holder for attribute maps'''
|
||||
def __init__(self,validate=None,desc=None,initial=None, **kw):
|
||||
self.validate = validate or isAnything
|
||||
self.desc = desc
|
||||
self.initial = initial
|
||||
for k,v in kw.items():
|
||||
setattr(self,k,v)
|
||||
|
||||
def __getattr__(self,name):
|
||||
#hack to allow callable initial values
|
||||
if name=='initial':
|
||||
if isinstance(self._initial,CallableValue): return self._initial()
|
||||
return self._initial
|
||||
elif name=='hidden':
|
||||
return 0
|
||||
raise AttributeError, name
|
||||
|
||||
class AttrMap(UserDict):
|
||||
def __init__(self,BASE=None,UNWANTED=[],**kw):
|
||||
data = {}
|
||||
if BASE:
|
||||
if isinstance(BASE,AttrMap):
|
||||
data = BASE.data #they used BASECLASS._attrMap
|
||||
else:
|
||||
if type(BASE) not in (type(()),type([])): BASE = (BASE,)
|
||||
for B in BASE:
|
||||
if hasattr(B,'_attrMap'):
|
||||
data.update(getattr(B._attrMap,'data',{}))
|
||||
else:
|
||||
raise ValueError, 'BASE=%s has wrong kind of value' % str(B)
|
||||
|
||||
UserDict.__init__(self,data)
|
||||
self.remove(UNWANTED)
|
||||
self.data.update(kw)
|
||||
|
||||
def update(self,kw):
|
||||
if isinstance(kw,AttrMap): kw = kw.data
|
||||
self.data.update(kw)
|
||||
|
||||
def remove(self,unwanted):
|
||||
for k in unwanted:
|
||||
try:
|
||||
del self[k]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def clone(self,UNWANTED=[],**kw):
|
||||
c = AttrMap(BASE=self,UNWANTED=UNWANTED)
|
||||
c.update(kw)
|
||||
return c
|
||||
|
||||
def validateSetattr(obj,name,value):
|
||||
'''validate setattr(obj,name,value)'''
|
||||
if rl_config.shapeChecking:
|
||||
map = obj._attrMap
|
||||
if map and name[0]!= '_':
|
||||
try:
|
||||
validate = map[name].validate
|
||||
if not validate(value):
|
||||
raise AttributeError, "Illegal assignment of '%s' to '%s' in class %s" % (value, name, obj.__class__.__name__)
|
||||
except KeyError:
|
||||
raise AttributeError, "Illegal attribute '%s' in class %s" % (name, obj.__class__.__name__)
|
||||
obj.__dict__[name] = value
|
||||
|
||||
def _privateAttrMap(obj,ret=0):
|
||||
'''clone obj._attrMap if required'''
|
||||
A = obj._attrMap
|
||||
oA = getattr(obj.__class__,'_attrMap',None)
|
||||
if ret:
|
||||
if oA is A:
|
||||
return A.clone(), oA
|
||||
else:
|
||||
return A, None
|
||||
else:
|
||||
if oA is A:
|
||||
obj._attrMap = A.clone()
|
||||
|
||||
def _findObjectAndAttr(src, P):
|
||||
'''Locate the object src.P for P a string, return parent and name of attribute
|
||||
'''
|
||||
P = string.split(P, '.')
|
||||
if len(P) == 0:
|
||||
return None, None
|
||||
else:
|
||||
for p in P[0:-1]:
|
||||
src = getattr(src, p)
|
||||
return src, P[-1]
|
||||
|
||||
def hook__setattr__(obj):
|
||||
if not hasattr(obj,'__attrproxy__'):
|
||||
C = obj.__class__
|
||||
import new
|
||||
obj.__class__=new.classobj(C.__name__,(C,)+C.__bases__,
|
||||
{'__attrproxy__':[],
|
||||
'__setattr__':lambda self,k,v,osa=getattr(obj,'__setattr__',None),hook=hook: hook(self,k,v,osa)})
|
||||
|
||||
def addProxyAttribute(src,name,validate=None,desc=None,initial=None,dst=None):
|
||||
'''
|
||||
Add a proxy attribute 'name' to src with targets dst
|
||||
'''
|
||||
#sanity
|
||||
assert hasattr(src,'_attrMap'), 'src object has no _attrMap'
|
||||
A, oA = _privateAttrMap(src,1)
|
||||
if type(dst) not in _SequenceTypes: dst = dst,
|
||||
D = []
|
||||
DV = []
|
||||
for d in dst:
|
||||
if type(d) in _SequenceTypes:
|
||||
d, e = d[0], d[1:]
|
||||
obj, attr = _findObjectAndAttr(src,d)
|
||||
if obj:
|
||||
dA = getattr(obj,'_attrMap',None)
|
|
@ -1,340 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/codecharts.py
|
||||
#$Header $
|
||||
__version__=''' $Id '''
|
||||
__doc__="""Routines to print code page (character set) drawings.
|
||||
|
||||
To be sure we can accurately represent characters in various encodings
|
||||
and fonts, we need some routines to display all those characters.
|
||||
These are defined herein. The idea is to include flowable, drawable
|
||||
and graphic objects for single and multi-byte fonts. """
|
||||
import string
|
||||
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.platypus import Flowable
|
||||
from reportlab.pdfbase import pdfmetrics, cidfonts
|
||||
from reportlab.graphics.shapes import Drawing, Group, String, Circle, Rect
|
||||
from reportlab.graphics.widgetbase import Widget
|
||||
from reportlab.lib import colors
|
||||
|
||||
|
||||
class CodeChartBase(Flowable):
|
||||
"""Basic bits of drawing furniture used by
|
||||
single and multi-byte versions: ability to put letters
|
||||
into boxes."""
|
||||
|
||||
def calcLayout(self):
|
||||
"Work out x and y positions for drawing"
|
||||
|
||||
|
||||
rows = self.codePoints * 1.0 / self.charsPerRow
|
||||
if rows == int(rows):
|
||||
self.rows = int(rows)
|
||||
else:
|
||||
self.rows = int(rows) + 1
|
||||
# size allows for a gray column of labels
|
||||
self.width = self.boxSize * (1+self.charsPerRow)
|
||||
self.height = self.boxSize * (1+self.rows)
|
||||
|
||||
#handy lists
|
||||
self.ylist = []
|
||||
for row in range(self.rows + 2):
|
||||
self.ylist.append(row * self.boxSize)
|
||||
self.xlist = []
|
||||
for col in range(self.charsPerRow + 2):
|
||||
self.xlist.append(col * self.boxSize)
|
||||
|
||||
def formatByte(self, byt):
|
||||
if self.hex:
|
||||
return '%02X' % byt
|
||||
else:
|
||||
return '%d' % byt
|
||||
|
||||
def drawChars(self, charList):
|
||||
"""Fills boxes in order. None means skip a box.
|
||||
Empty boxes at end get filled with gray"""
|
||||
extraNeeded = (self.rows * self.charsPerRow - len(charList))
|
||||
for i in range(extraNeeded):
|
||||
charList.append(None)
|
||||
#charList.extend([None] * extraNeeded)
|
||||
row = 0
|
||||
col = 0
|
||||
self.canv.setFont(self.fontName, self.boxSize * 0.75)
|
||||
for ch in charList: # may be 2 bytes or 1
|
||||
if ch is None:
|
||||
self.canv.setFillGray(0.9)
|
||||
self.canv.rect((1+col) * self.boxSize, (self.rows - row - 1) * self.boxSize,
|
||||
self.boxSize, self.boxSize, stroke=0, fill=1)
|
||||
self.canv.setFillGray(0.0)
|
||||
else:
|
||||
try:
|
||||
self.canv.drawCentredString(
|
||||
(col+1.5) * self.boxSize,
|
||||
(self.rows - row - 0.875) * self.boxSize,
|
||||
ch,
|
||||
)
|
||||
except:
|
||||
self.canv.setFillGray(0.9)
|
||||
self.canv.rect((1+col) * self.boxSize, (self.rows - row - 1) * self.boxSize,
|
||||
self.boxSize, self.boxSize, stroke=0, fill=1)
|
||||
self.canv.drawCentredString(
|
||||
(col+1.5) * self.boxSize,
|
||||
(self.rows - row - 0.875) * self.boxSize,
|
||||
'?',
|
||||
)
|
||||
self.canv.setFillGray(0.0)
|
||||
col = col + 1
|
||||
if col == self.charsPerRow:
|
||||
row = row + 1
|
||||
col = 0
|
||||
|
||||
def drawLabels(self, topLeft = ''):
|
||||
"""Writes little labels in the top row and first column"""
|
||||
self.canv.setFillGray(0.8)
|
||||
self.canv.rect(0, self.ylist[-2], self.width, self.boxSize, fill=1, stroke=0)
|
||||
self.canv.rect(0, 0, self.boxSize, self.ylist[-2], fill=1, stroke=0)
|
||||
self.canv.setFillGray(0.0)
|
||||
|
||||
#label each row and column
|
||||
self.canv.setFont('Helvetica-Oblique',0.375 * self.boxSize)
|
||||
byt = 0
|
||||
for row in range(self.rows):
|
||||
if self.rowLabels:
|
||||
label = self.rowLabels[row]
|
||||
else: # format start bytes as hex or decimal
|
||||
label = self.formatByte(row * self.charsPerRow)
|
||||
self.canv.drawCentredString(0.5 * self.boxSize,
|
||||
(self.rows - row - 0.75) * self.boxSize,
|
||||
label
|
||||
)
|
||||
for col in range(self.charsPerRow):
|
||||
self.canv.drawCentredString((col + 1.5) * self.boxSize,
|
||||
(self.rows + 0.25) * self.boxSize,
|
||||
self.formatByte(col)
|
||||
)
|
||||
|
||||
if topLeft:
|
||||
self.canv.setFont('Helvetica-BoldOblique',0.5 * self.boxSize)
|
||||
self.canv.drawCentredString(0.5 * self.boxSize,
|
||||
(self.rows + 0.25) * self.boxSize,
|
||||
topLeft
|
||||
)
|
||||
|
||||
class SingleByteEncodingChart(CodeChartBase):
|
||||
def __init__(self, faceName='Helvetica', encodingName='WinAnsiEncoding',
|
||||
charsPerRow=16, boxSize=14, hex=1):
|
||||
self.codePoints = 256
|
||||
self.faceName = faceName
|
||||
self.encodingName = encodingName
|
||||
self.fontName = self.faceName + '-' + self.encodingName
|
||||
self.charsPerRow = charsPerRow
|
||||
self.boxSize = boxSize
|
||||
self.hex = hex
|
||||
self.rowLabels = None
|
||||
pdfmetrics.registerFont(pdfmetrics.Font(self.fontName,
|
||||
self.faceName,
|
||||
self.encodingName)
|
||||
)
|
||||
|
||||
self.calcLayout()
|
||||
|
||||
|
||||
def draw(self):
|
||||
self.drawLabels()
|
||||
charList = [None] * 32 + map(chr, range(32, 256))
|
||||
self.drawChars(charList)
|
||||
self.canv.grid(self.xlist, self.ylist)
|
||||
|
||||
|
||||
class KutenRowCodeChart(CodeChartBase):
|
||||
"""Formats one 'row' of the 94x94 space used in many Asian encodings.aliases
|
||||
|
||||
These deliberately resemble the code charts in Ken Lunde's "Understanding
|
||||
CJKV Information Processing", to enable manual checking. Due to the large
|
||||
numbers of characters, we don't try to make one graphic with 10,000 characters,
|
||||
but rather output a sequence of these."""
|
||||
#would be cleaner if both shared one base class whose job
|
||||
#was to draw the boxes, but never mind...
|
||||
def __init__(self, row, faceName, encodingName):
|
||||
self.row = row
|
||||
self.codePoints = 94
|
||||
self.boxSize = 18
|
||||
self.charsPerRow = 20
|
||||
self.rows = 5
|
||||
self.rowLabels = ['00','20','40','60','80']
|
||||
self.hex = 0
|
||||
self.faceName = faceName
|
||||
self.encodingName = encodingName
|
||||
|
||||
try:
|
||||
# the dependent files might not be available
|
||||
font = cidfonts.CIDFont(self.faceName, self.encodingName)
|
||||
pdfmetrics.registerFont(font)
|
||||
except:
|
||||
# fall back to English and at least shwo we can draw the boxes
|
||||
self.faceName = 'Helvetica'
|
||||
self.encodingName = 'WinAnsiEncoding'
|
||||
self.fontName = self.faceName + '-' + self.encodingName
|
||||
self.calcLayout()
|
||||
|
||||
def makeRow(self, row):
|
||||
"""Works out the character values for this kuten row"""
|
||||
cells = []
|
||||
if string.find(self.encodingName, 'EUC') > -1:
|
||||
# it is an EUC family encoding.
|
||||
for col in range(1, 95):
|
||||
ch = chr(row + 160) + chr(col+160)
|
||||
cells.append(ch)
|
||||
## elif string.find(self.encodingName, 'GB') > -1:
|
||||
## # it is an EUC family encoding.
|
||||
## for col in range(1, 95):
|
||||
## ch = chr(row + 160) + chr(col+160)
|
||||
else:
|
||||
cells.append([None] * 94)
|
||||
return cells
|
||||
|
||||
def draw(self):
|
||||
self.drawLabels(topLeft= 'R%d' % self.row)
|
||||
|
||||
# work out which characters we need for the row
|
||||
#assert string.find(self.encodingName, 'EUC') > -1, 'Only handles EUC encoding today, you gave me %s!' % self.encodingName
|
||||
|
||||
# pad out by 1 to match Ken Lunde's tables
|
||||
charList = [None] + self.makeRow(self.row)
|
||||
self.drawChars(charList)
|
||||
self.canv.grid(self.xlist, self.ylist)
|
||||
|
||||
|
||||
class Big5CodeChart(CodeChartBase):
|
||||
"""Formats one 'row' of the 94x160 space used in Big 5
|
||||
|
||||
These deliberately resemble the code charts in Ken Lunde's "Understanding
|
||||
CJKV Information Processing", to enable manual checking."""
|
||||
def __init__(self, row, faceName, encodingName):
|
||||
self.row = row
|
||||
self.codePoints = 160
|
||||
self.boxSize = 18
|
||||
self.charsPerRow = 16
|
||||
self.rows = 10
|
||||
self.hex = 1
|
||||
self.faceName = faceName
|
||||
self.encodingName = encodingName
|
||||
self.rowLabels = ['4','5','6','7','A','B','C','D','E','F']
|
||||
try:
|
||||
# the dependent files might not be available
|
||||
font = cidfonts.CIDFont(self.faceName, self.encodingName)
|
||||
pdfmetrics.registerFont(font)
|
||||
except:
|
||||
# fall back to English and at least shwo we can draw the boxes
|
||||
self.faceName = 'Helvetica'
|
||||
self.encodingName = 'WinAnsiEncoding'
|
||||
self.fontName = self.faceName + '-' + self.encodingName
|
||||
self.calcLayout()
|
||||
|
||||
def makeRow(self, row):
|
||||
"""Works out the character values for this Big5 row.
|
||||
Rows start at 0xA1"""
|
||||
cells = []
|
||||
if string.find(self.encodingName, 'B5') > -1:
|
||||
# big 5, different row size
|
||||
for y in [4,5,6,7,10,11,12,13,14,15]:
|
||||
for x in range(16):
|
||||
col = y*16+x
|
||||
ch = chr(row) + chr(col)
|
||||
cells.append(ch)
|
||||
|
||||
else:
|
||||
cells.append([None] * 160)
|
||||
return cells
|
||||
|
||||
def draw(self):
|
||||
self.drawLabels(topLeft='%02X' % self.row)
|
||||
|
||||
charList = self.makeRow(self.row)
|
||||
self.drawChars(charList)
|
||||
self.canv.grid(self.xlist, self.ylist)
|
||||
|
||||
|
||||
def hBoxText(msg, canvas, x, y, faceName, encName):
|
||||
"""Helper for stringwidth tests on Asian fonts.
|
||||
|
||||
Registers font if needed. Then draws the string,
|
||||
and a box around it derived from the stringWidth function"""
|
||||
canvas.saveState()
|
||||
fontName = faceName + '-' + encName
|
||||
try:
|
||||
font = pdfmetrics.getFont(fontName)
|
||||
except KeyError:
|
||||
font = cidfonts.CIDFont(faceName, encName)
|
||||
pdfmetrics.registerFont(font)
|
||||
|
||||
canvas.setFillGray(0.8)
|
||||
canvas.rect(x,y,pdfmetrics.stringWidth(msg, fontName, 16),16,stroke=0,fill=1)
|
||||
canvas.setFillGray(0)
|
||||
canvas.setFont(fontName, 16,16)
|
||||
canvas.drawString(x,y,msg)
|
||||
canvas.restoreState()
|
||||
|
||||
|
||||
class CodeWidget(Widget):
|
||||
"""Block showing all the characters"""
|
||||
def __init__(self):
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.width = 160
|
||||
self.height = 160
|
||||
|
||||
def draw(self):
|
||||
dx = self.width / 16.0
|
||||
dy = self.height / 16.0
|
||||
g = Group()
|
||||
g.add(Rect(self.x, self.y, self.width, self.height,
|
||||
fillColor=None, strokeColor=colors.black))
|
||||
for x in range(16):
|
||||
for y in range(16):
|
||||
charValue = y * 16 + x
|
||||
if charValue > 32:
|
||||
s = String(self.x + x * dx,
|
||||
self.y + (self.height - y*dy), chr(charValue))
|
||||
g.add(s)
|
||||
return g
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def test():
|
||||
c = Canvas('codecharts.pdf')
|
||||
c.setFont('Helvetica-Bold', 24)
|
||||
c.drawString(72, 750, 'Testing code page charts')
|
||||
cc1 = SingleByteEncodingChart()
|
||||
cc1.drawOn(c, 72, 500)
|
||||
|
||||
cc2 = SingleByteEncodingChart(charsPerRow=32)
|
||||
cc2.drawOn(c, 72, 300)
|
||||
|
||||
cc3 = SingleByteEncodingChart(charsPerRow=25, hex=0)
|
||||
cc3.drawOn(c, 72, 100)
|
||||
|
||||
## c.showPage()
|
||||
##
|
||||
## c.setFont('Helvetica-Bold', 24)
|
||||
## c.drawString(72, 750, 'Multi-byte Kuten code chart examples')
|
||||
## KutenRowCodeChart(1, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, 600)
|
||||
## KutenRowCodeChart(16, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, 450)
|
||||
## KutenRowCodeChart(84, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, 300)
|
||||
##
|
||||
## c.showPage()
|
||||
## c.setFont('Helvetica-Bold', 24)
|
||||
## c.drawString(72, 750, 'Big5 Code Chart Examples')
|
||||
## #Big5CodeChart(0xA1, 'MSungStd-Light-Acro','ETenms-B5-H').drawOn(c, 72, 500)
|
||||
|
||||
c.save()
|
||||
print 'saved codecharts.pdf'
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
||||
|
||||
|
|
@ -1,564 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/colors.py
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
import string, math
|
||||
from types import StringType, ListType, TupleType
|
||||
from reportlab.lib.utils import fp_str
|
||||
_SeqTypes = (ListType,TupleType)
|
||||
|
||||
class Color:
|
||||
"""This class is used to represent color. Components red, green, blue
|
||||
are in the range 0 (dark) to 1 (full intensity)."""
|
||||
|
||||
def __init__(self, red=0, green=0, blue=0):
|
||||
"Initialize with red, green, blue in range [0-1]."
|
||||
self.red, self.green, self.blue = red,green,blue
|
||||
|
||||
def __repr__(self):
|
||||
return "Color(%s)" % string.replace(fp_str(self.red, self.green, self.blue),' ',',')
|
||||
|
||||
def __hash__(self):
|
||||
return hash( (self.red, self.green, self.blue) )
|
||||
|
||||
def __cmp__(self,other):
|
||||
try:
|
||||
dsum = 4*self.red-4*other.red + 2*self.green-2*other.green + self.blue-other.blue
|
||||
except:
|
||||
return -1
|
||||
if dsum > 0: return 1
|
||||
if dsum < 0: return -1
|
||||
return 0
|
||||
|
||||
def rgb(self):
|
||||
"Returns a three-tuple of components"
|
||||
return (self.red, self.green, self.blue)
|
||||
|
||||
def bitmap_rgb(self):
|
||||
return tuple(map(lambda x: int(x*255)&255, self.rgb()))
|
||||
|
||||
def hexval(self):
|
||||
return '0x%02x%02x%02x' % self.bitmap_rgb()
|
||||
|
||||
class CMYKColor(Color):
|
||||
"""This represents colors using the CMYK (cyan, magenta, yellow, black)
|
||||
model commonly used in professional printing. This is implemented
|
||||
as a derived class so that renderers which only know about RGB "see it"
|
||||
as an RGB color through its 'red','green' and 'blue' attributes, according
|
||||
to an approximate function.
|
||||
|
||||
The RGB approximation is worked out when the object in constructed, so
|
||||
the color attributes should not be changed afterwards.
|
||||
|
||||
Extra attributes may be attached to the class to support specific ink models,
|
||||
and renderers may look for these."""
|
||||
|
||||
def __init__(self, cyan=0, magenta=0, yellow=0, black=0,
|
||||
spotName=None, density=1, knockout=None):
|
||||
"""
|
||||
Initialize with four colors in range [0-1]. the optional
|
||||
spotName, density & knockout may be of use to specific renderers.
|
||||
spotName is intended for use as an identifier to the renderer not client programs.
|
||||
density is used to modify the overall amount of ink.
|
||||
knockout is a renderer dependent option that determines whether the applied colour
|
||||
knocksout (removes) existing colour; None means use the global default.
|
||||
"""
|
||||
self.cyan = cyan
|
||||
self.magenta = magenta
|
||||
self.yellow = yellow
|
||||
self.black = black
|
||||
self.spotName = spotName
|
||||
self.density = max(min(density,1),0) # force into right range
|
||||
self.knockout = knockout
|
||||
|
||||
# now work out the RGB approximation. override
|
||||
self.red, self.green, self.blue = cmyk2rgb( (cyan, magenta, yellow, black) )
|
||||
|
||||
if density<1:
|
||||
#density adjustment of rgb approximants, effectively mix with white
|
||||
r, g, b = self.red, self.green, self.blue
|
||||
r = density*(r-1)+1
|
||||
g = density*(g-1)+1
|
||||
b = density*(b-1)+1
|
||||
self.red, self.green, self.blue = (r,g,b)
|
||||
|
||||
def __repr__(self):
|
||||
return "CMYKColor(%s%s%s%s)" % (
|
||||
string.replace(fp_str(self.cyan, self.magenta, self.yellow, self.black),' ',','),
|
||||
(self.spotName and (',spotName='+repr(self.spotName)) or ''),
|
||||
(self.density!=1 and (',density='+fp_str(self.density)) or ''),
|
||||
(self.knockout is not None and (',knockout=%d' % self.knockout) or ''),
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return hash( (self.cyan, self.magenta, self.yellow, self.black, self.density, self.spotName) )
|
||||
|
||||
def __cmp__(self,other):
|
||||
"""Partial ordering of colors according to a notion of distance.
|
||||
|
||||
Comparing across the two color models is of limited use."""
|
||||
# why the try-except? What can go wrong?
|
||||
if isinstance(other, CMYKColor):
|
||||
dsum = (((( (self.cyan-other.cyan)*2 +
|
||||
(self.magenta-other.magenta))*2+
|
||||
(self.yellow-other.yellow))*2+
|
||||
(self.black-other.black))*2+
|
||||
(self.density-other.density))*2 + cmp(self.spotName or '',other.spotName or '')
|
||||
else: # do the RGB comparison
|
||||
try:
|
||||
dsum = ((self.red-other.red)*2+(self.green-other.green))*2+(self.blue-other.blue)
|
||||
except: # or just return 'not equal' if not a color
|
||||
return -1
|
||||
if dsum >= 0:
|
||||
return dsum>0
|
||||
else:
|
||||
return -1
|
||||
|
||||
def cmyk(self):
|
||||
"Returns a tuple of four color components - syntactic sugar"
|
||||
return (self.cyan, self.magenta, self.yellow, self.black)
|
||||
|
||||
def _density_str(self):
|
||||
return fp_str(self.density)
|
||||
|
||||
class PCMYKColor(CMYKColor):
|
||||
'''100 based CMYKColor with density and a spotName; just like Rimas uses'''
|
||||
def __init__(self,cyan,magenta,yellow,black,density=100,spotName=None,knockout=None):
|
||||
CMYKColor.__init__(self,cyan/100.,magenta/100.,yellow/100.,black/100.,spotName,density/100.,knockout=knockout)
|
||||
|
||||
def __repr__(self):
|
||||
return "PCMYKColor(%s%s%s%s)" % (
|
||||
string.replace(fp_str(self.cyan*100, self.magenta*100, self.yellow*100, self.black*100),' ',','),
|
||||
(self.spotName and (',spotName='+repr(self.spotName)) or ''),
|
||||
(self.density!=1 and (',density='+fp_str(self.density*100)) or ''),
|
||||
(self.knockout is not None and (',knockout=%d' % self.knockout) or ''),
|
||||
)
|
||||
|
||||
def cmyk2rgb((c,m,y,k),density=1):
|
||||
"Convert from a CMYK color tuple to an RGB color tuple"
|
||||
# From the Adobe Postscript Ref. Manual 2nd ed.
|
||||
r = 1.0 - min(1.0, c + k)
|
||||
g = 1.0 - min(1.0, m + k)
|
||||
b = 1.0 - min(1.0, y + k)
|
||||
return (r,g,b)
|
||||
|
||||
def rgb2cmyk(r,g,b):
|
||||
'''one way to get cmyk from rgb'''
|
||||
c = 1 - r
|
||||
m = 1 - g
|
||||
y = 1 - b
|
||||
k = min(c,m,y)
|
||||
c = min(1,max(0,c-k))
|
||||
m = min(1,max(0,m-k))
|
||||
y = min(1,max(0,y-k))
|
||||
k = min(1,max(0,k))
|
||||
return (c,m,y,k)
|
||||
|
||||
def color2bw(colorRGB):
|
||||
"Transform an RGB color to a black and white equivalent."
|
||||
|
||||
col = colorRGB
|
||||
r, g, b = col.red, col.green, col.blue
|
||||
n = (r + g + b) / 3.0
|
||||
bwColorRGB = Color(n, n, n)
|
||||
return bwColorRGB
|
||||
|
||||
def HexColor(val):
|
||||
"""This function converts a hex string, or an actual integer number,
|
||||
into the corresponding color. E.g., in "AABBCC" or 0xAABBCC,
|
||||
AA is the red, BB is the green, and CC is the blue (00-FF).
|
||||
|
||||
HTML uses a hex string with a preceding hash; if this is present,
|
||||
it is stripped off. (AR, 3-3-2000)
|
||||
|
||||
For completeness I assume that #aabbcc or 0xaabbcc are hex numbers
|
||||
otherwise a pure integer is converted as decimal rgb
|
||||
"""
|
||||
|
||||
if type(val) == StringType:
|
||||
b = 10
|
||||
if val[:1] == '#':
|
||||
val = val[1:]
|
||||
b = 16
|
||||
elif string.lower(val[:2]) == '0x':
|
||||
b = 16
|
||||
val = val[2:]
|
||||
val = string.atoi(val,b)
|
||||
return Color(((val>>16)&0xFF)/255.0,((val>>8)&0xFF)/255.0,(val&0xFF)/255.0)
|
||||
|
||||
def linearlyInterpolatedColor(c0, c1, x0, x1, x):
|
||||
"""
|
||||
Linearly interpolates colors. Can handle RGB, CMYK and PCMYK
|
||||
colors - give ValueError if colours aren't the same.
|
||||
Doesn't currently handle 'Spot Color Interpolation'.
|
||||
"""
|
||||
|
||||
if c0.__class__ != c1.__class__:
|
||||
raise ValueError, "Color classes must be the same for interpolation!"
|
||||
if x1<x0:
|
||||
x0,x1,c0,c1 = x1,x0,c1,c0 # normalized so x1>x0
|
||||
if x<x0-1e-8 or x>x1+1e-8: # fudge factor for numerical problems
|
||||
raise ValueError, "Can't interpolate: x=%f is not between %f and %f!" % (x,x0,x1)
|
||||
if x<=x0:
|
||||
return c0
|
||||
elif x>=x1:
|
||||
return c1
|
||||
|
||||
cname = c0.__class__.__name__
|
||||
dx = float(x1-x0)
|
||||
x = x-x0
|
||||
|
||||
if cname == 'Color': # RGB
|
||||
r = c0.red+x*(c1.red - c0.red)/dx
|
||||
g = c0.green+x*(c1.green- c0.green)/dx
|
||||
b = c0.blue+x*(c1.blue - c0.blue)/dx
|
||||
return Color(r,g,b)
|
||||
elif cname == 'CMYKColor':
|
||||
c = c0.cyan+x*(c1.cyan - c0.cyan)/dx
|
||||
m = c0.magenta+x*(c1.magenta - c0.magenta)/dx
|
||||
y = c0.yellow+x*(c1.yellow - c0.yellow)/dx
|
||||
k = c0.black+x*(c1.black - c0.black)/dx
|
||||
d = c0.density+x*(c1.density - c0.density)/dx
|
||||
return CMYKColor(c,m,y,k, density=d)
|
||||
elif cname == 'PCMYKColor':
|
||||
if cmykDistance(c0,c1)<1e-8:
|
||||
#colors same do density and preserve spotName if any
|
||||
assert c0.spotName == c1.spotName, "Identical cmyk, but different spotName"
|
||||
c = c0.cyan
|
||||
m = c0.magenta
|
||||
y = c0.yellow
|
||||
k = c0.black
|
||||
d = c0.density+x*(c1.density - c0.density)/dx
|
||||
return PCMYKColor(c*100,m*100,y*100,k*100, density=d*100, spotName=c0.spotName)
|
||||
elif cmykDistance(c0,_CMYK_white)<1e-8:
|
||||
#special c0 is white
|
||||
c = c1.cyan
|
||||
m = c1.magenta
|
||||
y = c1.yellow
|
||||
k = c1.black
|
||||
d = x*c1.density/dx
|
||||
return PCMYKColor(c*100,m*100,y*100,k*100, density=d*100, spotName=c1.spotName)
|
||||
elif cmykDistance(c1,_CMYK_white)<1e-8:
|
||||
#special c1 is white
|
||||
c = c0.cyan
|
||||
m = c0.magenta
|
||||
y = c0.yellow
|
||||
k = c0.black
|
||||
d = x*c0.density/dx
|
||||
d = c0.density*(1-x/dx)
|
||||
return PCMYKColor(c*100,m*100,y*100,k*100, density=d*100, spotName=c0.spotName)
|
||||
else:
|
||||
c = c0.cyan+x*(c1.cyan - c0.cyan)/dx
|
||||
m = c0.magenta+x*(c1.magenta - c0.magenta)/dx
|
||||
y = c0.yellow+x*(c1.yellow - c0.yellow)/dx
|
||||
k = c0.black+x*(c1.black - c0.black)/dx
|
||||
d = c0.density+x*(c1.density - c0.density)/dx
|
||||
return PCMYKColor(c*100,m*100,y*100,k*100, density=d*100)
|
||||
else:
|
||||
raise ValueError, "Can't interpolate: Unknown color class %s!" % cname
|
||||
|
||||
# special case -- indicates no drawing should be done
|
||||
# this is a hangover from PIDDLE - suggest we ditch it since it is not used anywhere
|
||||
#transparent = Color(-1, -1, -1)
|
||||
|
||||
_CMYK_white=CMYKColor(0,0,0,0)
|
||||
_PCMYK_white=PCMYKColor(0,0,0,0)
|
||||
_CMYK_black=CMYKColor(0,0,0,1)
|
||||
_PCMYK_black=PCMYKColor(0,0,0,100)
|
||||
|
||||
# Special colors
|
||||
ReportLabBlueOLD = HexColor(0x4e5688)
|
||||
ReportLabBlue = HexColor(0x00337f)
|
||||
ReportLabBluePCMYK = PCMYKColor(100,65,0,30,spotName='Pantone 288U')
|
||||
ReportLabLightBlue = HexColor(0xb7b9d3)
|
||||
ReportLabFidBlue=HexColor(0x3366cc)
|
||||
ReportLabFidRed=HexColor(0xcc0033)
|
||||
ReportLabGreen = HexColor(0x336600)
|
||||
ReportLabLightGreen = HexColor(0x339933)
|
||||
|
||||
# color constants -- mostly from HTML standard
|
||||
aliceblue = HexColor(0xF0F8FF)
|
||||
antiquewhite = HexColor(0xFAEBD7)
|
||||
aqua = HexColor(0x00FFFF)
|
||||
aquamarine = HexColor(0x7FFFD4)
|
||||
azure = HexColor(0xF0FFFF)
|
||||
beige = HexColor(0xF5F5DC)
|
||||
bisque = HexColor(0xFFE4C4)
|
||||
black = HexColor(0x000000)
|
||||
blanchedalmond = HexColor(0xFFEBCD)
|
||||
blue = HexColor(0x0000FF)
|
||||
blueviolet = HexColor(0x8A2BE2)
|
||||
brown = HexColor(0xA52A2A)
|
||||
burlywood = HexColor(0xDEB887)
|
||||
cadetblue = HexColor(0x5F9EA0)
|
||||
chartreuse = HexColor(0x7FFF00)
|
||||
chocolate = HexColor(0xD2691E)
|
||||
coral = HexColor(0xFF7F50)
|
||||
cornflowerblue = cornflower = HexColor(0x6495ED)
|
||||
cornsilk = HexColor(0xFFF8DC)
|
||||
crimson = HexColor(0xDC143C)
|
||||
cyan = HexColor(0x00FFFF)
|
||||
darkblue = HexColor(0x00008B)
|
||||
darkcyan = HexColor(0x008B8B)
|
||||
darkgoldenrod = HexColor(0xB8860B)
|
||||
darkgray = HexColor(0xA9A9A9)
|
||||
darkgreen = HexColor(0x006400)
|
||||
darkkhaki = HexColor(0xBDB76B)
|
||||
darkmagenta = HexColor(0x8B008B)
|
||||
darkolivegreen = HexColor(0x556B2F)
|
||||
darkorange = HexColor(0xFF8C00)
|
||||
darkorchid = HexColor(0x9932CC)
|
||||
darkred = HexColor(0x8B0000)
|
||||
darksalmon = HexColor(0xE9967A)
|
||||
darkseagreen = HexColor(0x8FBC8B)
|
||||
darkslateblue = HexColor(0x483D8B)
|
||||
darkslategray = HexColor(0x2F4F4F)
|
||||
darkturquoise = HexColor(0x00CED1)
|
||||
darkviolet = HexColor(0x9400D3)
|
||||
deeppink = HexColor(0xFF1493)
|
||||
deepskyblue = HexColor(0x00BFFF)
|
||||
dimgray = HexColor(0x696969)
|
||||
dodgerblue = HexColor(0x1E90FF)
|
||||
firebrick = HexColor(0xB22222)
|
||||
floralwhite = HexColor(0xFFFAF0)
|
||||
forestgreen = HexColor(0x228B22)
|
||||
fuchsia = HexColor(0xFF00FF)
|
||||
gainsboro = HexColor(0xDCDCDC)
|
||||
ghostwhite = HexColor(0xF8F8FF)
|
||||
gold = HexColor(0xFFD700)
|
||||
goldenrod = HexColor(0xDAA520)
|
||||
gray = HexColor(0x808080)
|
||||
grey = gray
|
||||
green = HexColor(0x008000)
|
||||
greenyellow = HexColor(0xADFF2F)
|
||||
honeydew = HexColor(0xF0FFF0)
|
||||
hotpink = HexColor(0xFF69B4)
|
||||
indianred = HexColor(0xCD5C5C)
|
||||
indigo = HexColor(0x4B0082)
|
||||
ivory = HexColor(0xFFFFF0)
|
||||
khaki = HexColor(0xF0E68C)
|
||||
lavender = HexColor(0xE6E6FA)
|
||||
lavenderblush = HexColor(0xFFF0F5)
|
||||
lawngreen = HexColor(0x7CFC00)
|
||||
lemonchiffon = HexColor(0xFFFACD)
|
||||
lightblue = HexColor(0xADD8E6)
|
||||
lightcoral = HexColor(0xF08080)
|
||||
lightcyan = HexColor(0xE0FFFF)
|
||||
lightgoldenrodyellow = HexColor(0xFAFAD2)
|
||||
lightgreen = HexColor(0x90EE90)
|
||||
lightgrey = HexColor(0xD3D3D3)
|
||||
lightpink = HexColor(0xFFB6C1)
|
||||
lightsalmon = HexColor(0xFFA07A)
|
||||
lightseagreen = HexColor(0x20B2AA)
|
||||
lightskyblue = HexColor(0x87CEFA)
|
||||
lightslategray = HexColor(0x778899)
|
||||
lightsteelblue = HexColor(0xB0C4DE)
|
||||
lightyellow = HexColor(0xFFFFE0)
|
||||
lime = HexColor(0x00FF00)
|
||||
limegreen = HexColor(0x32CD32)
|
||||
linen = HexColor(0xFAF0E6)
|
||||
magenta = HexColor(0xFF00FF)
|
||||
maroon = HexColor(0x800000)
|
||||
mediumaquamarine = HexColor(0x66CDAA)
|
||||
mediumblue = HexColor(0x0000CD)
|
||||
mediumorchid = HexColor(0xBA55D3)
|
||||
mediumpurple = HexColor(0x9370DB)
|
||||
mediumseagreen = HexColor(0x3CB371)
|
||||
mediumslateblue = HexColor(0x7B68EE)
|
||||
mediumspringgreen = HexColor(0x00FA9A)
|
||||
mediumturquoise = HexColor(0x48D1CC)
|
||||
mediumvioletred = HexColor(0xC71585)
|
||||
midnightblue = HexColor(0x191970)
|
||||
mintcream = HexColor(0xF5FFFA)
|
||||
mistyrose = HexColor(0xFFE4E1)
|
||||
moccasin = HexColor(0xFFE4B5)
|
||||
navajowhite = HexColor(0xFFDEAD)
|
||||
navy = HexColor(0x000080)
|
||||
oldlace = HexColor(0xFDF5E6)
|
||||
olive = HexColor(0x808000)
|
||||
olivedrab = HexColor(0x6B8E23)
|
||||
orange = HexColor(0xFFA500)
|
||||
orangered = HexColor(0xFF4500)
|
||||
orchid = HexColor(0xDA70D6)
|
||||
palegoldenrod = HexColor(0xEEE8AA)
|
||||
palegreen = HexColor(0x98FB98)
|
||||
paleturquoise = HexColor(0xAFEEEE)
|
||||
palevioletred = HexColor(0xDB7093)
|
||||
papayawhip = HexColor(0xFFEFD5)
|
||||
peachpuff = HexColor(0xFFDAB9)
|
||||
peru = HexColor(0xCD853F)
|
||||
pink = HexColor(0xFFC0CB)
|
||||
plum = HexColor(0xDDA0DD)
|
||||
powderblue = HexColor(0xB0E0E6)
|
||||
purple = HexColor(0x800080)
|
||||
red = HexColor(0xFF0000)
|
||||
rosybrown = HexColor(0xBC8F8F)
|
||||
royalblue = HexColor(0x4169E1)
|
||||
saddlebrown = HexColor(0x8B4513)
|
||||
salmon = HexColor(0xFA8072)
|
||||
sandybrown = HexColor(0xF4A460)
|
||||
seagreen = HexColor(0x2E8B57)
|
||||
seashell = HexColor(0xFFF5EE)
|
||||
sienna = HexColor(0xA0522D)
|
||||
silver = HexColor(0xC0C0C0)
|
||||
skyblue = HexColor(0x87CEEB)
|
||||
slateblue = HexColor(0x6A5ACD)
|
||||
slategray = HexColor(0x708090)
|
||||
snow = HexColor(0xFFFAFA)
|
||||
springgreen = HexColor(0x00FF7F)
|
||||
steelblue = HexColor(0x4682B4)
|
||||
tan = HexColor(0xD2B48C)
|
||||
teal = HexColor(0x008080)
|
||||
thistle = HexColor(0xD8BFD8)
|
||||
tomato = HexColor(0xFF6347)
|
||||
turquoise = HexColor(0x40E0D0)
|
||||
violet = HexColor(0xEE82EE)
|
||||
wheat = HexColor(0xF5DEB3)
|
||||
white = HexColor(0xFFFFFF)
|
||||
whitesmoke = HexColor(0xF5F5F5)
|
||||
yellow = HexColor(0xFFFF00)
|
||||
yellowgreen = HexColor(0x9ACD32)
|
||||
fidblue=HexColor(0x3366cc)
|
||||
fidlightblue=HexColor("#d6e0f5")
|
||||
|
||||
ColorType=type(black)
|
||||
|
||||
################################################################
|
||||
#
|
||||
# Helper functions for dealing with colors. These tell you
|
||||
# which are predefined, so you can print color charts;
|
||||
# and can give the nearest match to an arbitrary color object
|
||||
#
|
||||
#################################################################
|
||||
|
||||
def colorDistance(col1, col2):
|
||||
"""Returns a number between 0 and root(3) stating how similar
|
||||
two colours are - distance in r,g,b, space. Only used to find
|
||||
names for things."""
|
||||
return math.sqrt(
|
||||
(col1.red - col2.red)**2 +
|
||||
(col1.green - col2.green)**2 +
|
||||
(col1.blue - col2.blue)**2
|
||||
)
|
||||
|
||||
def cmykDistance(col1, col2):
|
||||
"""Returns a number between 0 and root(4) stating how similar
|
||||
two colours are - distance in r,g,b, space. Only used to find
|
||||
names for things."""
|
||||
return math.sqrt(
|
||||
(col1.cyan - col2.cyan)**2 +
|
||||
(col1.magenta - col2.magenta)**2 +
|
||||
(col1.yellow - col2.yellow)**2 +
|
||||
(col1.black - col2.black)**2
|
||||
)
|
||||
|
||||
_namedColors = None
|
||||
|
||||
def getAllNamedColors():
|
||||
#returns a dictionary of all the named ones in the module
|
||||
# uses a singleton for efficiency
|
||||
global _namedColors
|
||||
if _namedColors is not None: return _namedColors
|
||||
import colors
|
||||
_namedColors = {}
|
||||
for (name, value) in colors.__dict__.items():
|
||||
if isinstance(value, Color):
|
||||
_namedColors[name] = value
|
||||
|
||||
return _namedColors
|
||||
|
||||
def describe(aColor,mode=0):
|
||||
'''finds nearest colour match to aColor.
|
||||
mode=0 print a string desription
|
||||
mode=1 return a string description
|
||||
mode=2 return (distance, colorName)
|
||||
'''
|
||||
namedColors = getAllNamedColors()
|
||||
closest = (10, None, None) #big number, name, color
|
||||
for (name, color) in namedColors.items():
|
||||
distance = colorDistance(aColor, color)
|
||||
if distance < closest[0]:
|
||||
closest = (distance, name, color)
|
||||
if mode<=1:
|
||||
s = 'best match is %s, distance %0.4f' % (closest[1], closest[0])
|
||||
if mode==0: print s
|
||||
else: return s
|
||||
elif mode==2:
|
||||
return (closest[1], closest[0])
|
||||
else:
|
||||
raise ValueError, "Illegal value for mode "+str(mode)
|
||||
|
||||
def toColor(arg,default=None):
|
||||
'''try to map an arbitrary arg to a color instance'''
|
||||
if isinstance(arg,Color): return arg
|
||||
tArg = type(arg)
|
||||
if tArg in _SeqTypes:
|
||||
assert 3<=len(arg)<=4, 'Can only convert 3 and 4 sequences to color'
|
||||
assert 0<=min(arg) and max(arg)<=1
|
||||
return len(arg)==3 and Color(arg[0],arg[1],arg[2]) or CMYKColor(arg[0],arg[1],arg[2],arg[3])
|
||||
elif tArg == StringType:
|
||||
C = getAllNamedColors()
|
||||
s = string.lower(arg)
|
||||
if C.has_key(s): return C[s]
|
||||
try:
|
||||
return toColor(eval(arg))
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
return HexColor(arg)
|
||||
except:
|
||||
if default is None:
|
||||
raise 'Invalid color value', str(arg)
|
||||
return default
|
||||
|
||||
def toColorOrNone(arg,default=None):
|
||||
'''as above but allows None as a legal value'''
|
||||
if arg is None:
|
||||
return None
|
||||
else:
|
||||
return toColor(arg, default)
|
||||
|
||||
def setColors(**kw):
|
||||
UNDEF = []
|
||||
progress = 1
|
||||
assigned = {}
|
||||
while kw and progress:
|
||||
progress = 0
|
||||
for k, v in kw.items():
|
||||
if type(v) in (type(()),type([])):
|
||||
c = map(lambda x,UNDEF=UNDEF: toColor(x,UNDEF),v)
|
||||
if type(v) is type(()): c = tuple(c)
|
||||
ok = UNDEF not in c
|
||||
else:
|
||||
c = toColor(v,UNDEF)
|
||||
ok = c is not UNDEF
|
||||
if ok:
|
||||
assigned[k] = c
|
||||
del kw[k]
|
||||
progress = 1
|
||||
|
||||
if kw: raise ValueError("Can't convert\n%s" % str(kw))
|
||||
getAllNamedColors()
|
||||
for k, c in assigned.items():
|
||||
globals()[k] = c
|
||||
if isinstance(c,Color): _namedColors[k] = c
|
||||
|
||||
def Whiter(c,f):
|
||||
'''given a color combine with white as c*f w*(1-f) 0<=f<=1'''
|
||||
c = toColor(c)
|
||||
if isinstance(c,PCMYKColor):
|
||||
w = _PCMYK_white
|
||||
elif isinstance(c,CMYKColor): w = _CMYK_white
|
||||
else: w = white
|
||||
return linearlyInterpolatedColor(w, c, 0, 1, f)
|
||||
|
||||
def Blacker(c,f):
|
||||
'''given a color combine with black as c*f+b*(1-f) 0<=f<=1'''
|
||||
c = toColor(c)
|
||||
if isinstance(c,PCMYKColor):
|
||||
b = _PCMYK_black
|
||||
elif isinstance(c,CMYKColor): b = _CMYK_black
|
||||
else: b = black
|
||||
return linearlyInterpolatedColor(b, c, 0, 1, f)
|
|
@ -1,443 +0,0 @@
|
|||
#! /usr/bin/python2.3
|
||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/corp.py
|
||||
""" This module includes some reusable routines for ReportLab's
|
||||
'Corporate Image' - the logo, standard page backdrops and
|
||||
so on - you are advised to do the same for your own company!"""
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
from reportlab.lib.units import inch,cm
|
||||
from reportlab.lib.validators import *
|
||||
from reportlab.lib.attrmap import *
|
||||
from reportlab.graphics.shapes import definePath, Group, Drawing, Rect, PolyLine, String
|
||||
from reportlab.graphics.widgetbase import Widget
|
||||
from reportlab.lib.colors import Color, black, white, ReportLabBlue
|
||||
from reportlab.pdfbase.pdfmetrics import stringWidth
|
||||
from math import sin, pi
|
||||
|
||||
class RL_CorpLogo(Widget):
|
||||
'''Dinu's fat letter logo as hacked into decent paths by Robin'''
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber,'Logo x-coord'),
|
||||
y = AttrMapValue(isNumber,'Logo y-coord'),
|
||||
angle = AttrMapValue(isNumber,'Logo rotation'),
|
||||
strokeColor = AttrMapValue(isColorOrNone, 'Logo lettering stroke color'),
|
||||
fillColor = AttrMapValue(isColorOrNone, 'Logo lettering fill color'),
|
||||
strokeWidth = AttrMapValue(isNumber,'Logo lettering stroke width'),
|
||||
background = AttrMapValue(isColorOrNone,desc="Logo background color"),
|
||||
border = AttrMapValue(isColorOrNone,desc="Logo border color"),
|
||||
borderWidth = AttrMapValue(isNumber,desc="Logo border width (1)"),
|
||||
shadow = AttrMapValue(isNumberOrNone,desc="None or fraction of background for shadowing" ),
|
||||
width = AttrMapValue(isNumber, desc="width in points of the logo (default 129)"),
|
||||
height = AttrMapValue(isNumber, desc="height in points of the logo (default 86)"),
|
||||
skewX = AttrMapValue(isNumber, desc="x-skew of the logo (default 10)"),
|
||||
skewY = AttrMapValue(isNumber, desc="y-skew of the logo (default 0)"),
|
||||
showPage = AttrMapValue(isBoolean, desc="If true show the page lines"),
|
||||
xFlip = AttrMapValue(isBoolean, desc="If true do x reversal"),
|
||||
yFlip = AttrMapValue(isBoolean, desc="If true do y reversal"),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.fillColor = white
|
||||
self.strokeColor = None
|
||||
self.strokeWidth = 0.1
|
||||
self.background = ReportLabBlue
|
||||
self.border = None
|
||||
self.borderWidth = 1
|
||||
self.shadow = 0.5
|
||||
self.height = 86
|
||||
self.width = 130
|
||||
self.x = self.y = self.angle = self.skewY = self._dx = 0
|
||||
self.skewX = 10
|
||||
self._dy = 35.5
|
||||
self.showPage = 1
|
||||
|
||||
def demo(self):
|
||||
D = Drawing(self.width, self.height)
|
||||
D.add(self)
|
||||
return D
|
||||
|
||||
def _paintLogo(self, g, dx=0, dy=0, strokeColor=None, strokeWidth=0.1, fillColor=white):
|
||||
P = [
|
||||
('moveTo' ,15.7246,0 ), ('lineTo' ,9.49521,0 ), ('lineTo' ,6.64988,6.83711 ), ('curveTo' ,6.62224,6.95315 ,6.57391,7.10646 ,6.50485,7.29708 ), ('curveTo' ,6.43578,7.48767 ,6.35059,7.71559 ,6.24931,7.98079 ), ('lineTo' ,6.29074,6.71282 ), ('lineTo' ,6.29074,0 ), ('lineTo' ,0.55862,0 ), ('lineTo' ,0.55862,19.19365 ), ('lineTo' ,6.45649,19.19365 ), ('curveTo' ,9.05324,19.19365 ,10.99617,18.73371 ,12.28532,17.8138 ), ('curveTo' ,13.92439,16.63697 ,14.7439,14.96293 ,14.7439,12.79161 ), ('curveTo' ,14.7439,10.47114 ,13.64354,8.86755 ,11.44276,7.98079 ), 'closePath', ('moveTo' ,6.31838,10.30542 ), ('lineTo' ,6.70513,10.30542 ), ('curveTo' ,7.36812,10.30542 ,7.92062,10.53331 ,8.36261,10.98912 ), ('curveTo' ,8.80461,11.44491 ,9.0256,12.02504 ,9.0256,12.72947 ), ('curveTo' ,9.0256,14.16321 ,8.19227,14.88004 ,6.52556,14.88004 ), ('lineTo' ,6.31838,14.88004 ), 'closePath',
|
||||
('moveTo' ,25.06173,4.54978 ), ('lineTo' ,30.47611,4.45033 ), ('curveTo' ,30.08951,2.88402 ,29.33668,1.70513 ,28.21787,0.91369 ), ('curveTo' ,27.09906,0.12223 ,25.63726,-0.27348 ,23.83245,-0.27348 ), ('curveTo' ,21.69611,-0.27348 ,20.02024,0.32322 ,18.80475,1.5166 ), ('curveTo' ,17.59846,2.72658 ,16.99531,4.37988 ,16.99531,6.47662 ), ('curveTo' ,16.99531,8.6065 ,17.64451,10.34269 ,18.94286,11.68527 ), ('curveTo' ,20.24124,13.03612 ,21.91711,13.71152 ,23.97056,13.71152 ), ('curveTo' ,26.01482,13.71152 ,27.64466,13.06096 ,28.86015,11.75985 ), ('curveTo' ,30.07566,10.45042 ,30.68326,8.71423 ,30.68326,6.5512 ), ('lineTo' ,30.65586,5.66859 ), ('lineTo' ,22.53407,5.66859 ), ('curveTo' ,22.59855,4.29287 ,23.03132,3.60503 ,23.83245,3.60503 ), ('curveTo' ,24.45861,3.60503 ,24.86837,3.91994 ,25.06173,4.54978 ), 'closePath', ('moveTo' ,25.18604,8.35371 ), ('curveTo' ,25.18604,8.60235 ,25.15384,8.83024 ,25.08937,9.03742 ), ('curveTo' ,25.02489,9.24463 ,24.93514,9.42278 ,24.82001,9.57197 ), ('curveTo' ,24.70492,9.72113 ,24.56911,9.83923 ,24.41255,9.92624 ), ('curveTo' ,24.25603,10.01326 ,24.08568,10.05678 ,23.90152,10.05678 ), ('curveTo' ,23.51474,10.05678 ,23.20169,9.89725 ,22.96225,9.57819 ), ('curveTo' ,22.72283,9.25913 ,22.60314,8.85096 ,22.60314,8.35371 ), 'closePath',
|
||||
('moveTo' ,38.36308,-5.99181 ), ('lineTo' ,32.82428,-5.99181 ), ('lineTo' ,32.82428,13.43804 ), ('lineTo' ,38.36308,13.43804 ), ('lineTo' ,38.23873,11.53608 ), ('curveTo' ,38.46886,11.93387 ,38.70371,12.27159 ,38.94327,12.54922 ), ('curveTo' ,39.18254,12.82685 ,39.44037,13.05268 ,39.71676,13.22671 ), ('curveTo' ,39.99286,13.40074 ,40.28988,13.52712 ,40.60753,13.60585 ), ('curveTo' ,40.92518,13.68459 ,41.27759,13.72396 ,41.66419,13.72396 ), ('curveTo' ,43.10068,13.72396 ,44.2702,13.07755 ,45.17246,11.78472 ), ('curveTo' ,46.06588,10.50844 ,46.51229,8.81368 ,46.51229,6.70038 ), ('curveTo' ,46.51229,4.55394 ,46.08415,2.85502 ,45.22785,1.60362 ), ('curveTo' ,44.38983,0.35221 ,43.23416,-0.27348 ,41.76084,-0.27348 ), ('curveTo' ,40.41659,-0.27348 ,39.24235,0.42679 ,38.23873,1.82739 ), ('curveTo' ,38.2847,1.40472 ,38.31239,1.04007 ,38.32153,0.73345 ), ('curveTo' ,38.34923,0.41851 ,38.36308,0.04146 ,38.36308,-0.3978 ), 'closePath', ('moveTo' ,40.7802,6.84954 ), ('curveTo' ,40.7802,7.72802 ,40.66734,8.40964 ,40.44193,8.89448 ), ('curveTo' ,40.21621,9.37929 ,39.89621,9.62168 ,39.48191,9.62168 ), ('curveTo' ,38.62533,9.62168 ,38.19718,8.68108 ,38.19718,6.79983 ), ('curveTo' ,38.19718,4.87712 ,38.61177,3.91581 ,39.44037,3.91581 ), ('curveTo' ,39.85466,3.91581 ,40.18174,4.1727 ,40.42101,4.68654 ), ('curveTo' ,40.66057,5.20037 ,40.7802,5.92135 ,40.7802,6.84954 ), 'closePath',
|
||||
('moveTo' ,62.10648,6.51392 ), ('curveTo' ,62.10648,4.44205 ,61.47118,2.79288 ,60.2003,1.56631 ), ('curveTo' ,58.92971,0.33978 ,57.22626,-0.27348 ,55.08965,-0.27348 ), ('curveTo' ,52.99018,-0.27348 ,51.31914,0.35221 ,50.07595,1.60362 ), ('curveTo' ,48.8419,2.8633 ,48.22517,4.55394 ,48.22517,6.67551 ), ('curveTo' ,48.22517,8.79709 ,48.85575,10.50016 ,50.1175,11.78472 ), ('curveTo' ,51.36982,13.07755 ,53.03172,13.72396 ,55.1035,13.72396 ), ('curveTo' ,57.28608,13.72396 ,58.99866,13.08168 ,60.24185,11.79712 ), ('curveTo' ,61.48503,10.51259 ,62.10648,8.75154 ,62.10648,6.51392 ), 'closePath', ('moveTo' ,56.73358,6.67551 ), ('curveTo' ,56.73358,7.17276 ,56.69675,7.62236 ,56.62308,8.02428 ), ('curveTo' ,56.54942,8.42623 ,56.44334,8.77016 ,56.30544,9.05607 ), ('curveTo' ,56.16724,9.34198 ,56.00134,9.56369 ,55.80804,9.72113 ), ('curveTo' ,55.61474,9.8786 ,55.39817,9.95733 ,55.1589,9.95733 ), ('curveTo' ,54.68921,9.95733 ,54.31174,9.65898 ,54.02621,9.06229 ), ('curveTo' ,53.74068,8.54018 ,53.59807,7.75702 ,53.59807,6.71282 ), ('curveTo' ,53.59807,5.68515 ,53.74068,4.90202 ,54.02621,4.36332 ), ('curveTo' ,54.31174,3.76663 ,54.69392,3.46828 ,55.17275,3.46828 ), ('curveTo' ,55.62388,3.46828 ,55.99692,3.7625 ,56.29159,4.35088 ), ('curveTo' ,56.58625,5.0056 ,56.73358,5.78047 ,56.73358,6.67551 ), 'closePath',
|
||||
('moveTo' ,69.78629,0 ), ('lineTo' ,64.2475,0 ), ('lineTo' ,64.2475,13.43804 ), ('lineTo' ,69.78629,13.43804 ), ('lineTo' ,69.49605,10.81507 ), ('curveTo' ,70.33407,12.77921 ,71.71988,13.76126 ,73.65346,13.76126 ), ('lineTo' ,73.65346,8.16725 ), ('curveTo' ,73.04586,8.4656 ,72.5302,8.61478 ,72.10647,8.61478 ), ('curveTo' ,71.36068,8.61478 ,70.78756,8.37236 ,70.38711,7.88755 ), ('curveTo' ,69.98637,7.40274 ,69.78629,6.69623 ,69.78629,5.76804 ), 'closePath',
|
||||
('moveTo' ,81.55427,0 ), ('lineTo' ,76.00163,0 ), ('lineTo' ,76.00163,9.42278 ), ('lineTo' ,74.42725,9.42278 ), ('lineTo' ,74.42725,13.43804 ), ('lineTo' ,76.00163,13.43804 ), ('lineTo' ,76.00163,17.39113 ), ('lineTo' ,81.55427,17.39113 ), ('lineTo' ,81.55427,13.43804 ), ('lineTo' ,83.39121,13.43804 ), ('lineTo' ,83.39121,9.42278 ), ('lineTo' ,81.55427,9.42278 ), 'closePath',
|
||||
('moveTo' ,95.17333,0 ), ('lineTo' ,85.09024,0 ), ('lineTo' ,85.09024,19.19365 ), ('lineTo' ,90.85002,19.19365 ), ('lineTo' ,90.85002,4.61196 ), ('lineTo' ,95.17333,4.61196 ), 'closePath',
|
||||
('moveTo' ,110.00787,0 ), ('lineTo' ,104.45523,0 ), ('curveTo' ,104.5012,0.44754 ,104.53803,0.87433 ,104.56573,1.2804 ), ('curveTo' ,104.59313,1.68651 ,104.62083,2.01385 ,104.64853,2.26246 ), ('curveTo' ,103.69087,0.57182 ,102.40644,-0.27348 ,100.79492,-0.27348 ), ('curveTo' ,99.39527,-0.27348 ,98.28557,0.35637 ,97.46611,1.61605 ), ('curveTo' ,96.65578,2.86746 ,96.25062,4.59952 ,96.25062,6.81227 ), ('curveTo' ,96.25062,8.95041 ,96.66963,10.63276 ,97.50765,11.8593 ), ('curveTo' ,98.34538,13.10242 ,99.4872,13.72396 ,100.93312,13.72396 ), ('curveTo' ,102.41557,13.72396 ,103.61249,12.92008 ,104.52418,11.31231 ), ('curveTo' ,104.50591,11.47806 ,104.49206,11.62309 ,104.48293,11.74741 ), ('curveTo' ,104.4735,11.87173 ,104.46437,11.9753 ,104.45523,12.05819 ), ('lineTo' ,104.39983,12.84135 ), ('lineTo' ,104.35858,13.43804 ), ('lineTo' ,110.00787,13.43804 ), 'closePath', ('moveTo' ,104.39983,6.88685 ), ('curveTo' ,104.39983,7.38409 ,104.37921,7.80676 ,104.33766,8.15481 ), ('curveTo' ,104.29641,8.5029 ,104.22952,8.78672 ,104.13758,9.00636 ), ('curveTo' ,104.04535,9.22598 ,103.92572,9.38341 ,103.77839,9.47874 ), ('curveTo' ,103.63106,9.57403 ,103.45161,9.62168 ,103.23974,9.62168 ), ('curveTo' ,102.30036,9.62168 ,101.83096,8.49875 ,101.83096,6.25285 ), ('curveTo' ,101.83096,4.64508 ,102.24967,3.8412 ,103.0877,3.8412 ), ('curveTo' ,103.96255,3.8412 ,104.39983,4.85641 ,104.39983,6.88685 ), 'closePath',
|
||||
('moveTo' ,118.22604,0 ), ('lineTo' ,112.5629,0 ), ('lineTo' ,112.5629,20.99616 ), ('lineTo' ,118.10169,20.99616 ), ('lineTo' ,118.10169,13.63694 ), ('curveTo' ,118.10169,13.01538 ,118.07399,12.30268 ,118.01889,11.49877 ), ('curveTo' ,118.52542,12.31096 ,119.03636,12.88693 ,119.55202,13.22671 ), ('curveTo' ,120.08625,13.55821 ,120.75838,13.72396 ,121.5687,13.72396 ), ('curveTo' ,123.07885,13.72396 ,124.24837,13.09827 ,125.07697,11.84686 ), ('curveTo' ,125.90586,10.60373 ,126.32015,8.85099 ,126.32015,6.5885 ), ('curveTo' ,126.32015,4.42546 ,125.89201,2.74314 ,125.03571,1.54147 ), ('curveTo' ,124.18826,0.3315 ,123.01432,-0.27348 ,121.51331,-0.27348 ), ('curveTo' ,120.78608,-0.27348 ,120.16905,-0.12432 ,119.66252,0.17403 ), ('curveTo' ,119.41383,0.3315 ,119.15835,0.54283 ,118.8961,0.80803 ), ('curveTo' ,118.63356,1.07322 ,118.36866,1.40472 ,118.10169,1.80252 ), ('curveTo' ,118.11112,1.64505 ,118.12025,1.51039 ,118.12939,1.3985 ), ('curveTo' ,118.13852,1.28662 ,118.14766,1.19339 ,118.15709,1.11881 ), 'closePath', ('moveTo' ,120.58806,6.70038 ), ('curveTo' ,120.58806,8.62306 ,120.11837,9.5844 ,119.17898,9.5844 ), ('curveTo' ,118.35039,9.5844 ,117.93609,8.67693 ,117.93609,6.86198 ), ('curveTo' ,117.93609,4.96417 ,118.36424,4.01526 ,119.22053,4.01526 ), ('curveTo' ,120.13222,4.01526 ,120.58806,4.91027 ,120.58806,6.70038 ), 'closePath',
|
||||
] + (self.showPage and [
|
||||
('moveTo',38.30626,-7.28346),('lineTo',38.30626,-25.55261),('lineTo',85.15777,-25.55261),('lineTo',85.15777,-1.39019),('lineTo',90.46172,-1.39019),('lineTo',90.46172,-31.15121),('lineTo',32.70766,-31.15121),('lineTo',32.70766,-7.28346), 'closePath',
|
||||
('moveTo' ,32.70766,14.52164 ), ('lineTo' ,32.70766,47.81862 ), ('lineTo' ,80.14849,47.81862 ), ('lineTo' ,90.46172,37.21073 ), ('lineTo' ,90.46172,20.12025 ), ('lineTo' ,85.15777,20.12025 ), ('lineTo' ,85.15777,30.72814 ), ('lineTo' ,73.66589,30.72814 ), ('lineTo' ,73.66589,42.22002 ), ('lineTo' ,38.30626,42.22002 ), ('lineTo' ,38.30626,14.52164 ), 'closePath', ('moveTo' ,79.2645,36.32674 ), ('lineTo' ,85.15777,36.32674 ), ('lineTo' ,79.2645,42.22002 ), 'closePath',
|
||||
] or [])
|
||||
g.add(definePath(P,strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor, dx=dx, dy=dy))
|
||||
|
||||
def draw(self):
|
||||
fillColor = self.fillColor
|
||||
strokeColor = self.strokeColor
|
||||
g = Group()
|
||||
bg = self.background
|
||||
bd = self.border
|
||||
bdw = self.borderWidth
|
||||
shadow = self.shadow
|
||||
x, y = self.x, self.y
|
||||
if bg:
|
||||
if shadow is not None and 0<=shadow<1:
|
||||
shadow = Color(bg.red*shadow,bg.green*shadow,bg.blue*shadow)
|
||||
self._paintLogo(g,dy=-2.5, dx=2,fillColor=shadow)
|
||||
self._paintLogo(g,fillColor=fillColor,strokeColor=strokeColor)
|
||||
g.skew(kx=self.skewX, ky=self.skewY)
|
||||
g.shift(self._dx,self._dy)
|
||||
G = Group()
|
||||
G.add(g)
|
||||
_w, _h = 130, 86
|
||||
w, h = self.width, self.height
|
||||
if bg or (bd and bdw):
|
||||
G.insert(0,Rect(0,0,_w,_h,fillColor=bg,strokeColor=bd,strokeWidth=bdw))
|
||||
if w!=_w or h!=_h: G.scale(w/float(_w),h/float(_h))
|
||||
|
||||
angle = self.angle
|
||||
if self.angle:
|
||||
w, h = w/2., h/2.
|
||||
G.shift(-w,-h)
|
||||
G.rotate(angle)
|
||||
G.shift(w,h)
|
||||
xFlip = getattr(self,'xFlip',0) and -1 or 0
|
||||
yFlip = getattr(self,'yFlip',0) and -1 or 0
|
||||
if xFlip or yFlip:
|
||||
sx = xFlip or 1
|
||||
sy = yFlip or 1
|
||||
G.shift(sx*x+w*xFlip,sy*y+yFlip*h)
|
||||
G = Group(G,transform=(sx,0,0,sy,0,0))
|
||||
else:
|
||||
G.shift(x,y)
|
||||
return G
|
||||
|
||||
class RL_CorpLogoReversed(RL_CorpLogo):
|
||||
def __init__(self):
|
||||
RL_CorpLogo.__init__(self)
|
||||
self.background = white
|
||||
self.fillColor = ReportLabBlue
|
||||
|
||||
class RL_CorpLogoThin(Widget):
|
||||
"""The ReportLab Logo.
|
||||
|
||||
New version created by John Precedo on 7-8 August 2001.
|
||||
Based on bitmapped imaged from E-Id.
|
||||
Improved by Robin Becker."""
|
||||
|
||||
_attrMap = AttrMap(
|
||||
x = AttrMapValue(isNumber),
|
||||
y = AttrMapValue(isNumber),
|
||||
height = AttrMapValue(isNumberOrNone),
|
||||
width = AttrMapValue(isNumberOrNone),
|
||||
fillColor = AttrMapValue(isColorOrNone),
|
||||
strokeColor = AttrMapValue( isColorOrNone)
|
||||
)
|
||||
|
||||
_h = 90.5
|
||||
_w = 136.5
|
||||
_text='R e p o r t L a b'
|
||||
_fontName = 'Helvetica-Bold'
|
||||
_fontSize = 16
|
||||
|
||||
def __init__(self):
|
||||
self.fillColor = ReportLabBlue
|
||||
self.strokeColor = white
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.height = self._h
|
||||
self.width = self._w
|
||||
|
||||
def demo(self):
|
||||
D = Drawing(self.width, self.height)
|
||||
D.add(self)
|
||||
return D
|
||||
|
||||
def _getText(self, x=0, y=0, color=None):
|
||||
return String(x,y, self._text, fontName=self._fontName, fontSize=self._fontSize, fillColor=color)
|
||||
|
||||
def _sw(self,f=None,l=None):
|
||||
text = self._text
|
||||
if f is None: f = 0
|
||||
if l is None: l = len(text)
|
||||
return stringWidth(text[f:l],self._fontName,self._fontSize)
|
||||
|
||||
def _addPage(self, g, strokeWidth=3, color=None, dx=0, dy=0):
|
||||
x1, x2 = 31.85+dx, 80.97+dx
|
||||
fL = 10 # fold length
|
||||
y1, y2 = dy-34, dy+50.5
|
||||
L = [[x1,dy-4,x1,y1, x2, y1, x2, dy-1],
|
||||
[x1,dy+11,x1,y2,x2-fL,y2,x2,y2-fL,x2,dy+14],
|
||||
[x2-10,y2,x2-10,y2-fL,x2,y2-fL]]
|
||||
|
||||
for l in L:
|
||||
g.add(PolyLine(l, strokeWidth=strokeWidth, strokeColor=color, strokeLineJoin=0))
|
||||
|
||||
def draw(self):
|
||||
sx = 0.5
|
||||
fillColor = self.fillColor
|
||||
strokeColor = self.strokeColor
|
||||
shadow = Color(fillColor.red*sx,fillColor.green*sx,fillColor.blue*sx)
|
||||
g = Group()
|
||||
g2= Group()
|
||||
g.add(Rect(fillColor=fillColor, strokeColor=fillColor, x=0, y=0, width=self._w, height=self._h))
|
||||
sx = (self._w-2)/self._sw()
|
||||
g2.scale(sx,1)
|
||||
self._addPage(g2,strokeWidth=3,dx=2,dy=-2.5,color=shadow)
|
||||
self._addPage(g2,strokeWidth=3,color=strokeColor)
|
||||
g2.scale(1/sx,1)
|
||||
g2.add(self._getText(x=1,y=0,color=shadow))
|
||||
g2.add(self._getText(x=0,y=1,color=strokeColor))
|
||||
g2.scale(sx,1)
|
||||
g2.skew(kx=10, ky=0)
|
||||
g2.shift(0,38)
|
||||
g.add(g2)
|
||||
g.scale(self.width/self._w,self.height/self._h)
|
||||
g.shift(self.x,self.y)
|
||||
return g
|
||||
|
||||
class ReportLabLogo:
|
||||
"""vector reportlab logo centered in a 250x by 150y rectangle"""
|
||||
|
||||
def __init__(self, atx=0, aty=0, width=2.5*inch, height=1.5*inch, powered_by=0):
|
||||
self.origin = (atx, aty)
|
||||
self.dimensions = (width, height)
|
||||
self.powered_by = powered_by
|
||||
|
||||
def draw(self, canvas):
|
||||
from reportlab.graphics import renderPDF
|
||||
canvas.saveState()
|
||||
(atx,aty) = self.origin
|
||||
(width, height) = self.dimensions
|
||||
logo = RL_CorpLogo()
|
||||
logo.width, logo.height = width, height
|
||||
renderPDF.draw(logo.demo(),canvas,atx,aty,0)
|
||||
canvas.restoreState()
|
||||
|
||||
class RL_BusinessCard(Widget):
|
||||
"""Widget that creates a single business card.
|
||||
Uses RL_CorpLogo for the logo.
|
||||
|
||||
For a black border around your card, set self.border to 1.
|
||||
To change the details on the card, over-ride the following properties:
|
||||
self.name, self.position, self.telephone, self.mobile, self.fax, self.email, self.web
|
||||
The office locations are set in self.rh_blurb_top ("London office" etc), and
|
||||
self.rh_blurb_bottom ("New York office" etc).
|
||||
"""
|
||||
# for items where it 'isString' the string can be an empty one...
|
||||
_attrMap = AttrMap(
|
||||
fillColor = AttrMapValue(isColorOrNone),
|
||||
strokeColor = AttrMapValue(isColorOrNone),
|
||||
altStrokeColor = AttrMapValue(isColorOrNone),
|
||||
x = AttrMapValue(isNumber),
|
||||
y = AttrMapValue(isNumber),
|
||||
height = AttrMapValue(isNumber),
|
||||
width = AttrMapValue(isNumber),
|
||||
borderWidth = AttrMapValue(isNumber),
|
||||
bleed=AttrMapValue(isNumberOrNone),
|
||||
cropMarks=AttrMapValue(isBoolean),
|
||||
border=AttrMapValue(isBoolean),
|
||||
name=AttrMapValue(isString),
|
||||
position=AttrMapValue(isString),
|
||||
telephone=AttrMapValue(isString),
|
||||
mobile=AttrMapValue(isString),
|
||||
fax=AttrMapValue(isString),
|
||||
email=AttrMapValue(isString),
|
||||
web=AttrMapValue(isString),
|
||||
rh_blurb_top=AttrMapValue(isListOfStringsOrNone),
|
||||
rh_blurb_bottom=AttrMapValue(isListOfStringsOrNone)
|
||||
)
|
||||
|
||||
_h = 5.35*cm
|
||||
_w = 8.5*cm
|
||||
_fontName = 'Helvetica-Bold'
|
||||
_strapline = "strategic reporting solutions for e-business"
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.fillColor = ReportLabBlue
|
||||
self.strokeColor = black
|
||||
self.altStrokeColor = white
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.height = self._h
|
||||
self.width = self._w
|
||||
self.borderWidth = self.width/6.15
|
||||
self.bleed=0.2*cm
|
||||
self.cropMarks=1
|
||||
self.border=0
|
||||
#Over-ride these with your own info
|
||||
self.name="Joe Cool"
|
||||
self.position="Freelance Demonstrator"
|
||||
self.telephone="020 8545 7271"
|
||||
self.mobile="-"
|
||||
self.fax="020 8544 1311"
|
||||
self.email="info@reportlab.com"
|
||||
self.web="www.reportlab.com"
|
||||
self.rh_blurb_top = ["London office:",
|
||||
"ReportLab Europe Ltd",
|
||||
"Lombard Business Park",
|
||||
"8 Lombard Road",
|
||||
"Wimbledon",
|
||||
"London SW19 3TZ",
|
||||
"United Kingdom"]
|
||||
self.rh_blurb_bottom = ["New York office:",
|
||||
"ReportLab Inc",
|
||||
"219 Harper Street",
|
||||
"Highland Park",
|
||||
"New Jersey 08904",
|
||||
"USA"]
|
||||
|
||||
def demo(self):
|
||||
D = Drawing(self.width, self.height)
|
||||
D.add(self)
|
||||
return D
|
||||
|
||||
def draw(self):
|
||||
fillColor = self.fillColor
|
||||
strokeColor = self.strokeColor
|
||||
|
||||
g = Group()
|
||||
g.add(Rect(x = 0, y = 0,
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = self.fillColor,
|
||||
width = self.borderWidth,
|
||||
height = self.height))
|
||||
g.add(Rect(x = 0, y = self.height-self.borderWidth,
|
||||
fillColor = self.fillColor,
|
||||
strokeColor = self.fillColor,
|
||||
width = self.width,
|
||||
height = self.borderWidth))
|
||||
|
||||
g2 = Group()
|
||||
rl=RL_CorpLogo()
|
||||
rl.height = 1.25*cm
|
||||
rl.width = 1.9*cm
|
||||
rl.draw()
|
||||
g2.add(rl)
|
||||
g.add(g2)
|
||||
g2.shift(x=(self.width-(rl.width+(self.width/42))),
|
||||
y=(self.height - (rl.height+(self.height/42))))
|
||||
|
||||
g.add(String(x = self.borderWidth/5.0,
|
||||
y = ((self.height - (rl.height+(self.height/42)))+((38/90.5)*rl.height)),
|
||||
fontSize = 6,
|
||||
fillColor = self.altStrokeColor,
|
||||
fontName = "Helvetica-BoldOblique",
|
||||
textAnchor = 'start',
|
||||
text = self._strapline))
|
||||
|
||||
leftText=["Tel:", "Mobile:", "Fax:", "Email:", "Web:"]
|
||||
leftDetails=[self.telephone,self.mobile,self.fax,self.email,self.web]
|
||||
leftText.reverse()
|
||||
leftDetails.reverse()
|
||||
for f in range(len(leftText),0,-1):
|
||||
g.add(String(x = self.borderWidth+(self.borderWidth/5.0),
|
||||
y = (self.borderWidth/5.0)+((f-1)*(5*1.2)),
|
||||
fontSize = 5,
|
||||
fillColor = self.strokeColor,
|
||||
fontName = "Helvetica",
|
||||
textAnchor = 'start',
|
||||
text = leftText[f-1]))
|
||||
g.add(String(x = self.borderWidth+(self.borderWidth/5.0)+self.borderWidth,
|
||||
y = (self.borderWidth/5.0)+((f-1)*(5*1.2)),
|
||||
fontSize = 5,
|
||||
fillColor = self.strokeColor,
|
||||
fontName = "Helvetica",
|
||||
textAnchor = 'start',
|
||||
text = leftDetails[f-1]))
|
||||
|
||||
rightText=self.rh_blurb_bottom
|
||||
rightText.reverse()
|
||||
for f in range(len(rightText),0,-1):
|
||||
g.add(String(x = self.width-((self.borderWidth/5.0)),
|
||||
y = (self.borderWidth/5.0)+((f-1)*(5*1.2)),
|
||||
fontSize = 5,
|
||||
fillColor = self.strokeColor,
|
||||
fontName = "Helvetica",
|
||||
textAnchor = 'end',
|
||||
text = rightText[f-1]))
|
||||
|
||||
ty = (self.height-self.borderWidth-(self.borderWidth/5.0)+2)
|
||||
# g.add(Line(self.borderWidth, ty, self.borderWidth+(self.borderWidth/5.0), ty))
|
||||
# g.add(Line(self.borderWidth+(self.borderWidth/5.0), ty, self.borderWidth+(self.borderWidth/5.0),
|
||||
# ty+(self.borderWidth/5.0)))
|
||||
# g.add(Line(self.borderWidth, ty-10,
|
||||
# self.borderWidth+(self.borderWidth/5.0), ty-10))
|
||||
|
||||
rightText=self.rh_blurb_top
|
||||
for f in range(1,(len(rightText)+1)):
|
||||
g.add(String(x = self.width-(self.borderWidth/5.0),
|
||||
y = ty-((f)*(5*1.2)),
|
||||
fontSize = 5,
|
||||
fillColor = self.strokeColor,
|
||||
fontName = "Helvetica",
|
||||
textAnchor = 'end',
|
||||
text = rightText[f-1]))
|
||||
|
||||
g.add(String(x = self.borderWidth+(self.borderWidth/5.0),
|
||||
y = ty-10,
|
||||
fontSize = 10,
|
||||
fillColor = self.strokeColor,
|
||||
fontName = "Helvetica",
|
||||
textAnchor = 'start',
|
||||
text = self.name))
|
||||
|
||||
ty1 = ty-10*1.2
|
||||
|
||||
g.add(String(x = self.borderWidth+(self.borderWidth/5.0),
|
||||
y = ty1-8,
|
||||
fontSize = 8,
|
||||
fillColor = self.strokeColor,
|
||||
fontName = "Helvetica",
|
||||
textAnchor = 'start',
|
||||
text = self.position))
|
||||
if self.border:
|
||||
g.add(Rect(x = 0, y = 0,
|
||||
fillColor=None,
|
||||
strokeColor = black,
|
||||
width = self.width,
|
||||
height = self.height))
|
||||
g.shift(self.x,self.y)
|
||||
return g
|
||||
|
||||
|
||||
def test():
|
||||
"""This function produces a pdf with examples. """
|
||||
|
||||
#wbite on blue
|
||||
rl = RL_CorpLogo()
|
||||
rl.width = 129
|
||||
rl.height = 86
|
||||
D = Drawing(rl.width,rl.height)
|
||||
D.add(rl)
|
||||
D.__dict__['verbose'] = 1
|
||||
D.save(fnRoot='corplogo_whiteonblue',formats=['pdf','eps','jpg','gif'])
|
||||
|
||||
|
||||
#blue on white
|
||||
rl = RL_CorpLogoReversed()
|
||||
rl.width = 129
|
||||
rl.height = 86
|
||||
D = Drawing(rl.width,rl.height)
|
||||
D.add(rl)
|
||||
D.__dict__['verbose'] = 1
|
||||
D.save(fnRoot='corplogo_blueonwhite',formats=['pdf','eps','jpg','gif'])
|
||||
|
||||
|
||||
|
||||
rl = RL_BusinessCard()
|
||||
rl.x=25
|
||||
rl.y=25
|
||||
rl.border=1
|
||||
D = Drawing(rl.width+50,rl.height+50)
|
||||
D.add(rl)
|
||||
D.__dict__['verbose'] = 1
|
||||
D.save(fnRoot='RL_BusinessCard',formats=['pdf'])
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
|
@ -1,11 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/enums.py
|
||||
__version__=''' $Id$ '''
|
||||
__doc__="""
|
||||
holder for all reportlab's enumerated types
|
||||
"""
|
||||
TA_LEFT = 0
|
||||
TA_CENTER = 1
|
||||
TA_RIGHT = 2
|
||||
TA_JUSTIFY = 4
|
|
@ -1,81 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/extformat.py
|
||||
from tokenize import tokenprog
|
||||
import sys
|
||||
|
||||
def _matchorfail(text, pos):
|
||||
match = tokenprog.match(text, pos)
|
||||
if match is None: raise ValueError(text, pos)
|
||||
return match, match.end()
|
||||
|
||||
'''
|
||||
Extended dictionary formatting
|
||||
We allow expressions in the parentheses instead of
|
||||
just a simple variable.
|
||||
'''
|
||||
def dictformat(_format, L={}, G={}):
|
||||
format = _format
|
||||
|
||||
S = {}
|
||||
chunks = []
|
||||
pos = 0
|
||||
n = 0
|
||||
|
||||
while 1:
|
||||
pc = format.find("%", pos)
|
||||
if pc < 0: break
|
||||
nextchar = format[pc+1]
|
||||
|
||||
if nextchar == "(":
|
||||
chunks.append(format[pos:pc])
|
||||
pos, level = pc+2, 1
|
||||
while level:
|
||||
match, pos = _matchorfail(format, pos)
|
||||
tstart, tend = match.regs[3]
|
||||
token = format[tstart:tend]
|
||||
if token == "(": level = level+1
|
||||
elif token == ")": level = level-1
|
||||
vname = '__superformat_%d' % n
|
||||
n += 1
|
||||
S[vname] = eval(format[pc+2:pos-1],L,G)
|
||||
chunks.append('%%(%s)' % vname)
|
||||
else:
|
||||
nc = pc+1+(nextchar=="%")
|
||||
chunks.append(format[pos:nc])
|
||||
pos = nc
|
||||
|
||||
if pos < len(format): chunks.append(format[pos:])
|
||||
return (''.join(chunks)) % S
|
||||
|
||||
def magicformat(format):
|
||||
"""Evaluate and substitute the appropriate parts of the string."""
|
||||
try: 1/0
|
||||
except: frame = sys.exc_traceback.tb_frame
|
||||
while frame.f_globals["__name__"] == __name__: frame = frame.f_back
|
||||
return dictformat(format,frame.f_locals, frame.f_globals)
|
||||
|
||||
if __name__=='__main__':
|
||||
from reportlab.lib.formatters import DecimalFormatter
|
||||
_DF={}
|
||||
def df(n,dp=2,ds='.',ts=','):
|
||||
try:
|
||||
_df = _DF[dp,ds]
|
||||
except KeyError:
|
||||
_df = _DF[dp,ds] = DecimalFormatter(places=dp,decimalSep=ds,thousandSep=ts)
|
||||
return _df(n)
|
||||
|
||||
from reportlab.lib.extformat import magicformat
|
||||
|
||||
Z={'abc': ('ab','c')}
|
||||
x = 300000.23
|
||||
percent=79.2
|
||||
class dingo:
|
||||
a=3
|
||||
print magicformat('''
|
||||
$%%(df(x,dp=3))s --> $%(df(x,dp=3))s
|
||||
$%%(df(x,dp=2,ds=',',ts='.'))s --> $%(df(x,dp=2,ds=',',ts='.'))s
|
||||
%%(percent).2f%%%% --> %(percent).2f%%
|
||||
%%(dingo.a)s --> %(dingo.a)s
|
||||
%%(Z['abc'][0])s --> %(Z['abc'][0])s
|
||||
''')
|
|
@ -1,89 +0,0 @@
|
|||
#! /usr/bin/python2.3
|
||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/fonts.py
|
||||
__version__=''' $Id$ '''
|
||||
import string, sys, os
|
||||
###############################################################################
|
||||
# A place to put useful font stuff
|
||||
###############################################################################
|
||||
#
|
||||
# Font Mappings
|
||||
# The brute force approach to finding the correct postscript font name;
|
||||
# much safer than the rule-based ones we tried.
|
||||
# preprocessor to reduce font face names to the shortest list
|
||||
# possible. Add any aliases you wish; it keeps looking up
|
||||
# until it finds no more translations to do. Any input
|
||||
# will be lowercased before checking.
|
||||
_family_alias = {
|
||||
'serif':'times',
|
||||
'sansserif':'helvetica',
|
||||
'monospaced':'courier',
|
||||
'arial':'helvetica'
|
||||
}
|
||||
#maps a piddle font to a postscript one.
|
||||
_tt2ps_map = {
|
||||
#face, bold, italic -> ps name
|
||||
('times', 0, 0) :'Times-Roman',
|
||||
('times', 1, 0) :'Times-Bold',
|
||||
('times', 0, 1) :'Times-Italic',
|
||||
('times', 1, 1) :'Times-BoldItalic',
|
||||
|
||||
('courier', 0, 0) :'Courier',
|
||||
('courier', 1, 0) :'Courier-Bold',
|
||||
('courier', 0, 1) :'Courier-Oblique',
|
||||
('courier', 1, 1) :'Courier-BoldOblique',
|
||||
|
||||
('helvetica', 0, 0) :'Helvetica',
|
||||
('helvetica', 1, 0) :'Helvetica-Bold',
|
||||
('helvetica', 0, 1) :'Helvetica-Oblique',
|
||||
('helvetica', 1, 1) :'Helvetica-BoldOblique',
|
||||
|
||||
|
||||
# there is only one Symbol font
|
||||
('symbol', 0, 0) :'Symbol',
|
||||
('symbol', 1, 0) :'Symbol',
|
||||
('symbol', 0, 1) :'Symbol',
|
||||
('symbol', 1, 1) :'Symbol',
|
||||
|
||||
# ditto for dingbats
|
||||
('zapfdingbats', 0, 0) :'ZapfDingbats',
|
||||
('zapfdingbats', 1, 0) :'ZapfDingbats',
|
||||
('zapfdingbats', 0, 1) :'ZapfDingbats',
|
||||
('zapfdingbats', 1, 1) :'ZapfDingbats',
|
||||
|
||||
|
||||
}
|
||||
|
||||
_ps2tt_map={}
|
||||
for k,v in _tt2ps_map.items():
|
||||
if not _ps2tt_map.has_key(k):
|
||||
_ps2tt_map[string.lower(v)] = k
|
||||
|
||||
def ps2tt(psfn):
|
||||
'ps fontname to family name, bold, italic'
|
||||
psfn = string.lower(psfn)
|
||||
if _ps2tt_map.has_key(psfn):
|
||||
return _ps2tt_map[psfn]
|
||||
raise ValueError, "Can't map determine family/bold/italic for %s" % psfn
|
||||
|
||||
def tt2ps(fn,b,i):
|
||||
'family name + bold & italic to ps font name'
|
||||
K = (string.lower(fn),b,i)
|
||||
if _tt2ps_map.has_key(K):
|
||||
return _tt2ps_map[K]
|
||||
else:
|
||||
fn, b1, i1 = ps2tt(K[0])
|
||||
K = fn, b1|b, i1|i
|
||||
if _tt2ps_map.has_key(K):
|
||||
return _tt2ps_map[K]
|
||||
raise ValueError, "Can't find concrete font for family=%s, bold=%d, italic=%d" % (fn, b, i)
|
||||
|
||||
def addMapping(face, bold, italic, psname):
|
||||
'allow a custom font to be put in the mapping'
|
||||
k = (string.lower(face), bold, italic)
|
||||
_tt2ps_map[k] = psname
|
||||
# rebuild inverse - inefficient
|
||||
for k,v in _tt2ps_map.items():
|
||||
if not _ps2tt_map.has_key(k):
|
||||
_ps2tt_map[string.lower(v)] = k
|
|
@ -1,100 +0,0 @@
|
|||
#! /usr/bin/python2.3
|
||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/formatters.py
|
||||
__version__=''' $Id$ '''
|
||||
__doc__="""
|
||||
These help format numbers and dates in a user friendly way.
|
||||
|
||||
Used by the graphics framework.
|
||||
"""
|
||||
import string, sys, os
|
||||
|
||||
class Formatter:
|
||||
"Base formatter - simply applies python format strings"
|
||||
def __init__(self, pattern):
|
||||
self.pattern = pattern
|
||||
def format(self, obj):
|
||||
return self.pattern % obj
|
||||
def __repr__(self):
|
||||
return "%s('%s')" % (self.__class__.__name__, self.pattern)
|
||||
def __call__(self, x):
|
||||
return self.format(x)
|
||||
|
||||
|
||||
class DecimalFormatter(Formatter):
|
||||
"""lets you specify how to build a decimal.
|
||||
|
||||
A future NumberFormatter class will take Microsoft-style patterns
|
||||
instead - "$#,##0.00" is WAY easier than this."""
|
||||
def __init__(self, places=2, decimalSep='.', thousandSep=None, prefix=None, suffix=None):
|
||||
self.places = places
|
||||
self.dot = decimalSep
|
||||
self.comma = thousandSep
|
||||
self.prefix = prefix
|
||||
self.suffix = suffix
|
||||
|
||||
def format(self, num):
|
||||
# positivize the numbers
|
||||
sign=num<0
|
||||
if sign:
|
||||
num = -num
|
||||
places, sep = self.places, self.dot
|
||||
strip = places<=0
|
||||
if places and strip: places = -places
|
||||
strInt = ('%.' + str(places) + 'f') % num
|
||||
if places:
|
||||
strInt, strFrac = strInt.split('.')
|
||||
strFrac = sep + strFrac
|
||||
if strip:
|
||||
while strFrac and strFrac[-1] in ['0',sep]: strFrac = strFrac[:-1]
|
||||
else:
|
||||
strFrac = ''
|
||||
|
||||
if self.comma is not None:
|
||||
strNew = ''
|
||||
while strInt:
|
||||
left, right = strInt[0:-3], strInt[-3:]
|
||||
if left == '':
|
||||
#strNew = self.comma + right + strNew
|
||||
strNew = right + strNew
|
||||
else:
|
||||
strNew = self.comma + right + strNew
|
||||
strInt = left
|
||||
strInt = strNew
|
||||
|
||||
strBody = strInt + strFrac
|
||||
if sign: strBody = '-' + strBody
|
||||
if self.prefix:
|
||||
strBody = self.prefix + strBody
|
||||
if self.suffix:
|
||||
strBody = strBody + self.suffix
|
||||
return strBody
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(places=%d, decimalSep=%s, thousandSep=%s, prefix=%s, suffix=%s)" % (
|
||||
self.__class__.__name__,
|
||||
self.places,
|
||||
repr(self.dot),
|
||||
repr(self.comma),
|
||||
repr(self.prefix),
|
||||
repr(self.suffix)
|
||||
)
|
||||
|
||||
if __name__=='__main__':
|
||||
def t(n, s, places=2, decimalSep='.', thousandSep=None, prefix=None, suffix=None):
|
||||
f=DecimalFormatter(places,decimalSep,thousandSep,prefix,suffix)
|
||||
r = f(n)
|
||||
print "places=%2d dot=%-4s comma=%-4s prefix=%-4s suffix=%-4s result=%10s %s" %(f.places, f.dot, f.comma, f.prefix, f.suffix,r, r==s and 'OK' or 'BAD')
|
||||
t(1000.9,'1,000.9',1,thousandSep=',')
|
||||
t(1000.95,'1,001.0',1,thousandSep=',')
|
||||
t(1000.95,'1,001',-1,thousandSep=',')
|
||||
t(1000.9,'1,001',0,thousandSep=',')
|
||||
t(1000.9,'1000.9',1)
|
||||
t(1000.95,'1001.0',1)
|
||||
t(1000.95,'1001',-1)
|
||||
t(1000.9,'1001',0)
|
||||
t(1000.1,'1000.1',1)
|
||||
t(1000.55,'1000.6',1)
|
||||
t(1000.449,'1000.4',-1)
|
||||
t(1000.45,'1000',0)
|
|
@ -1,61 +0,0 @@
|
|||
#! /usr/bin/python2.3
|
||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/logger.py
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
from sys import stderr
|
||||
class Logger:
|
||||
'''
|
||||
An extended file type thing initially equivalent to sys.stderr
|
||||
You can add/remove file type things; it has a write method
|
||||
'''
|
||||
def __init__(self):
|
||||
self._fps = [stderr]
|
||||
self._fns = {}
|
||||
|
||||
def add(self,fp):
|
||||
'''add the file/string fp to the destinations'''
|
||||
if type(fp) is StringType:
|
||||
if fp in self._fns: return
|
||||
fp = open(fn,'wb')
|
||||
self._fns[fn] = fp
|
||||
self._fps.append(fp)
|
||||
|
||||
def remove(self,fp):
|
||||
'''remove the file/string fp from the destinations'''
|
||||
if type(fp) is StringType:
|
||||
if fp not in self._fns: return
|
||||
fn = fp
|
||||
fp = self._fns[fn]
|
||||
del self.fns[fn]
|
||||
if fp in self._fps:
|
||||
del self._fps[self._fps.index(fp)]
|
||||
|
||||
def write(self,text):
|
||||
'''write text to all the destinations'''
|
||||
if text[-1]!='\n': text=text+'\n'
|
||||
map(lambda fp,t=text: fp.write(t),self._fps)
|
||||
|
||||
def __call__(self,text):
|
||||
self.write(text)
|
||||
|
||||
logger=Logger()
|
||||
|
||||
class WarnOnce:
|
||||
|
||||
def __init__(self,kind='Warn'):
|
||||
self.uttered = {}
|
||||
self.pfx = '%s: '%kind
|
||||
self.enabled = 1
|
||||
|
||||
def once(self,warning):
|
||||
if not self.uttered.has_key(warning):
|
||||
if self.enabled: logger.write(self.pfx + warning)
|
||||
self.uttered[warning] = 1
|
||||
|
||||
def __call__(self,warning):
|
||||
self.once(warning)
|
||||
|
||||
warnOnce=WarnOnce()
|
||||
infoOnce=WarnOnce('Info')
|
|
@ -1,603 +0,0 @@
|
|||
#! /usr/bin/python2.3
|
||||
# normalDate.py - version 1.0 - 20000717
|
||||
#hacked by Robin Becker 10/Apr/2001
|
||||
#major changes include
|
||||
# using Types instead of type(0) etc
|
||||
# BusinessDate class
|
||||
# __radd__, __rsub__ methods
|
||||
# formatMS stuff
|
||||
|
||||
# derived from an original version created
|
||||
# by Jeff Bauer of Rubicon Research and used
|
||||
# with his kind permission
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
|
||||
|
||||
_bigBangScalar = -4345732 # based on (-9999, 1, 1) BC/BCE minimum
|
||||
_bigCrunchScalar = 2958463 # based on (9999,12,31) AD/CE maximum
|
||||
_daysInMonthNormal = [31,28,31,30,31,30,31,31,30,31,30,31]
|
||||
_daysInMonthLeapYear = [31,29,31,30,31,30,31,31,30,31,30,31]
|
||||
_dayOfWeekName = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
|
||||
'Friday', 'Saturday', 'Sunday']
|
||||
_monthName = ['January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July','August','September','October','November','December']
|
||||
|
||||
from types import IntType, StringType, ListType, TupleType
|
||||
import string, re, time
|
||||
if hasattr(time,'struct_time'):
|
||||
_DateSeqTypes = (ListType,TupleType,time.struct_time)
|
||||
else:
|
||||
_DateSeqTypes = (ListType,TupleType)
|
||||
|
||||
_fmtPat = re.compile('\\{(m{1,5}|yyyy|yy|d{1,4})\\}',re.MULTILINE|re.IGNORECASE)
|
||||
_iso_re = re.compile(r'(\d\d\d\d|\d\d)-(\d\d)-(\d\d)')
|
||||
|
||||
def getStdMonthNames():
|
||||
return map(string.lower,_monthName)
|
||||
|
||||
def getStdShortMonthNames():
|
||||
return map(lambda x: x[:3],getStdMonthNames())
|
||||
|
||||
def getStdDayNames():
|
||||
return map(string.lower,_dayOfWeekName)
|
||||
|
||||
def getStdShortDayNames():
|
||||
return map(lambda x: x[:3],getStdDayNames())
|
||||
|
||||
def isLeapYear(year):
|
||||
"""determine if specified year is leap year, returns Python boolean"""
|
||||
if year < 1600:
|
||||
if year % 4:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
elif year % 4 != 0:
|
||||
return 0
|
||||
elif year % 100 != 0:
|
||||
return 1
|
||||
elif year % 400 != 0:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
class NormalDateException(Exception):
|
||||
"""Exception class for NormalDate"""
|
||||
pass
|
||||
|
||||
class NormalDate:
|
||||
"""
|
||||
NormalDate is a specialized class to handle dates without
|
||||
all the excess baggage (time zones, daylight savings, leap
|
||||
seconds, etc.) of other date structures. The minimalist
|
||||
strategy greatly simplifies its implementation and use.
|
||||
|
||||
Internally, NormalDate is stored as an integer with values
|
||||
in a discontinuous range of -99990101 to 99991231. The
|
||||
integer value is used principally for storage and to simplify
|
||||
the user interface. Internal calculations are performed by
|
||||
a scalar based on Jan 1, 1900.
|
||||
|
||||
Valid NormalDate ranges include (-9999,1,1) B.C.E. through
|
||||
(9999,12,31) C.E./A.D.
|
||||
|
||||
1.0 - No changes, except the version number. After 3 years of use
|
||||
by various parties I think we can consider it stable.
|
||||
0.8 - added Prof. Stephen Walton's suggestion for a range method
|
||||
- module author resisted the temptation to use lambda <0.5 wink>
|
||||
0.7 - added Dan Winkler's suggestions for __add__, __sub__ methods
|
||||
0.6 - modifications suggested by Kevin Digweed to fix:
|
||||
- dayOfWeek, dayOfWeekAbbrev, clone methods
|
||||
- permit NormalDate to be a better behaved superclass
|
||||
0.5 - minor tweaking
|
||||
0.4 - added methods __cmp__, __hash__
|
||||
- added Epoch variable, scoped to the module
|
||||
- added setDay, setMonth, setYear methods
|
||||
0.3 - minor touch-ups
|
||||
0.2 - fixed bug for certain B.C.E leap years
|
||||
- added Jim Fulton's suggestions for short alias class name =ND
|
||||
and __getstate__, __setstate__ methods
|
||||
|
||||
Special thanks: Roedy Green
|
||||
"""
|
||||
def __init__(self, normalDate=None):
|
||||
"""
|
||||
Accept 1 of 4 values to initialize a NormalDate:
|
||||
1. None - creates a NormalDate for the current day
|
||||
2. integer in yyyymmdd format
|
||||
3. string in yyyymmdd format
|
||||
4. tuple in (yyyy, mm, dd) - localtime/gmtime can also be used
|
||||
"""
|
||||
if normalDate is None:
|
||||
self.setNormalDate(time.localtime(time.time()))
|
||||
else:
|
||||
self.setNormalDate(normalDate)
|
||||
|
||||
def add(self, days):
|
||||
"""add days to date; use negative integers to subtract"""
|
||||
if not type(days) is IntType:
|
||||
raise NormalDateException( \
|
||||
'add method parameter must be integer type')
|
||||
self.normalize(self.scalar() + days)
|
||||
|
||||
def __add__(self, days):
|
||||
"""add integer to normalDate and return a new, calculated value"""
|
||||
if not type(days) is IntType:
|
||||
raise NormalDateException( \
|
||||
'__add__ parameter must be integer type')
|
||||
cloned = self.clone()
|
||||
cloned.add(days)
|
||||
return cloned
|
||||
|
||||
def __radd__(self,days):
|
||||
'''for completeness'''
|
||||
return self.__add__(days)
|
||||
|
||||
def clone(self):
|
||||
"""return a cloned instance of this normalDate"""
|
||||
return self.__class__(self.normalDate)
|
||||
|
||||
def __cmp__(self, target):
|
||||
if target is None:
|
||||
return 1
|
||||
elif not hasattr(target, 'normalDate'):
|
||||
return 1
|
||||
else:
|
||||
return cmp(self.normalDate, target.normalDate)
|
||||
|
||||
def day(self):
|
||||
"""return the day as integer 1-31"""
|
||||
return int(repr(self.normalDate)[-2:])
|
||||
|
||||
def dayOfWeek(self):
|
||||
"""return integer representing day of week, Mon=0, Tue=1, etc."""
|
||||
return apply(dayOfWeek, self.toTuple())
|
||||
|
||||
def dayOfWeekAbbrev(self):
|
||||
"""return day of week abbreviation for current date: Mon, Tue, etc."""
|
||||
return _dayOfWeekName[self.dayOfWeek()][:3]
|
||||
|
||||
def dayOfWeekName(self):
|
||||
"""return day of week name for current date: Monday, Tuesday, etc."""
|
||||
return _dayOfWeekName[self.dayOfWeek()]
|
||||
|
||||
def dayOfYear(self):
|
||||
"""day of year"""
|
||||
if self.isLeapYear():
|
||||
daysByMonth = _daysInMonthLeapYear
|
||||
else:
|
||||
daysByMonth = _daysInMonthNormal
|
||||
priorMonthDays = 0
|
||||
for m in xrange(self.month() - 1):
|
||||
priorMonthDays = priorMonthDays + daysByMonth[m]
|
||||
return self.day() + priorMonthDays
|
||||
|
||||
def daysBetweenDates(self, normalDate):
|
||||
"""
|
||||
return value may be negative, since calculation is
|
||||
self.scalar() - arg
|
||||
"""
|
||||
if type(normalDate) is _NDType:
|
||||
return self.scalar() - normalDate.scalar()
|
||||
else:
|
||||
return self.scalar() - NormalDate(normalDate).scalar()
|
||||
|
||||
def equals(self, target):
|
||||
if type(target) is _NDType:
|
||||
if target is None:
|
||||
return self.normalDate is None
|
||||
else:
|
||||
return self.normalDate == target.normalDate
|
||||
else:
|
||||
return 0
|
||||
|
||||
def endOfMonth(self):
|
||||
"""returns (cloned) last day of month"""
|
||||
return self.__class__(self.__repr__()[-8:-2]+str(self.lastDayOfMonth()))
|
||||
|
||||
def firstDayOfMonth(self):
|
||||
"""returns (cloned) first day of month"""
|
||||
return self.__class__(self.__repr__()[-8:-2]+"01")
|
||||
|
||||
def formatUS(self):
|
||||
"""return date as string in common US format: MM/DD/YY"""
|
||||
d = self.__repr__()
|
||||
return "%s/%s/%s" % (d[-4:-2], d[-2:], d[-6:-4])
|
||||
|
||||
def formatUSCentury(self):
|
||||
"""return date as string in 4-digit year US format: MM/DD/YYYY"""
|
||||
d = self.__repr__()
|
||||
return "%s/%s/%s" % (d[-4:-2], d[-2:], d[-8:-4])
|
||||
|
||||
def _fmtM(self):
|
||||
return str(self.month())
|
||||
|
||||
def _fmtMM(self):
|
||||
return '%02d' % self.month()
|
||||
|
||||
def _fmtMMM(self):
|
||||
return self.monthAbbrev()
|
||||
|
||||
def _fmtMMMM(self):
|
||||
return self.monthName()
|
||||
|
||||
def _fmtMMMMM(self):
|
||||
return self.monthName()[0]
|
||||
|
||||
def _fmtD(self):
|
||||
return str(self.day())
|
||||
|
||||
def _fmtDD(self):
|
||||
return '%02d' % self.day()
|
||||
|
||||
def _fmtDDD(self):
|
||||
return self.dayOfWeekAbbrev()
|
||||
|
||||
def _fmtDDDD(self):
|
||||
return self.dayOfWeekName()
|
||||
|
||||
def _fmtYY(self):
|
||||
return '%02d' % (self.year()%100)
|
||||
|
||||
def _fmtYYYY(self):
|
||||
return str(self.year())
|
||||
|
||||
def formatMS(self,fmt):
|
||||
'''format like MS date using the notation
|
||||
{YY} --> 2 digit year
|
||||
{YYYY} --> 4 digit year
|
||||
{M} --> month as digit
|
||||
{MM} --> 2 digit month
|
||||
{MMM} --> abbreviated month name
|
||||
{MMMM} --> monthname
|
||||
{MMMMM} --> first character of monthname
|
||||
{D} --> day of month as digit
|
||||
{DD} --> 2 digit day of month
|
||||
{DDD} --> abrreviated weekday name
|
||||
{DDDD} --> weekday name
|
||||
'''
|
||||
r = fmt[:]
|
||||
f = 0
|
||||
while 1:
|
||||
m = _fmtPat.search(r,f)
|
||||
if m:
|
||||
y = getattr(self,'_fmt'+string.upper(m.group()[1:-1]))()
|
||||
i, j = m.span()
|
||||
r = (r[0:i] + y) + r[j:]
|
||||
f = i + len(y)
|
||||
else:
|
||||
return r
|
||||
|
||||
def __getstate__(self):
|
||||
"""minimize persistent storage requirements"""
|
||||
return self.normalDate
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.normalDate)
|
||||
|
||||
def __int__(self):
|
||||
return self.normalDate
|
||||
|
||||
def isLeapYear(self):
|
||||
"""
|
||||
determine if specified year is leap year, returning true (1) or
|
||||
false (0)
|
||||
"""
|
||||
return isLeapYear(self.year())
|
||||
|
||||
def _isValidNormalDate(self, normalDate):
|
||||
"""checks for date validity in [-]yyyymmdd format"""
|
||||
if type(normalDate) is not IntType:
|
||||
return 0
|
||||
if len(repr(normalDate)) > 9:
|
||||
return 0
|
||||
if normalDate < 0:
|
||||
dateStr = "%09d" % normalDate
|
||||
else:
|
||||
dateStr = "%08d" % normalDate
|
||||
if len(dateStr) < 8:
|
||||
return 0
|
||||
elif len(dateStr) == 9:
|
||||
if (dateStr[0] != '-' and dateStr[0] != '+'):
|
||||
return 0
|
||||
year = int(dateStr[:-4])
|
||||
if year < -9999 or year > 9999 or year == 0:
|
||||
return 0 # note: zero (0) is not a valid year
|
||||
month = int(dateStr[-4:-2])
|
||||
if month < 1 or month > 12:
|
||||
return 0
|
||||
if isLeapYear(year):
|
||||
maxDay = _daysInMonthLeapYear[month - 1]
|
||||
else:
|
||||
maxDay = _daysInMonthNormal[month - 1]
|
||||
day = int(dateStr[-2:])
|
||||
if day < 1 or day > maxDay:
|
||||
return 0
|
||||
if year == 1582 and month == 10 and day > 4 and day < 15:
|
||||
return 0 # special case of 10 days dropped: Oct 5-14, 1582
|
||||
return 1
|
||||
|
||||
def lastDayOfMonth(self):
|
||||
"""returns last day of the month as integer 28-31"""
|
||||
if self.isLeapYear():
|
||||
return _daysInMonthLeapYear[self.month() - 1]
|
||||
else:
|
||||
return _daysInMonthNormal[self.month() - 1]
|
||||
|
||||
def localeFormat(self):
|
||||
"""override this method to use your preferred locale format"""
|
||||
return self.formatUS()
|
||||
|
||||
def month(self):
|
||||
"""returns month as integer 1-12"""
|
||||
return int(repr(self.normalDate)[-4:-2])
|
||||
|
||||
def monthAbbrev(self):
|
||||
"""returns month as a 3-character abbreviation, i.e. Jan, Feb, etc."""
|
||||
return _monthName[self.month() - 1][:3]
|
||||
|
||||
def monthName(self):
|
||||
"""returns month name, i.e. January, February, etc."""
|
||||
return _monthName[self.month() - 1]
|
||||
|
||||
def normalize(self, scalar):
|
||||
"""convert scalar to normalDate"""
|
||||
if scalar < _bigBangScalar:
|
||||
msg = "normalize(%d): scalar below minimum" % \
|
||||
_bigBangScalar
|
||||
raise NormalDateException(msg)
|
||||
if scalar > _bigCrunchScalar:
|
||||
msg = "normalize(%d): scalar exceeds maximum" % \
|
||||
_bigCrunchScalar
|
||||
raise NormalDateException(msg)
|
||||
from math import floor
|
||||
if scalar >= -115860:
|
||||
year = 1600 + int(floor((scalar + 109573) / 365.2425))
|
||||
elif scalar >= -693597:
|
||||
year = 4 + int(floor((scalar + 692502) / 365.2425))
|
||||
else:
|
||||
year = -4 + int(floor((scalar + 695058) / 365.2425))
|
||||
days = scalar - firstDayOfYear(year) + 1
|
||||
if days <= 0:
|
||||
year = year - 1
|
||||
days = scalar - firstDayOfYear(year) + 1
|
||||
daysInYear = 365
|
||||
if isLeapYear(year):
|
||||
daysInYear = daysInYear + 1
|
||||
if days > daysInYear:
|
||||
year = year + 1
|
||||
days = scalar - firstDayOfYear(year) + 1
|
||||
# add 10 days if between Oct 15, 1582 and Dec 31, 1582
|
||||
if (scalar >= -115860 and scalar <= -115783):
|
||||
days = days + 10
|
||||
if isLeapYear(year):
|
||||
daysByMonth = _daysInMonthLeapYear
|
||||
else:
|
||||
daysByMonth = _daysInMonthNormal
|
||||
dc = 0; month = 12
|
||||
for m in xrange(len(daysByMonth)):
|
||||
dc = dc + daysByMonth[m]
|
||||
if dc >= days:
|
||||
month = m + 1
|
||||
break
|
||||
# add up the days in prior months
|
||||
priorMonthDays = 0
|
||||
for m in xrange(month - 1):
|
||||
priorMonthDays = priorMonthDays + daysByMonth[m]
|
||||
day = days - priorMonthDays
|
||||
self.setNormalDate((year, month, day))
|
||||
|
||||
def range(self, days):
|
||||
"""Return a range of normalDates as a list. Parameter
|
||||
may be an int or normalDate."""
|
||||
if type(days) is not IntType:
|
||||
days = days - self # if not int, assume arg is normalDate type
|
||||
r = []
|
||||
for i in range(days):
|
||||
r.append(self + i)
|
||||
return r
|
||||
|
||||
def __repr__(self):
|
||||
"""print format: [-]yyyymmdd"""
|
||||
# Note: When disassembling a NormalDate string, be sure to
|
||||
# count from the right, i.e. epochMonth = int(`Epoch`[-4:-2]),
|
||||
# or the slice won't work for dates B.C.
|
||||
if self.normalDate < 0:
|
||||
return "%09d" % self.normalDate
|
||||
else:
|
||||
return "%08d" % self.normalDate
|
||||
|
||||
def scalar(self):
|
||||
"""days since baseline date: Jan 1, 1900"""
|
||||
(year, month, day) = self.toTuple()
|
||||
days = firstDayOfYear(year) + day - 1
|
||||
if self.isLeapYear():
|
||||
for m in xrange(month - 1):
|
||||
days = days + _daysInMonthLeapYear[m]
|
||||
else:
|
||||
for m in xrange(month - 1):
|
||||
days = days + _daysInMonthNormal[m]
|
||||
if year == 1582:
|
||||
if month > 10 or (month == 10 and day > 4):
|
||||
days = days - 10
|
||||
return days
|
||||
|
||||
def setDay(self, day):
|
||||
"""set the day of the month"""
|
||||
maxDay = self.lastDayOfMonth()
|
||||
if day < 1 or day > maxDay:
|
||||
msg = "day is outside of range 1 to %d" % maxDay
|
||||
raise NormalDateException(msg)
|
||||
(y, m, d) = self.toTuple()
|
||||
self.setNormalDate((y, m, day))
|
||||
|
||||
def setMonth(self, month):
|
||||
"""set the month [1-12]"""
|
||||
if month < 1 or month > 12:
|
||||
raise NormalDateException('month is outside range 1 to 12')
|
||||
(y, m, d) = self.toTuple()
|
||||
self.setNormalDate((y, month, d))
|
||||
|
||||
def setNormalDate(self, normalDate):
|
||||
"""
|
||||
accepts date as scalar string/integer (yyyymmdd) or tuple
|
||||
(year, month, day, ...)"""
|
||||
tn=type(normalDate)
|
||||
if tn is IntType:
|
||||
self.normalDate = normalDate
|
||||
elif tn is StringType:
|
||||
try:
|
||||
self.normalDate = int(normalDate)
|
||||
except:
|
||||
m = _iso_re.match(normalDate)
|
||||
if m:
|
||||
self.setNormalDate(m.group(1)+m.group(2)+m.group(3))
|
||||
else:
|
||||
raise NormalDateException("unable to setNormalDate(%s)" % `normalDate`)
|
||||
elif tn in _DateSeqTypes:
|
||||
self.normalDate = int("%04d%02d%02d" % normalDate[:3])
|
||||
elif tn is _NDType:
|
||||
self.normalDate = normalDate.normalDate
|
||||
if not self._isValidNormalDate(self.normalDate):
|
||||
raise NormalDateException("unable to setNormalDate(%s)" % `normalDate`)
|
||||
|
||||
def setYear(self, year):
|
||||
if year == 0:
|
||||
raise NormalDateException('cannot set year to zero')
|
||||
elif year < -9999:
|
||||
raise NormalDateException('year cannot be less than -9999')
|
||||
elif year > 9999:
|
||||
raise NormalDateException('year cannot be greater than 9999')
|
||||
(y, m, d) = self.toTuple()
|
||||
self.setNormalDate((year, m, d))
|
||||
|
||||
__setstate__ = setNormalDate
|
||||
|
||||
def __sub__(self, v):
|
||||
if type(v) is IntType:
|
||||
return self.__add__(-v)
|
||||
return self.scalar() - v.scalar()
|
||||
|
||||
def __rsub__(self,v):
|
||||
if type(v) is IntType:
|
||||
return NormalDate(v) - self
|
||||
else:
|
||||
return v.scalar() - self.scalar()
|
||||
|
||||
def toTuple(self):
|
||||
"""return date as (year, month, day) tuple"""
|
||||
return (self.year(), self.month(), self.day())
|
||||
|
||||
def year(self):
|
||||
"""return year in yyyy format, negative values indicate B.C."""
|
||||
return int(repr(self.normalDate)[:-4])
|
||||
|
||||
################# Utility functions #################
|
||||
|
||||
def bigBang():
|
||||
"""return lower boundary as a NormalDate"""
|
||||
return NormalDate((-9999, 1, 1))
|
||||
|
||||
def bigCrunch():
|
||||
"""return upper boundary as a NormalDate"""
|
||||
return NormalDate((9999, 12, 31))
|
||||
|
||||
def dayOfWeek(y, m, d):
|
||||
"""return integer representing day of week, Mon=0, Tue=1, etc."""
|
||||
if m == 1 or m == 2:
|
||||
m = m + 12
|
||||
y = y - 1
|
||||
return (d + 2*m + 3*(m+1)/5 + y + y/4 - y/100 + y/400) % 7
|
||||
|
||||
def firstDayOfYear(year):
|
||||
"""number of days to the first of the year, relative to Jan 1, 1900"""
|
||||
if type(year) is not IntType:
|
||||
msg = "firstDayOfYear() expected integer, got %s" % type(year)
|
||||
raise NormalDateException(msg)
|
||||
if year == 0:
|
||||
raise NormalDateException('first day of year cannot be zero (0)')
|
||||
elif year < 0: # BCE calculation
|
||||
firstDay = (year * 365) + int((year - 1) / 4) - 693596
|
||||
else: # CE calculation
|
||||
leapAdjust = int((year + 3) / 4)
|
||||
if year > 1600:
|
||||
leapAdjust = leapAdjust - int((year + 99 - 1600) / 100) + \
|
||||
int((year + 399 - 1600) / 400)
|
||||
firstDay = year * 365 + leapAdjust - 693963
|
||||
if year > 1582:
|
||||
firstDay = firstDay - 10
|
||||
return firstDay
|
||||
|
||||
def FND(d):
|
||||
'''convert to ND if required'''
|
||||
return (type(d) is _NDType) and d or ND(d)
|
||||
|
||||
Epoch=bigBang()
|
||||
ND=NormalDate
|
||||
_NDType = type(Epoch)
|
||||
BDEpoch=ND(15821018)
|
||||
BDEpochScalar = -115857
|
||||
|
||||
class BusinessDate(NormalDate):
|
||||
"""
|
||||
Specialised NormalDate
|
||||
"""
|
||||
def add(self, days):
|
||||
"""add days to date; use negative integers to subtract"""
|
||||
if not type(days) is IntType:
|
||||
raise NormalDateException('add method parameter must be integer type')
|
||||
self.normalize(self.scalar() + days)
|
||||
|
||||
def __add__(self, days):
|
||||
"""add integer to BusinessDate and return a new, calculated value"""
|
||||
if not type(days) is IntType:
|
||||
raise NormalDateException('__add__ parameter must be integer type')
|
||||
cloned = self.clone()
|
||||
cloned.add(days)
|
||||
return cloned
|
||||
|
||||
def __sub__(self, v):
|
||||
return type(v) is IntType and self.__add__(-v) or self.scalar() - v.scalar()
|
||||
|
||||
def asNormalDate(self):
|
||||
return ND(self.normalDate)
|
||||
|
||||
def daysBetweenDates(self, normalDate):
|
||||
return self.asNormalDate.daysBetweenDates(normalDate)
|
||||
|
||||
def _checkDOW(self):
|
||||
if self.dayOfWeek()>4: raise NormalDateException("%s isn't a business day" % `self.normalDate`)
|
||||
|
||||
def normalize(self, i):
|
||||
i = int(i)
|
||||
NormalDate.normalize(self,(i/5)*7+i%5+BDEpochScalar)
|
||||
|
||||
def scalar(self):
|
||||
d = self.asNormalDate()
|
||||
i = d - BDEpoch #luckily BDEpoch is a Monday so we don't have a problem
|
||||
#concerning the relative weekday
|
||||
return 5*(i/7) + i%7
|
||||
|
||||
def setNormalDate(self, normalDate):
|
||||
NormalDate.setNormalDate(self,normalDate)
|
||||
self._checkDOW()
|
||||
|
||||
if __name__ == '__main__':
|
||||
today = NormalDate()
|
||||
print "NormalDate test:"
|
||||
print " Today (%s) is: %s %s" % (today, today.dayOfWeekAbbrev(), today.localeFormat())
|
||||
yesterday = today - 1
|
||||
print " Yesterday was: %s %s" % (yesterday.dayOfWeekAbbrev(), yesterday.localeFormat())
|
||||
tomorrow = today + 1
|
||||
print " Tomorrow will be: %s %s" % (tomorrow.dayOfWeekAbbrev(), tomorrow.localeFormat())
|
||||
print " Days between tomorrow and yesterday: %d" % (tomorrow - yesterday)
|
||||
print today.formatMS('{d}/{m}/{yy}')
|
||||
print today.formatMS('{dd}/{m}/{yy}')
|
||||
print today.formatMS('{ddd} {d}/{m}/{yy}')
|
||||
print today.formatMS('{dddd} {d}/{m}/{yy}')
|
||||
print today.formatMS('{d}/{mm}/{yy}')
|
||||
print today.formatMS('{d}/{mmm}/{yy}')
|
||||
print today.formatMS('{d}/{mmmm}/{yy}')
|
||||
print today.formatMS('{d}/{m}/{yyyy}')
|
||||
b = BusinessDate('20010116')
|
||||
print 'b=',b,'b.scalar()', b.scalar()
|
|
@ -1,55 +0,0 @@
|
|||
#! /usr/bin/python2.3
|
||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/pagesizes.py
|
||||
|
||||
"""This module defines a few common page sizes in points (1/72 inch).
|
||||
To be expanded to include things like label sizes, envelope windows
|
||||
etc."""
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
from reportlab.lib.units import cm, inch
|
||||
|
||||
_W, _H = (21*cm, 29.7*cm)
|
||||
|
||||
A6 = (_W*.5, _H*.5)
|
||||
A5 = (_H*.5, _W)
|
||||
A4 = (_W, _H)
|
||||
A3 = (_H, _W*2)
|
||||
A2 = (_W*2, _H*2)
|
||||
A1 = (_H*2, _W*4)
|
||||
A0 = (_W*4, _H*4)
|
||||
|
||||
LETTER = (8.5*inch, 11*inch)
|
||||
LEGAL = (8.5*inch, 14*inch)
|
||||
ELEVENSEVENTEEN = (11*inch, 17*inch)
|
||||
# lower case is deprecated as of 12/2001, but here
|
||||
# for compatability
|
||||
letter=LETTER
|
||||
legal=LEGAL
|
||||
elevenSeventeen = ELEVENSEVENTEEN
|
||||
|
||||
_BW, _BH = (25*cm, 35.3*cm)
|
||||
B6 = (_BW*.5, _BH*.5)
|
||||
B5 = (_BH*.5, _BW)
|
||||
B4 = (_BW, _BH)
|
||||
B3 = (_BH*2, _BW)
|
||||
B2 = (_BW*2, _BH*2)
|
||||
B1 = (_BH*4, _BW*2)
|
||||
B0 = (_BW*4, _BH*4)
|
||||
|
||||
def landscape(pagesize):
|
||||
"""Use this to get page orientation right"""
|
||||
a, b = pagesize
|
||||
if a < b:
|
||||
return (b, a)
|
||||
else:
|
||||
return (a, b)
|
||||
|
||||
def portrait(pagesize):
|
||||
"""Use this to get page orientation right"""
|
||||
a, b = pagesize
|
||||
if a >= b:
|
||||
return (b, a)
|
||||
else:
|
||||
return (a, b)
|
|
@ -1,348 +0,0 @@
|
|||
#! /usr/bin/python2.3
|
||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/randomtext.py
|
||||
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
###############################################################################
|
||||
# generates so-called 'Greek Text' for use in filling documents.
|
||||
###############################################################################
|
||||
"""
|
||||
This module exposes a function randomText() which generates paragraphs.
|
||||
These can be used when testing out document templates and stylesheets.
|
||||
A number of 'themes' are provided - please contribute more!
|
||||
We need some real Greek text too.
|
||||
|
||||
There are currently six themes provided:
|
||||
STARTUP (words suitable for a business plan - or not as the case may be),
|
||||
COMPUTERS (names of programming languages and operating systems etc),
|
||||
BLAH (variations on the word 'blah'),
|
||||
BUZZWORD (buzzword bingo),
|
||||
STARTREK (Star Trek),
|
||||
PRINTING (print-related terms)
|
||||
PYTHON (snippets and quotes from Monty Python)
|
||||
CHOMSKY (random lingusitic nonsense)
|
||||
|
||||
EXAMPLE USAGE:
|
||||
from reportlab.lib import randomtext
|
||||
print randomtext.randomText(randomtext.PYTHON, 10)
|
||||
|
||||
This prints a random number of random sentences (up to a limit
|
||||
of ten) using the theme 'PYTHON'.
|
||||
|
||||
"""
|
||||
|
||||
#theme one :-)
|
||||
STARTUP = ['strategic', 'direction', 'proactive', 'venture capital',
|
||||
'reengineering', 'forecast', 'resources', 'SWOT analysis',
|
||||
'forward-thinking', 'profit', 'growth', 'doubletalk', 'B2B', 'B2C',
|
||||
'venture capital', 'IPO', "NASDAQ meltdown - we're all doomed!"]
|
||||
|
||||
#theme two - computery things.
|
||||
COMPUTERS = ['Python', 'Perl', 'Pascal', 'Java', 'Javascript',
|
||||
'VB', 'Basic', 'LISP', 'Fortran', 'ADA', 'APL', 'C', 'C++',
|
||||
'assembler', 'Larry Wall', 'Guido van Rossum', 'XML', 'HTML',
|
||||
'cgi', 'cgi-bin', 'Amiga', 'Macintosh', 'Dell', 'Microsoft',
|
||||
'firewall', 'server', 'Linux', 'Unix', 'MacOS', 'BeOS', 'AS/400',
|
||||
'sendmail', 'TCP/IP', 'SMTP', 'RFC822-compliant', 'dynamic',
|
||||
'Internet', 'A/UX', 'Amiga OS', 'BIOS', 'boot managers', 'CP/M',
|
||||
'DOS', 'file system', 'FreeBSD', 'Freeware', 'GEOS', 'GNU',
|
||||
'Hurd', 'Linux', 'Mach', 'Macintosh OS', 'mailing lists', 'Minix',
|
||||
'Multics', 'NetWare', 'NextStep', 'OS/2', 'Plan 9', 'Realtime',
|
||||
'UNIX', 'VMS', 'Windows', 'X Windows', 'Xinu', 'security', 'Intel',
|
||||
'encryption', 'PGP' , 'software', 'ActiveX', 'AppleScript', 'awk',
|
||||
'BETA', 'COBOL', 'Delphi', 'Dylan', 'Eiffel', 'extreme programming',
|
||||
'Forth', 'Fortran', 'functional languages', 'Guile', 'format your hard drive',
|
||||
'Icon', 'IDL', 'Infer', 'Intercal', 'J', 'Java', 'JavaScript', 'CD-ROM',
|
||||
'JCL', 'Lisp', '"literate programming"', 'Logo', 'MUMPS', 'C: drive',
|
||||
'Modula-2', 'Modula-3', 'Oberon', 'Occam', 'OpenGL', 'parallel languages',
|
||||
'Pascal', 'Perl', 'PL/I', 'PostScript', 'Prolog', 'hardware', 'Blue Screen of Death',
|
||||
'Rexx', 'RPG', 'Scheme', 'scripting languages', 'Smalltalk', 'crash!', 'disc crash',
|
||||
'Spanner', 'SQL', 'Tcl/Tk', 'TeX', 'TOM', 'Visual', 'Visual Basic', '4GL',
|
||||
'VRML', 'Virtual Reality Modeling Language', 'difference engine', '...went into "yo-yo mode"',
|
||||
'Sun', 'Sun Microsystems', 'Hewlett Packard', 'output device',
|
||||
'CPU', 'memory', 'registers', 'monitor', 'TFT display', 'plasma screen',
|
||||
'bug report', '"mis-feature"', '...millions of bugs!', 'pizza',
|
||||
'"illiterate programming"','...lots of pizza!', 'pepperoni pizza',
|
||||
'coffee', 'Jolt Cola[TM]', 'beer', 'BEER!']
|
||||
|
||||
#theme three - 'blah' - for when you want to be subtle. :-)
|
||||
BLAH = ['Blah', 'BLAH', 'blahblah', 'blahblahblah', 'blah-blah',
|
||||
'blah!', '"Blah Blah Blah"', 'blah-de-blah', 'blah?', 'blah!!!',
|
||||
'blah...', 'Blah.', 'blah;', 'blah, Blah, BLAH!', 'Blah!!!']
|
||||
|
||||
#theme four - 'buzzword bingo' time!
|
||||
BUZZWORD = ['intellectual capital', 'market segment', 'flattening',
|
||||
'regroup', 'platform', 'client-based', 'long-term', 'proactive',
|
||||
'quality vector', 'out of the loop', 'implement',
|
||||
'streamline', 'cost-centered', 'phase', 'synergy',
|
||||
'synergize', 'interactive', 'facilitate',
|
||||
'appropriate', 'goal-setting', 'empowering', 'low-risk high-yield',
|
||||
'peel the onion', 'goal', 'downsize', 'result-driven',
|
||||
'conceptualize', 'multidisciplinary', 'gap analysis', 'dysfunctional',
|
||||
'networking', 'knowledge management', 'goal-setting',
|
||||
'mastery learning', 'communication', 'real-estate', 'quarterly',
|
||||
'scalable', 'Total Quality Management', 'best of breed',
|
||||
'nimble', 'monetize', 'benchmark', 'hardball',
|
||||
'client-centered', 'vision statement', 'empowerment',
|
||||
'lean & mean', 'credibility', 'synergistic',
|
||||
'backward-compatible', 'hardball', 'stretch the envelope',
|
||||
'bleeding edge', 'networking', 'motivation', 'best practice',
|
||||
'best of breed', 'implementation', 'Total Quality Management',
|
||||
'undefined', 'disintermediate', 'mindset', 'architect',
|
||||
'gap analysis', 'morale', 'objective', 'projection',
|
||||
'contribution', 'proactive', 'go the extra mile', 'dynamic',
|
||||
'world class', 'real estate', 'quality vector', 'credibility',
|
||||
'appropriate', 'platform', 'projection', 'mastery learning',
|
||||
'recognition', 'quality', 'scenario', 'performance based',
|
||||
'solutioning', 'go the extra mile', 'downsize', 'phase',
|
||||
'networking', 'experiencing slippage', 'knowledge management',
|
||||
'high priority', 'process', 'ethical', 'value-added', 'implement',
|
||||
're-factoring', 're-branding', 'embracing change']
|
||||
|
||||
#theme five - Star Trek
|
||||
STARTREK = ['Starfleet', 'Klingon', 'Romulan', 'Cardassian', 'Vulcan',
|
||||
'Benzite', 'IKV Pagh', 'emergency transponder', 'United Federation of Planets',
|
||||
'Bolian', "K'Vort Class Bird-of-Prey", 'USS Enterprise', 'USS Intrepid',
|
||||
'USS Reliant', 'USS Voyager', 'Starfleet Academy', 'Captain Picard',
|
||||
'Captain Janeway', 'Tom Paris', 'Harry Kim', 'Counsellor Troi',
|
||||
'Lieutenant Worf', 'Lieutenant Commander Data', 'Dr. Beverly Crusher',
|
||||
'Admiral Nakamura', 'Irumodic Syndrome', 'Devron system', 'Admiral Pressman',
|
||||
'asteroid field', 'sensor readings', 'Binars', 'distress signal', 'shuttlecraft',
|
||||
'cloaking device', 'shuttle bay 2', 'Dr. Pulaski', 'Lwaxana Troi', 'Pacifica',
|
||||
'William Riker', "Chief O'Brian", 'Soyuz class science vessel', 'Wolf-359',
|
||||
'Galaxy class vessel', 'Utopia Planitia yards', 'photon torpedo', 'Archer IV',
|
||||
'quantum flux', 'spacedock', 'Risa', 'Deep Space Nine', 'blood wine',
|
||||
'quantum torpedoes', 'holodeck', 'Romulan Warbird', 'Betazoid', 'turbolift', 'battle bridge',
|
||||
'Memory Alpha', '...with a phaser!', 'Romulan ale', 'Ferrengi', 'Klingon opera',
|
||||
'Quark', 'wormhole', 'Bajoran', 'cruiser', 'warship', 'battlecruiser', '"Intruder alert!"',
|
||||
'scout ship', 'science vessel', '"Borg Invasion imminent!" ', '"Abandon ship!"',
|
||||
'Red Alert!', 'warp-core breech', '"All hands abandon ship! This is not a drill!"']
|
||||
|
||||
#theme six - print-related terms
|
||||
PRINTING = ['points', 'picas', 'leading', 'kerning', 'CMYK', 'offset litho',
|
||||
'type', 'font family', 'typography', 'type designer',
|
||||
'baseline', 'white-out type', 'WOB', 'bicameral', 'bitmap',
|
||||
'blockletter', 'bleed', 'margin', 'body', 'widow', 'orphan',
|
||||
'cicero', 'cursive', 'letterform', 'sidehead', 'dingbat', 'leader',
|
||||
'DPI', 'drop-cap', 'paragraph', 'En', 'Em', 'flush left', 'left justified',
|
||||
'right justified', 'centered', 'italic', 'Latin letterform', 'ligature',
|
||||
'uppercase', 'lowercase', 'serif', 'sans-serif', 'weight', 'type foundry',
|
||||
'fleuron', 'folio', 'gutter', 'whitespace', 'humanist letterform', 'caption',
|
||||
'page', 'frame', 'ragged setting', 'flush-right', 'rule', 'drop shadows',
|
||||
'prepress', 'spot-colour', 'duotones', 'colour separations', 'four-colour printing',
|
||||
'Pantone[TM]', 'service bureau', 'imagesetter']
|
||||
|
||||
#it had to be done!...
|
||||
#theme seven - the "full Monty"!
|
||||
PYTHON = ['Good evening ladies and Bruces','I want to buy some cheese', 'You do have some cheese, do you?',
|
||||
"Of course sir, it's a cheese shop sir, we've got...",'discipline?... naked? ... With a melon!?',
|
||||
'The Church Police!!' , "There's a dead bishop on the landing", 'Would you like a twist of lemming sir?',
|
||||
'"Conquistador Coffee brings a new meaning to the word vomit"','Your lupins please',
|
||||
'Crelm Toothpaste, with the miracle ingredient Fraudulin',
|
||||
"Well there's the first result and the Silly Party has held Leicester.",
|
||||
'Hello, I would like to buy a fish license please', "Look, it's people like you what cause unrest!",
|
||||
"When we got home, our Dad would thrash us to sleep with his belt!", 'Luxury', "Gumby Brain Specialist",
|
||||
"My brain hurts!!!", "My brain hurts too.", "How not to be seen",
|
||||
"In this picture there are 47 people. None of them can be seen",
|
||||
"Mrs Smegma, will you stand up please?",
|
||||
"Mr. Nesbitt has learned the first lesson of 'Not Being Seen', not to stand up.",
|
||||
"My hovercraft is full of eels", "Ah. You have beautiful thighs.", "My nipples explode with delight",
|
||||
"Drop your panties Sir William, I cannot wait 'til lunchtime",
|
||||
"I'm a completely self-taught idiot.", "I always wanted to be a lumberjack!!!",
|
||||
"Told you so!! Oh, coitus!!", "",
|
||||
"Nudge nudge?", "Know what I mean!", "Nudge nudge, nudge nudge?", "Say no more!!",
|
||||
"Hello, well it's just after 8 o'clock, and time for the penguin on top of your television set to explode",
|
||||
"Oh, intercourse the penguin!!", "Funny that penguin being there, isn't it?",
|
||||
"I wish to register a complaint.", "Now that's what I call a dead parrot", "Pining for the fjords???",
|
||||
"No, that's not dead, it's ,uhhhh, resting", "This is an ex-parrot!!",
|
||||
"That parrot is definitely deceased.", "No, no, no - it's spelt Raymond Luxury Yach-t, but it's pronounced 'Throatwobbler Mangrove'.",
|
||||
"You're a very silly man and I'm not going to interview you.", "No Mungo... never kill a customer."
|
||||
"And I'd like to conclude by putting my finger up my nose",
|
||||
"egg and Spam", "egg bacon and Spam", "egg bacon sausage and Spam", "Spam bacon sausage and Spam",
|
||||
"Spam egg Spam Spam bacon and Spam", "Spam sausage Spam Spam Spam bacon Spam tomato and Spam",
|
||||
"Spam Spam Spam egg and Spam", "Spam Spam Spam Spam Spam Spam baked beans Spam Spam Spam",
|
||||
"Spam!!", "I don't like Spam!!!", "You can't have egg, bacon, Spam and sausage without the Spam!",
|
||||
"I'll have your Spam. I Love it!",
|
||||
"I'm having Spam Spam Spam Spam Spam Spam Spam baked beans Spam Spam Spam and Spam",
|
||||
"Have you got anything without Spam?", "There's Spam egg sausage and Spam, that's not got much Spam in it.",
|
||||
"No one expects the Spanish Inquisition!!", "Our weapon is surprise, surprise and fear!",
|
||||
"Get the comfy chair!", "Amongst our weaponry are such diverse elements as: fear, surprise, ruthless efficiency, an almost fanatical devotion to the Pope, and nice red uniforms - Oh damn!",
|
||||
"Nobody expects the... Oh bugger!", "What swims in the sea and gets caught in nets? Henri Bergson?",
|
||||
"Goats. Underwater goats with snorkels and flippers?", "A buffalo with an aqualung?",
|
||||
"Dinsdale was a looney, but he was a happy looney.", "Dinsdale!!",
|
||||
"The 127th Upper-Class Twit of the Year Show", "What a great Twit!",
|
||||
"thought by many to be this year's outstanding twit",
|
||||
"...and there's a big crowd here today to see these prize idiots in action.",
|
||||
"And now for something completely different.", "Stop that, it's silly",
|
||||
"We interrupt this program to annoy you and make things generally irritating",
|
||||
"This depraved and degrading spectacle is going to stop right now, do you hear me?",
|
||||
"Stop right there!", "This is absolutely disgusting and I'm not going to stand for it",
|
||||
"I object to all this sex on the television. I mean, I keep falling off",
|
||||
"Right! Stop that, it's silly. Very silly indeed", "Very silly indeed", "Lemon curry?",
|
||||
"And now for something completely different, a man with 3 buttocks",
|
||||
"I've heard of unisex, but I've never had it", "That's the end, stop the program! Stop it!"]
|
||||
leadins=[
|
||||
"To characterize a linguistic level L,",
|
||||
"On the other hand,",
|
||||
"This suggests that",
|
||||
"It appears that",
|
||||
"Furthermore,",
|
||||
"We will bring evidence in favor of the following thesis: ",
|
||||
"To provide a constituent structure for T(Z,K),",
|
||||
"From C1, it follows that",
|
||||
"For any transformation which is sufficiently diversified in application to be of any interest,",
|
||||
"Analogously,",
|
||||
"Clearly,",
|
||||
"Note that",
|
||||
"Of course,",
|
||||
"Suppose, for instance, that",
|
||||
"Thus",
|
||||
"With this clarification,",
|
||||
"Conversely,",
|
||||
"We have already seen that",
|
||||
"By combining adjunctions and certain deformations,",
|
||||
"I suggested that these results would follow from the assumption that",
|
||||
"If the position of the trace in (99c) were only relatively inaccessible to movement,",
|
||||
"However, this assumption is not correct, since",
|
||||
"Comparing these examples with their parasitic gap counterparts in (96) and (97), we see that",
|
||||
"In the discussion of resumptive pronouns following (81),",
|
||||
"So far,",
|
||||
"Nevertheless,",
|
||||
"For one thing,",
|
||||
"Summarizing, then, we assume that",
|
||||
"A consequence of the approach just outlined is that",
|
||||
"Presumably,",
|
||||
"On our assumptions,",
|
||||
"It may be, then, that",
|
||||
"It must be emphasized, once again, that",
|
||||
"Let us continue to suppose that",
|
||||
"Notice, incidentally, that",
|
||||
"A majority of informed linguistic specialists agree that",
|
||||
]
|
||||
|
||||
subjects = [
|
||||
"the notion of level of grammaticalness",
|
||||
"a case of semigrammaticalness of a different sort",
|
||||
"most of the methodological work in modern linguistics",
|
||||
"a subset of English sentences interesting on quite independent grounds",
|
||||
"the natural general principle that will subsume this case",
|
||||
"an important property of these three types of EC",
|
||||
"any associated supporting element",
|
||||
"the appearance of parasitic gaps in domains relatively inaccessible to ordinary extraction",
|
||||
"the speaker-hearer's linguistic intuition",
|
||||
"the descriptive power of the base component",
|
||||
"the earlier discussion of deviance",
|
||||
"this analysis of a formative as a pair of sets of features",
|
||||
"this selectionally introduced contextual feature",
|
||||
"a descriptively adequate grammar",
|
||||
"the fundamental error of regarding functional notions as categorial",
|
||||
"relational information",
|
||||
"the systematic use of complex symbols",
|
||||
"the theory of syntactic features developed earlier",
|
||||
]
|
||||
|
||||
verbs= [
|
||||
"can be defined in such a way as to impose",
|
||||
"delimits",
|
||||
"suffices to account for",
|
||||
"cannot be arbitrary in",
|
||||
"is not subject to",
|
||||
"does not readily tolerate",
|
||||
"raises serious doubts about",
|
||||
"is not quite equivalent to",
|
||||
"does not affect the structure of",
|
||||
"may remedy and, at the same time, eliminate",
|
||||
"is not to be considered in determining",
|
||||
"is to be regarded as",
|
||||
"is unspecified with respect to",
|
||||
"is, apparently, determined by",
|
||||
"is necessary to impose an interpretation on",
|
||||
"appears to correlate rather closely with",
|
||||
"is rather different from",
|
||||
]
|
||||
|
||||
objects = [
|
||||
"problems of phonemic and morphological analysis.",
|
||||
"a corpus of utterance tokens upon which conformity has been defined by the paired utterance test.",
|
||||
"the traditional practice of grammarians.",
|
||||
"the levels of acceptability from fairly high (e.g. (99a)) to virtual gibberish (e.g. (98d)).",
|
||||
"a stipulation to place the constructions into these various categories.",
|
||||
"a descriptive fact.",
|
||||
"a parasitic gap construction.",
|
||||
"the extended c-command discussed in connection with (34).",
|
||||
"the ultimate standard that determines the accuracy of any proposed grammar.",
|
||||
"the system of base rules exclusive of the lexicon.",
|
||||
"irrelevant intervening contexts in selectional rules.",
|
||||
"nondistinctness in the sense of distinctive feature theory.",
|
||||
"a general convention regarding the forms of the grammar.",
|
||||
"an abstract underlying order.",
|
||||
"an important distinction in language use.",
|
||||
"the requirement that branching is not tolerated within the dominance scope of a complex symbol.",
|
||||
"the strong generative capacity of the theory.",
|
||||
]
|
||||
|
||||
def format_wisdom(text,line_length=72):
|
||||
try:
|
||||
import textwrap
|
||||
return textwrap.fill(text, line_length)
|
||||
except:
|
||||
return text
|
||||
|
||||
def chomsky(times = 1):
|
||||
if not isinstance(times, int):
|
||||
return format_wisdom(__doc__)
|
||||
import random
|
||||
prevparts = []
|
||||
newparts = []
|
||||
output = []
|
||||
for i in xrange(times):
|
||||
for partlist in (leadins, subjects, verbs, objects):
|
||||
while 1:
|
||||
part = random.choice(partlist)
|
||||
if part not in prevparts:
|
||||
break
|
||||
newparts.append(part)
|
||||
output.append(' '.join(newparts))
|
||||
prevparts = newparts
|
||||
newparts = []
|
||||
return format_wisdom(' '.join(output))
|
||||
|
||||
from reportlab import rl_config
|
||||
if rl_config.invariant:
|
||||
if not getattr(rl_config,'_random',None):
|
||||
rl_config._random = 1
|
||||
import random
|
||||
random.seed(2342471922L)
|
||||
del random
|
||||
del rl_config
|
||||
|
||||
def randomText(theme=STARTUP, sentences=5):
|
||||
#this may or may not be appropriate in your company
|
||||
if type(theme)==type(''):
|
||||
if theme.lower()=='chomsky': return chomsky(sentences)
|
||||
elif theme.upper() in ('STARTUP','COMPUTERS','BLAH','BUZZWORD','STARTREK','PRINTING','PYTHON'):
|
||||
theme = globals()[theme]
|
||||
else:
|
||||
raise ValueError('Unknown theme "%s"' % theme)
|
||||
|
||||
from random import randint, choice
|
||||
|
||||
RANDOMWORDS = theme
|
||||
|
||||
#sentences = 5
|
||||
output = ""
|
||||
for sentenceno in range(randint(1,sentences)):
|
||||
output = output + 'Blah'
|
||||
for wordno in range(randint(10,25)):
|
||||
if randint(0,4)==0:
|
||||
word = choice(RANDOMWORDS)
|
||||
else:
|
||||
word = 'blah'
|
||||
output = output + ' ' +word
|
||||
output = output+'. '
|
||||
return output
|
||||
|
||||
if __name__=='__main__':
|
||||
print chomsky(5)
|
|
@ -1,440 +0,0 @@
|
|||
"""Radically simple xml parsing
|
||||
|
||||
Example parse
|
||||
|
||||
<this type="xml">text <b>in</b> xml</this>
|
||||
|
||||
( "this",
|
||||
{"type": "xml"},
|
||||
[ "text ",
|
||||
("b", None, ["in"], None),
|
||||
" xml"
|
||||
]
|
||||
None )
|
||||
|
||||
{ 0: "this"
|
||||
"type": "xml"
|
||||
1: ["text ",
|
||||
{0: "b", 1:["in"]},
|
||||
" xml"]
|
||||
}
|
||||
|
||||
Ie, xml tag translates to a tuple:
|
||||
(name, dictofattributes, contentlist, miscellaneousinfo)
|
||||
|
||||
where miscellaneousinfo can be anything, (but defaults to None)
|
||||
(with the intention of adding, eg, line number information)
|
||||
|
||||
special cases: name of "" means "top level, no containing tag".
|
||||
Top level parse always looks like this
|
||||
|
||||
("", list, None, None)
|
||||
|
||||
contained text of None means <simple_tag\>
|
||||
|
||||
In order to support stuff like
|
||||
|
||||
<this></this><one></one>
|
||||
|
||||
AT THE MOMENT & ETCETERA ARE IGNORED. THEY MUST BE PROCESSED
|
||||
IN A POST-PROCESSING STEP.
|
||||
|
||||
PROLOGUES ARE NOT UNDERSTOOD. OTHER STUFF IS PROBABLY MISSING.
|
||||
"""
|
||||
|
||||
RequirePyRXP = 0 # set this to 1 to disable the nonvalidating fallback parser.
|
||||
|
||||
import string
|
||||
try:
|
||||
#raise ImportError, "dummy error"
|
||||
simpleparse = 0
|
||||
import pyRXP
|
||||
if pyRXP.version>='0.5':
|
||||
def warnCB(s):
|
||||
print s
|
||||
pyRXP_parser = pyRXP.Parser(
|
||||
ErrorOnValidityErrors=1,
|
||||
NoNoDTDWarning=1,
|
||||
ExpandCharacterEntities=0,
|
||||
ExpandGeneralEntities=0,
|
||||
warnCB = warnCB,
|
||||
srcName='string input')
|
||||
def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None):
|
||||
pyRXP_parser.eoCB = eoCB
|
||||
p = pyRXP_parser.parse(xmlText)
|
||||
return oneOutermostTag and p or ('',None,[p],None)
|
||||
else:
|
||||
def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None):
|
||||
'''eoCB is the entity open callback'''
|
||||
def warnCB(s):
|
||||
print s
|
||||
flags = 0x0157e1ff | pyRXP.PARSER_FLAGS['ErrorOnValidityErrors']
|
||||
for k in ('ExpandCharacterEntities','ExpandGeneralEntities'):
|
||||
flags = flags & (~pyRXP.PARSER_FLAGS[k])
|
||||
p = pyRXP.parse(xmlText,srcName='string input',flags=flags,warnCB=warnCB,eoCB=eoCB)
|
||||
return oneOutermostTag and p or ('',None,[p],None)
|
||||
except ImportError:
|
||||
simpleparse = 1
|
||||
|
||||
NONAME = ""
|
||||
NAMEKEY = 0
|
||||
CONTENTSKEY = 1
|
||||
CDATAMARKER = "<![CDATA["
|
||||
LENCDATAMARKER = len(CDATAMARKER)
|
||||
CDATAENDMARKER = "]]>"
|
||||
replacelist = [("<", "<"), (">", ">"), ("&", "&")] # amp must be last
|
||||
#replacelist = []
|
||||
def unEscapeContentList(contentList):
|
||||
result = []
|
||||
from string import replace
|
||||
for e in contentList:
|
||||
if "&" in e:
|
||||
for (old, new) in replacelist:
|
||||
e = replace(e, old, new)
|
||||
result.append(e)
|
||||
return result
|
||||
|
||||
def parsexmlSimple(xmltext, oneOutermostTag=0,eoCB=None,entityReplacer=unEscapeContentList):
|
||||
"""official interface: discard unused cursor info"""
|
||||
if RequirePyRXP:
|
||||
raise ImportError, "pyRXP not found, fallback parser disabled"
|
||||
(result, cursor) = parsexml0(xmltext,entityReplacer=entityReplacer)
|
||||
if oneOutermostTag:
|
||||
return result[2][0]
|
||||
else:
|
||||
return result
|
||||
|
||||
if simpleparse:
|
||||
parsexml = parsexmlSimple
|
||||
|
||||
def parseFile(filename):
|
||||
raw = open(filename, 'r').read()
|
||||
return parsexml(raw)
|
||||
|
||||
verbose = 0
|
||||
|
||||
def skip_prologue(text, cursor):
|
||||
"""skip any prologue found after cursor, return index of rest of text"""
|
||||
### NOT AT ALL COMPLETE!!! definitely can be confused!!!
|
||||
from string import find
|
||||
prologue_elements = ("!DOCTYPE", "?xml", "!--")
|
||||
done = None
|
||||
while done is None:
|
||||
#print "trying to skip:", repr(text[cursor:cursor+20])
|
||||
openbracket = find(text, "<", cursor)
|
||||
if openbracket<0: break
|
||||
past = openbracket+1
|
||||
found = None
|
||||
for e in prologue_elements:
|
||||
le = len(e)
|
||||
if text[past:past+le]==e:
|
||||
found = 1
|
||||
cursor = find(text, ">", past)
|
||||
if cursor<0:
|
||||
raise ValueError, "can't close prologue %s" % `e`
|
||||
cursor = cursor+1
|
||||
if found is None:
|
||||
done=1
|
||||
#print "done skipping"
|
||||
return cursor
|
||||
|
||||
def parsexml0(xmltext, startingat=0, toplevel=1,
|
||||
# snarf in some globals
|
||||
strip=string.strip, split=string.split, find=string.find, entityReplacer=unEscapeContentList,
|
||||
#len=len, None=None
|
||||
#LENCDATAMARKER=LENCDATAMARKER, CDATAMARKER=CDATAMARKER
|
||||
):
|
||||
"""simple recursive descent xml parser...
|
||||
return (dictionary, endcharacter)
|
||||
special case: comment returns (None, endcharacter)"""
|
||||
#from string import strip, split, find
|
||||
#print "parsexml0", `xmltext[startingat: startingat+10]`
|
||||
# DEFAULTS
|
||||
NameString = NONAME
|
||||
ContentList = AttDict = ExtraStuff = None
|
||||
if toplevel is not None:
|
||||
#if verbose: print "at top level"
|
||||
#if startingat!=0:
|
||||
# raise ValueError, "have to start at 0 for top level!"
|
||||
xmltext = strip(xmltext)
|
||||
cursor = startingat
|
||||
#look for interesting starting points
|
||||
firstbracket = find(xmltext, "<", cursor)
|
||||
afterbracket2char = xmltext[firstbracket+1:firstbracket+3]
|
||||
#print "a", `afterbracket2char`
|
||||
#firstampersand = find(xmltext, "&", cursor)
|
||||
#if firstampersand>0 and firstampersand<firstbracket:
|
||||
# raise ValueError, "I don't handle ampersands yet!!!"
|
||||
docontents = 1
|
||||
if firstbracket<0:
|
||||
# no tags
|
||||
#if verbose: print "no tags"
|
||||
if toplevel is not None:
|
||||
#D = {NAMEKEY: NONAME, CONTENTSKEY: [xmltext[cursor:]]}
|
||||
ContentList = [xmltext[cursor:]]
|
||||
if entityReplacer: ContentList = entityReplacer(ContentList)
|
||||
return (NameString, AttDict, ContentList, ExtraStuff), len(xmltext)
|
||||
else:
|
||||
raise ValueError, "no tags at non-toplevel %s" % `xmltext[cursor:cursor+20]`
|
||||
#D = {}
|
||||
L = []
|
||||
# look for start tag
|
||||
# NEED to force always outer level is unnamed!!!
|
||||
#if toplevel and firstbracket>0:
|
||||
#afterbracket2char = xmltext[firstbracket:firstbracket+2]
|
||||
if toplevel is not None:
|
||||
#print "toplevel with no outer tag"
|
||||
NameString = name = NONAME
|
||||
cursor = skip_prologue(xmltext, cursor)
|
||||
#break
|
||||
elif firstbracket<0:
|
||||
raise ValueError, "non top level entry should be at start tag: %s" % repr(xmltext[:10])
|
||||
# special case: CDATA
|
||||
elif afterbracket2char=="![" and xmltext[firstbracket:firstbracket+9]=="<![CDATA[":
|
||||
#print "in CDATA", cursor
|
||||
# skip straight to the close marker
|
||||
startcdata = firstbracket+9
|
||||
endcdata = find(xmltext, CDATAENDMARKER, startcdata)
|
||||
if endcdata<0:
|
||||
raise ValueError, "unclosed CDATA %s" % repr(xmltext[cursor:cursor+20])
|
||||
NameString = CDATAMARKER
|
||||
ContentList = [xmltext[startcdata: endcdata]]
|
||||
cursor = endcdata+len(CDATAENDMARKER)
|
||||
docontents = None
|
||||
# special case COMMENT
|
||||
elif afterbracket2char=="!-" and xmltext[firstbracket:firstbracket+4]=="<!--":
|
||||
#print "in COMMENT"
|
||||
endcommentdashes = find(xmltext, "--", firstbracket+4)
|
||||
if endcommentdashes<firstbracket:
|
||||
raise ValueError, "unterminated comment %s" % repr(xmltext[cursor:cursor+20])
|
||||
endcomment = endcommentdashes+2
|
||||
if xmltext[endcomment]!=">":
|
||||
raise ValueError, "invalid comment: contains double dashes %s" % repr(xmltext[cursor:cursor+20])
|
||||
return (None, endcomment+1) # shortcut exit
|
||||
else:
|
||||
# get the rest of the tag
|
||||
#if verbose: print "parsing start tag"
|
||||
# make sure the tag isn't in doublequote pairs
|
||||
closebracket = find(xmltext, ">", firstbracket)
|
||||
noclose = closebracket<0
|
||||
startsearch = closebracket+1
|
||||
pastfirstbracket = firstbracket+1
|
||||
tagcontent = xmltext[pastfirstbracket:closebracket]
|
||||
# shortcut, no equal means nothing but name in the tag content
|
||||
if '=' not in tagcontent:
|
||||
if tagcontent[-1]=="/":
|
||||
# simple case
|
||||
#print "simple case", tagcontent
|
||||
tagcontent = tagcontent[:-1]
|
||||
docontents = None
|
||||
name = strip(tagcontent)
|
||||
NameString = name
|
||||
cursor = startsearch
|
||||
else:
|
||||
if '"' in tagcontent:
|
||||
# check double quotes
|
||||
stop = None
|
||||
# not inside double quotes! (the split should have odd length)
|
||||
if noclose or len(split(tagcontent+".", '"'))% 2:
|
||||
stop=1
|
||||
while stop is None:
|
||||
closebracket = find(xmltext, ">", startsearch)
|
||||
startsearch = closebracket+1
|
||||
noclose = closebracket<0
|
||||
tagcontent = xmltext[pastfirstbracket:closebracket]
|
||||
# not inside double quotes! (the split should have odd length)
|
||||
if noclose or len(split(tagcontent+".", '"'))% 2:
|
||||
stop=1
|
||||
if noclose:
|
||||
raise ValueError, "unclosed start tag %s" % repr(xmltext[firstbracket:firstbracket+20])
|
||||
cursor = startsearch
|
||||
#cursor = closebracket+1
|
||||
# handle simple tag /> syntax
|
||||
if xmltext[closebracket-1]=="/":
|
||||
#if verbose: print "it's a simple tag"
|
||||
closebracket = closebracket-1
|
||||
tagcontent = tagcontent[:-1]
|
||||
docontents = None
|
||||
#tagcontent = xmltext[firstbracket+1:closebracket]
|
||||
tagcontent = strip(tagcontent)
|
||||
taglist = split(tagcontent, "=")
|
||||
#if not taglist:
|
||||
# raise ValueError, "tag with no name %s" % repr(xmltext[firstbracket:firstbracket+20])
|
||||
taglist0 = taglist[0]
|
||||
taglist0list = split(taglist0)
|
||||
#if len(taglist0list)>2:
|
||||
# raise ValueError, "bad tag head %s" % repr(taglist0)
|
||||
name = taglist0list[0]
|
||||
#print "tag name is", name
|
||||
NameString = name
|
||||
# now parse the attributes
|
||||
attributename = taglist0list[-1]
|
||||
# put a fake att name at end of last taglist entry for consistent parsing
|
||||
taglist[-1] = taglist[-1]+" f"
|
||||
AttDict = D = {}
|
||||
taglistindex = 1
|
||||
lasttaglistindex = len(taglist)
|
||||
#for attentry in taglist[1:]:
|
||||
while taglistindex<lasttaglistindex:
|
||||
#print "looking for attribute named", attributename
|
||||
attentry = taglist[taglistindex]
|
||||
taglistindex = taglistindex+1
|
||||
attentry = strip(attentry)
|
||||
if attentry[0]!='"':
|
||||
raise ValueError, "attribute value must start with double quotes" + repr(attentry)
|
||||
while '"' not in attentry[1:]:
|
||||
# must have an = inside the attribute value...
|
||||
if taglistindex>lasttaglistindex:
|
||||
raise ValueError, "unclosed value " + repr(attentry)
|
||||
nextattentry = taglist[taglistindex]
|
||||
taglistindex = taglistindex+1
|
||||
attentry = "%s=%s" % (attentry, nextattentry)
|
||||
attentry = strip(attentry) # only needed for while loop...
|
||||
attlist = split(attentry)
|
||||
nextattname = attlist[-1]
|
||||
attvalue = attentry[:-len(nextattname)]
|
||||
attvalue = strip(attvalue)
|
||||
try:
|
||||
first = attvalue[0]; last=attvalue[-1]
|
||||
except:
|
||||
raise ValueError, "attvalue,attentry,attlist="+repr((attvalue, attentry,attlist))
|
||||
if first==last=='"' or first==last=="'":
|
||||
attvalue = attvalue[1:-1]
|
||||
#print attributename, "=", attvalue
|
||||
D[attributename] = attvalue
|
||||
attributename = nextattname
|
||||
# pass over other tags and content looking for end tag
|
||||
if docontents is not None:
|
||||
#print "now looking for end tag"
|
||||
ContentList = L
|
||||
while docontents is not None:
|
||||
nextopenbracket = find(xmltext, "<", cursor)
|
||||
if nextopenbracket<cursor:
|
||||
#if verbose: print "no next open bracket found"
|
||||
if name==NONAME:
|
||||
#print "no more tags for noname", repr(xmltext[cursor:cursor+10])
|
||||
docontents=None # done
|
||||
remainder = xmltext[cursor:]
|
||||
cursor = len(xmltext)
|
||||
if remainder:
|
||||
L.append(remainder)
|
||||
else:
|
||||
raise ValueError, "no close bracket for %s found after %s" % (name,repr(xmltext[cursor: cursor+20]))
|
||||
# is it a close bracket?
|
||||
elif xmltext[nextopenbracket+1]=="/":
|
||||
#print "found close bracket", repr(xmltext[nextopenbracket:nextopenbracket+20])
|
||||
nextclosebracket = find(xmltext, ">", nextopenbracket)
|
||||
if nextclosebracket<nextopenbracket:
|
||||
raise ValueError, "unclosed close tag %s" % repr(xmltext[nextopenbracket: nextopenbracket+20])
|
||||
closetagcontents = xmltext[nextopenbracket+2: nextclosebracket]
|
||||
closetaglist = split(closetagcontents)
|
||||
#if len(closetaglist)!=1:
|
||||
#print closetagcontents
|
||||
#raise ValueError, "bad close tag format %s" % repr(xmltext[nextopenbracket: nextopenbracket+20])
|
||||
# name should match
|
||||
closename = closetaglist[0]
|
||||
#if verbose: print "closetag name is", closename
|
||||
if name!=closename:
|
||||
prefix = xmltext[:cursor]
|
||||
endlinenum = len(split(prefix, "\n"))
|
||||
prefix = xmltext[:startingat]
|
||||
linenum = len(split(prefix, "\n"))
|
||||
raise ValueError, \
|
||||
"at lines %s...%s close tag name doesn't match %s...%s %s" %(
|
||||
linenum, endlinenum, `name`, `closename`, repr(xmltext[cursor: cursor+100]))
|
||||
remainder = xmltext[cursor:nextopenbracket]
|
||||
if remainder:
|
||||
#if verbose: print "remainder", repr(remainder)
|
||||
L.append(remainder)
|
||||
cursor = nextclosebracket+1
|
||||
#print "for", name, "found close tag"
|
||||
docontents = None # done
|
||||
# otherwise we are looking at a new tag, recursively parse it...
|
||||
# first record any intervening content
|
||||
else:
|
||||
remainder = xmltext[cursor:nextopenbracket]
|
||||
if remainder:
|
||||
L.append(remainder)
|
||||
#if verbose:
|
||||
# #print "skipping", repr(remainder)
|
||||
# #print "--- recursively parsing starting at", xmltext[nextopenbracket:nextopenbracket+20]
|
||||
(parsetree, cursor) = parsexml0(xmltext, startingat=nextopenbracket, toplevel=None, entityReplacer=entityReplacer)
|
||||
if parsetree:
|
||||
L.append(parsetree)
|
||||
# maybe should check for trailing garbage?
|
||||
# toplevel:
|
||||
# remainder = strip(xmltext[cursor:])
|
||||
# if remainder:
|
||||
# raise ValueError, "trailing garbage at top level %s" % repr(remainder[:20])
|
||||
if ContentList:
|
||||
if entityReplacer: ContentList = entityReplacer(ContentList)
|
||||
t = (NameString, AttDict, ContentList, ExtraStuff)
|
||||
return (t, cursor)
|
||||
|
||||
import types
|
||||
def pprettyprint(parsedxml):
|
||||
"""pretty printer mainly for testing"""
|
||||
st = types.StringType
|
||||
if type(parsedxml) is st:
|
||||
return parsedxml
|
||||
(name, attdict, textlist, extra) = parsedxml
|
||||
if not attdict: attdict={}
|
||||
join = string.join
|
||||
attlist = []
|
||||
for k in attdict.keys():
|
||||
v = attdict[k]
|
||||
attlist.append("%s=%s" % (k, `v`))
|
||||
attributes = join(attlist, " ")
|
||||
if not name and attributes:
|
||||
raise ValueError, "name missing with attributes???"
|
||||
if textlist is not None:
|
||||
# with content
|
||||
textlistpprint = map(pprettyprint, textlist)
|
||||
textpprint = join(textlistpprint, "\n")
|
||||
if not name:
|
||||
return textpprint # no outer tag
|
||||
# indent it
|
||||
nllist = string.split(textpprint, "\n")
|
||||
textpprint = " "+join(nllist, "\n ")
|
||||
return "<%s %s>\n%s\n</%s>" % (name, attributes, textpprint, name)
|
||||
# otherwise must be a simple tag
|
||||
return "<%s %s/>" % (name, attributes)
|
||||
|
||||
dump = 0
|
||||
def testparse(s):
|
||||
from time import time
|
||||
from pprint import pprint
|
||||
now = time()
|
||||
D = parsexmlSimple(s)
|
||||
print "DONE", time()-now
|
||||
if dump&4:
|
||||
pprint(D)
|
||||
#pprint(D)
|
||||
if dump&1:
|
||||
print "============== reformatting"
|
||||
p = pprettyprint(D)
|
||||
print p
|
||||
|
||||
def test():
|
||||
testparse("""<this type="xml">text <><b>in</b> <funnytag foo="bar"/> xml</this>
|
||||
<!-- comment -->
|
||||
<![CDATA[
|
||||
<this type="xml">text <b>in</b> xml</this> ]]>
|
||||
<tag with="<brackets in values>">just testing brackets feature</tag>
|
||||
""")
|
||||
|
||||
filenames = [ #"../../reportlab/demos/pythonpoint/pythonpoint.xml",
|
||||
"samples/hamlet.xml"]
|
||||
|
||||
#filenames = ["moa.xml"]
|
||||
|
||||
dump=1
|
||||
if __name__=="__main__":
|
||||
test()
|
||||
from time import time
|
||||
now = time()
|
||||
for f in filenames:
|
||||
t = open(f).read()
|
||||
print "parsing", f
|
||||
testparse(t)
|
||||
print "elapsed", time()-now
|
|
@ -1,284 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/sequencer.py
|
||||
__version__=''' $Id$ '''
|
||||
"""This module defines a single public class, Sequencer, which aids in
|
||||
numbering and formatting lists."""
|
||||
#
|
||||
# roman numbers conversion thanks to
|
||||
#
|
||||
# fredrik lundh, november 1996 (based on a C hack from 1984)
|
||||
#
|
||||
# fredrik@pythonware.com
|
||||
# http://www.pythonware.com
|
||||
|
||||
_RN_TEMPLATES = [ 0, 01, 011, 0111, 012, 02, 021, 0211, 02111, 013 ]
|
||||
_RN_LETTERS = "IVXLCDM"
|
||||
|
||||
from string import lower
|
||||
|
||||
|
||||
def _format_I(value):
|
||||
if value < 0 or value > 3999:
|
||||
raise ValueError, "illegal value"
|
||||
str = ""
|
||||
base = -1
|
||||
while value:
|
||||
value, index = divmod(value, 10)
|
||||
tmp = _RN_TEMPLATES[index]
|
||||
while tmp:
|
||||
tmp, index = divmod(tmp, 8)
|
||||
str = _RN_LETTERS[index+base] + str
|
||||
base = base + 2
|
||||
return str
|
||||
|
||||
def _format_i(num):
|
||||
return lower(_format_I(num))
|
||||
|
||||
def _format_123(num):
|
||||
"""The simplest formatter"""
|
||||
return str(num)
|
||||
|
||||
def _format_ABC(num):
|
||||
"""Uppercase. Wraps around at 26."""
|
||||
n = (num -1) % 26
|
||||
return chr(n+65)
|
||||
|
||||
def _format_abc(num):
|
||||
"""Lowercase. Wraps around at 26."""
|
||||
n = (num -1) % 26
|
||||
return chr(n+97)
|
||||
|
||||
|
||||
class _Counter:
|
||||
"""Private class used by Sequencer. Each counter
|
||||
knows its format, and the IDs of anything it
|
||||
resets, as well as its value. Starts at zero
|
||||
and increments just before you get the new value,
|
||||
so that it is still 'Chapter 5' and not 'Chapter 6'
|
||||
when you print 'Figure 5.1'"""
|
||||
|
||||
def __init__(self):
|
||||
self._base = 0
|
||||
self._value = self._base
|
||||
self._formatter = _format_123
|
||||
self._resets = []
|
||||
|
||||
def setFormatter(self, formatFunc):
|
||||
self._formatter = formatFunc
|
||||
|
||||
def reset(self, value=None):
|
||||
if value:
|
||||
self._value = value
|
||||
else:
|
||||
self._value = self._base
|
||||
|
||||
def next(self):
|
||||
self._value = self._value + 1
|
||||
v = self._value
|
||||
for counter in self._resets:
|
||||
counter.reset()
|
||||
return v
|
||||
|
||||
def _this(self):
|
||||
return self._value
|
||||
|
||||
def nextf(self):
|
||||
"""Returns next value formatted"""
|
||||
return self._formatter(self.next())
|
||||
|
||||
def thisf(self):
|
||||
return self._formatter(self._this())
|
||||
|
||||
def chain(self, otherCounter):
|
||||
if not otherCounter in self._resets:
|
||||
self._resets.append(otherCounter)
|
||||
|
||||
|
||||
class Sequencer:
|
||||
"""Something to make it easy to number paragraphs, sections,
|
||||
images and anything else. The features include registering
|
||||
new string formats for sequences, and 'chains' whereby
|
||||
some counters are reset when their parents.
|
||||
It keeps track of a number of
|
||||
'counters', which are created on request:
|
||||
Usage:
|
||||
>>> seq = layout.Sequencer()
|
||||
>>> seq.next('Bullets')
|
||||
1
|
||||
>>> seq.next('Bullets')
|
||||
2
|
||||
>>> seq.next('Bullets')
|
||||
3
|
||||
>>> seq.reset('Bullets')
|
||||
>>> seq.next('Bullets')
|
||||
1
|
||||
>>> seq.next('Figures')
|
||||
1
|
||||
>>>
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._counters = {} #map key to current number
|
||||
self._defaultCounter = None
|
||||
|
||||
self._formatters = {
|
||||
# the formats it knows initially
|
||||
'1':_format_123,
|
||||
'A':_format_ABC,
|
||||
'a':_format_abc,
|
||||
'I':_format_I,
|
||||
'i':_format_i,
|
||||
}
|
||||
|
||||
def _getCounter(self, counter=None):
|
||||
"""Creates one if not present"""
|
||||
try:
|
||||
return self._counters[counter]
|
||||
except KeyError:
|
||||
cnt = _Counter()
|
||||
self._counters[counter] = cnt
|
||||
return cnt
|
||||
|
||||
def _this(self, counter=None):
|
||||
"""Retrieves counter value but does not increment. For
|
||||
new counters, sets base value to 1."""
|
||||
if not counter:
|
||||
counter = self._defaultCounter
|
||||
return self._getCounter(counter)._this()
|
||||
|
||||
def next(self, counter=None):
|
||||
"""Retrieves the numeric value for the given counter, then
|
||||
increments it by one. New counters start at one."""
|
||||
if not counter:
|
||||
counter = self._defaultCounter
|
||||
return self._getCounter(counter).next()
|
||||
|
||||
def thisf(self, counter=None):
|
||||
if not counter:
|
||||
counter = self._defaultCounter
|
||||
return self._getCounter(counter).thisf()
|
||||
|
||||
def nextf(self, counter=None):
|
||||
"""Retrieves the numeric value for the given counter, then
|
||||
increments it by one. New counters start at one."""
|
||||
if not counter:
|
||||
counter = self._defaultCounter
|
||||
return self._getCounter(counter).nextf()
|
||||
|
||||
def setDefaultCounter(self, default=None):
|
||||
"""Changes the key used for the default"""
|
||||
self._defaultCounter = default
|
||||
|
||||
def registerFormat(self, format, func):
|
||||
"""Registers a new formatting function. The funtion
|
||||
must take a number as argument and return a string;
|
||||
fmt is a short menmonic string used to access it."""
|
||||
self._formatters[format] = func
|
||||
|
||||
def setFormat(self, counter, format):
|
||||
"""Specifies that the given counter should use
|
||||
the given format henceforth."""
|
||||
func = self._formatters[format]
|
||||
self._getCounter(counter).setFormatter(func)
|
||||
|
||||
def reset(self, counter=None, base=0):
|
||||
if not counter:
|
||||
counter = self._defaultCounter
|
||||
self._getCounter(counter)._value = base
|
||||
|
||||
def chain(self, parent, child):
|
||||
p = self._getCounter(parent)
|
||||
c = self._getCounter(child)
|
||||
p.chain(c)
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Allows compact notation to support the format function.
|
||||
s['key'] gets current value, s['key+'] increments."""
|
||||
if key[-1:] == '+':
|
||||
counter = key[:-1]
|
||||
return self.nextf(counter)
|
||||
else:
|
||||
return self.thisf(key)
|
||||
|
||||
def format(self, template):
|
||||
"""The crowning jewels - formats multi-level lists."""
|
||||
return template % self
|
||||
|
||||
def dump(self):
|
||||
"""Write current state to stdout for diagnostics"""
|
||||
counters = self._counters.items()
|
||||
counters.sort()
|
||||
print 'Sequencer dump:'
|
||||
for (key, counter) in counters:
|
||||
print ' %s: value = %d, base = %d, format example = %s' % (
|
||||
key, counter._this(), counter._base, counter.thisf())
|
||||
|
||||
|
||||
"""Your story builder needs to set this to"""
|
||||
_sequencer = None
|
||||
|
||||
def getSequencer():
|
||||
global _sequencer
|
||||
if _sequencer is None:
|
||||
_sequencer = Sequencer()
|
||||
return _sequencer
|
||||
|
||||
def setSequencer(seq):
|
||||
global _sequencer
|
||||
s = _sequencer
|
||||
_sequencer = seq
|
||||
return s
|
||||
|
||||
def test():
|
||||
s = Sequencer()
|
||||
print 'Counting using default sequence: %d %d %d' % (s.next(),s.next(), s.next())
|
||||
print 'Counting Figures: Figure %d, Figure %d, Figure %d' % (
|
||||
s.next('figure'), s.next('figure'), s.next('figure'))
|
||||
print 'Back to default again: %d' % s.next()
|
||||
s.setDefaultCounter('list1')
|
||||
print 'Set default to list1: %d %d %d' % (s.next(),s.next(), s.next())
|
||||
s.setDefaultCounter()
|
||||
print 'Set default to None again: %d %d %d' % (s.next(),s.next(), s.next())
|
||||
print
|
||||
print 'Creating Appendix counter with format A, B, C...'
|
||||
s.setFormat('Appendix', 'A')
|
||||
print ' Appendix %s, Appendix %s, Appendix %s' % (
|
||||
s.nextf('Appendix'), s.nextf('Appendix'),s.nextf('Appendix'))
|
||||
|
||||
def format_french(num):
|
||||
return ('un','deux','trois','quatre','cinq')[(num-1)%5]
|
||||
print
|
||||
print 'Defining a custom format with french words:'
|
||||
s.registerFormat('french', format_french)
|
||||
s.setFormat('FrenchList', 'french')
|
||||
print ' ',
|
||||
for i in range(1,6):
|
||||
print s.nextf('FrenchList'),
|
||||
print
|
||||
print 'Chaining H1 and H2 - H2 goes back to one when H1 increases'
|
||||
s.chain('H1','H2')
|
||||
print ' H1 = %d' % s.next('H1')
|
||||
print ' H2 = %d' % s.next('H2')
|
||||
print ' H2 = %d' % s.next('H2')
|
||||
print ' H2 = %d' % s.next('H2')
|
||||
print ' H1 = %d' % s.next('H1')
|
||||
print ' H2 = %d' % s.next('H2')
|
||||
print ' H2 = %d' % s.next('H2')
|
||||
print ' H2 = %d' % s.next('H2')
|
||||
print
|
||||
print 'GetItem notation - append a plus to increment'
|
||||
print ' seq["Appendix"] = %s' % s["Appendix"]
|
||||
print ' seq["Appendix+"] = %s' % s["Appendix+"]
|
||||
print ' seq["Appendix+"] = %s' % s["Appendix+"]
|
||||
print ' seq["Appendix"] = %s' % s["Appendix"]
|
||||
print
|
||||
print 'Finally, string format notation for nested lists. Cool!'
|
||||
print 'The expression ("Figure %(Chapter)s.%(Figure+)s" % seq) gives:'
|
||||
print ' Figure %(Chapter)s.%(Figure+)s' % s
|
||||
print ' Figure %(Chapter)s.%(Figure+)s' % s
|
||||
print ' Figure %(Chapter)s.%(Figure+)s' % s
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
|
@ -1,38 +0,0 @@
|
|||
#! /usr/bin/python2.3
|
||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/set_ops.py
|
||||
import types
|
||||
import string
|
||||
|
||||
def __set_coerce(t, S):
|
||||
if t is types.ListType:
|
||||
return list(S)
|
||||
elif t is types.TupleType:
|
||||
return tuple(S)
|
||||
elif t is types.StringType:
|
||||
return string.join(S, '')
|
||||
return S
|
||||
|
||||
def unique(seq):
|
||||
result = []
|
||||
for i in seq:
|
||||
if i not in result:
|
||||
result.append(i)
|
||||
return __set_coerce(type(seq), result)
|
||||
|
||||
def intersect(seq1, seq2):
|
||||
result = []
|
||||
if type(seq1) != type(seq2) and type(seq2) == types.StringType: seq2 = list(seq2)
|
||||
for i in seq1:
|
||||
if i in seq2 and i not in result: result.append(i)
|
||||
return __set_coerce(type(seq1), result)
|
||||
|
||||
def union(seq1, seq2):
|
||||
if type(seq1) == type(seq2):
|
||||
return unique(seq1 + seq2)
|
||||
if type(seq1) == types.ListType or type(seq2) == types.ListType:
|
||||
return unique(list(seq1) + list(seq2))
|
||||
if type(seq1) == types.TupleType or type(seq2) == types.TupleType:
|
||||
return unique(tuple(seq1) + tuple(seq2))
|
||||
return unique(list(seq1) + list(seq2))
|
|
@ -1,256 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/styles.py
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
from reportlab.lib.colors import white, black
|
||||
from reportlab.lib.enums import TA_LEFT, TA_CENTER
|
||||
|
||||
###########################################################
|
||||
# This class provides an 'instance inheritance'
|
||||
# mechanism for its descendants, simpler than acquisition
|
||||
# but not as far-reaching
|
||||
###########################################################
|
||||
class PropertySet:
|
||||
defaults = {}
|
||||
|
||||
def __init__(self, name, parent=None, **kw):
|
||||
"""When initialized, it copies the class defaults;
|
||||
then takes a copy of the attributes of the parent
|
||||
if any. All the work is done in init - styles
|
||||
should cost little to use at runtime."""
|
||||
# step one - validate the hell out of it
|
||||
assert not self.defaults.has_key('name'), "Class Defaults may not contain a 'name' attribute"
|
||||
assert not self.defaults.has_key('parent'), "Class Defaults may not contain a 'parent' attribute"
|
||||
if parent:
|
||||
assert parent.__class__ == self.__class__, "Parent style must have same class as new style"
|
||||
|
||||
#step two
|
||||
self.name = name
|
||||
self.parent = parent
|
||||
self.__dict__.update(self.defaults)
|
||||
|
||||
#step two - copy from parent if any. Try to be
|
||||
# very strict that only keys in class defaults are
|
||||
# allowed, so they cannot inherit
|
||||
self.refresh()
|
||||
|
||||
#step three - copy keywords if any
|
||||
for (key, value) in kw.items():
|
||||
self.__dict__[key] = value
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s '%s'>" % (self.__class__.__name__, self.name)
|
||||
|
||||
def refresh(self):
|
||||
"""re-fetches attributes from the parent on demand;
|
||||
use if you have been hacking the styles. This is
|
||||
used by __init__"""
|
||||
if self.parent:
|
||||
for (key, value) in self.parent.__dict__.items():
|
||||
if (key not in ['name','parent']):
|
||||
self.__dict__[key] = value
|
||||
|
||||
|
||||
def listAttrs(self, indent=''):
|
||||
print indent + 'name =', self.name
|
||||
print indent + 'parent =', self.parent
|
||||
keylist = self.__dict__.keys()
|
||||
keylist.sort()
|
||||
keylist.remove('name')
|
||||
keylist.remove('parent')
|
||||
for key in keylist:
|
||||
value = self.__dict__.get(key, None)
|
||||
print indent + '%s = %s' % (key, value)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
class LineStyle(PropertySet):
|
||||
defaults = {
|
||||
'width':1,
|
||||
'color': black
|
||||
}
|
||||
def prepareCanvas(self, canvas):
|
||||
"""You can ask a LineStyle to set up the canvas for drawing
|
||||
the lines."""
|
||||
canvas.setLineWidth(1)
|
||||
#etc. etc.
|
||||
|
||||
class StyleSheet1:
|
||||
"""This may or may not be used. The idea is to
|
||||
1. slightly simplify construction of stylesheets;
|
||||
2. enforce rules to validate styles when added
|
||||
(e.g. we may choose to disallow having both
|
||||
'heading1' and 'Heading1' - actual rules are
|
||||
open to discussion);
|
||||
3. allow aliases and alternate style lookup
|
||||
mechanisms
|
||||
4. Have a place to hang style-manipulation
|
||||
methods (save, load, maybe support a GUI
|
||||
editor)
|
||||
Access is via getitem, so they can be
|
||||
compatible with plain old dictionaries.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.byName = {}
|
||||
self.byAlias = {}
|
||||
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return self.byAlias[key]
|
||||
except KeyError:
|
||||
try:
|
||||
return self.byName[key]
|
||||
except KeyError:
|
||||
raise KeyError, "Style '%s' not found in stylesheet" % key
|
||||
|
||||
def has_key(self, key):
|
||||
if self.byAlias.has_key(key):
|
||||
return 1
|
||||
elif self.byName.has_key(key):
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def add(self, style, alias=None):
|
||||
key = style.name
|
||||
if self.byName.has_key(key):
|
||||
raise KeyError, "Style '%s' already defined in stylesheet" % key
|
||||
if self.byAlias.has_key(key):
|
||||
raise KeyError, "Style name '%s' is already an alias in stylesheet" % key
|
||||
|
||||
if alias:
|
||||
if self.byName.has_key(alias):
|
||||
raise KeyError, "Style '%s' already defined in stylesheet" % alias
|
||||
if self.byAlias.has_key(alias):
|
||||
raise KeyError, "Alias name '%s' is already an alias in stylesheet" % alias
|
||||
#passed all tests? OK, add it
|
||||
self.byName[key] = style
|
||||
if alias:
|
||||
self.byAlias[alias] = style
|
||||
|
||||
def list(self):
|
||||
styles = self.byName.items()
|
||||
styles.sort()
|
||||
alii = {}
|
||||
for (alias, style) in self.byAlias.items():
|
||||
alii[style] = alias
|
||||
for (name, style) in styles:
|
||||
alias = alii.get(style, None)
|
||||
print name, alias
|
||||
style.listAttrs(' ')
|
||||
print
|
||||
|
||||
|
||||
|
||||
|
||||
def testStyles():
|
||||
pNormal = ParagraphStyle('Normal',None)
|
||||
pNormal.fontName = 'Times-Roman'
|
||||
pNormal.fontSize = 12
|
||||
pNormal.leading = 14.4
|
||||
|
||||
pNormal.listAttrs()
|
||||
print
|
||||
pPre = ParagraphStyle('Literal', pNormal)
|
||||
pPre.fontName = 'Courier'
|
||||
pPre.listAttrs()
|
||||
return pNormal, pPre
|
||||
|
||||
def getSampleStyleSheet():
|
||||
"""Returns a stylesheet object"""
|
||||
stylesheet = StyleSheet1()
|
||||
|
||||
stylesheet.add(ParagraphStyle(name='Normal',
|
||||
fontName='Times-Roman',
|
||||
fontSize=10,
|
||||
leading=12)
|
||||
)
|
||||
|
||||
stylesheet.add(ParagraphStyle(name='BodyText',
|
||||
parent=stylesheet['Normal'],
|
||||
spaceBefore=6)
|
||||
)
|
||||
stylesheet.add(ParagraphStyle(name='Italic',
|
||||
parent=stylesheet['BodyText'],
|
||||
fontName = 'Times-Italic')
|
||||
)
|
||||
|
||||
stylesheet.add(ParagraphStyle(name='Heading1',
|
||||
parent=stylesheet['Normal'],
|
||||
fontName = 'Times-Bold',
|
||||
fontSize=18,
|
||||
leading=22,
|
||||
spaceAfter=6),
|
||||
alias='h1')
|
||||
|
||||
stylesheet.add(ParagraphStyle(name='Title',
|
||||
parent=stylesheet['Normal'],
|
||||
fontName = 'Times-Bold',
|
||||
fontSize=18,
|
||||
leading=22,
|
||||
alignment=TA_CENTER,
|
||||
spaceAfter=6),
|
||||
alias='title')
|
||||
|
||||
stylesheet.add(ParagraphStyle(name='Heading2',
|
||||
parent=stylesheet['Normal'],
|
||||
fontName = 'Times-Bold',
|
||||
fontSize=14,
|
||||
leading=18,
|
||||
spaceBefore=12,
|
||||
spaceAfter=6),
|
||||
alias='h2')
|
||||
|
||||
stylesheet.add(ParagraphStyle(name='Heading3',
|
||||
parent=stylesheet['Normal'],
|
||||
fontName = 'Times-BoldItalic',
|
||||
fontSize=12,
|
||||
leading=14,
|
||||
spaceBefore=12,
|
||||
spaceAfter=6),
|
||||
alias='h3')
|
||||
|
||||
stylesheet.add(ParagraphStyle(name='Bullet',
|
||||
parent=stylesheet['Normal'],
|
||||
firstLineIndent=0,
|
||||
spaceBefore=3),
|
||||
alias='bu')
|
||||
|
||||
stylesheet.add(ParagraphStyle(name='Definition',
|
||||
parent=stylesheet['Normal'],
|
||||
firstLineIndent=0,
|
||||
leftIndent=36,
|
||||
bulletIndent=0,
|
||||
spaceBefore=6,
|
||||
bulletFontName='Times-BoldItalic'),
|
||||
alias='df')
|
||||
|
||||
stylesheet.add(ParagraphStyle(name='Code',
|
||||
parent=stylesheet['Normal'],
|
||||
fontName='Courier',
|
||||
fontSize=8,
|
||||
leading=8.8,
|
||||
firstLineIndent=0,
|
||||
leftIndent=36))
|
||||
|
||||
|
||||
return stylesheet
|
|
@ -1,294 +0,0 @@
|
|||
# Tables of Contents and Indices
|
||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/tocindex.py
|
||||
__version__=''' $Id$ '''
|
||||
__doc__=''
|
||||
"""
|
||||
This module will contain standard Table of Contents and Index objects.
|
||||
under development, and pending certain hooks adding in DocTemplate
|
||||
As of today, it onyl handles the formatting aspect of TOCs
|
||||
"""
|
||||
|
||||
import string
|
||||
|
||||
from reportlab.platypus import Flowable, BaseDocTemplate, Paragraph, \
|
||||
PageBreak, Frame, PageTemplate, NextPageTemplate
|
||||
from reportlab.platypus.doctemplate import IndexingFlowable
|
||||
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
|
||||
from reportlab.platypus import tables
|
||||
from reportlab.lib import enums
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.units import inch, cm
|
||||
from reportlab.rl_config import defaultPageSize
|
||||
|
||||
##############################################################
|
||||
#
|
||||
# we first define a paragraph style for each level of the
|
||||
# table, and one for the table as whole; you can supply your
|
||||
# own.
|
||||
#
|
||||
##############################################################
|
||||
|
||||
|
||||
levelZeroParaStyle = ParagraphStyle(name='LevelZero',
|
||||
fontName='Times-Roman',
|
||||
fontSize=10,
|
||||
leading=12)
|
||||
levelOneParaStyle = ParagraphStyle(name='LevelOne',
|
||||
parent = levelZeroParaStyle,
|
||||
firstLineIndent = 0,
|
||||
leftIndent = 18)
|
||||
levelTwoParaStyle = ParagraphStyle(name='LevelTwo',
|
||||
parent = levelOneParaStyle,
|
||||
firstLineIndent = 0,
|
||||
leftIndent = 36)
|
||||
levelThreeParaStyle = ParagraphStyle(name='LevelThree',
|
||||
parent = levelTwoParaStyle,
|
||||
firstLineIndent = 0,
|
||||
leftIndent = 54)
|
||||
|
||||
defaultTableStyle = tables.TableStyle([
|
||||
('VALIGN',(0,0),(-1,-1),'TOP'),
|
||||
('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
|
||||
('BOX', (0,0), (-1,-1), 0.25, colors.black),
|
||||
])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TableOfContents0(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)."""
|
||||
def __init__(self):
|
||||
self.entries = []
|
||||
self.rightColumnWidth = 72
|
||||
self.levelStyles = [levelZeroParaStyle,
|
||||
levelOneParaStyle,
|
||||
levelTwoParaStyle,
|
||||
levelThreeParaStyle]
|
||||
self.tableStyle = defaultTableStyle
|
||||
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):
|
||||
"""DocTemplate framework can call this with all kinds
|
||||
of events; we say we are interested in 'TOCEntry'
|
||||
events."""
|
||||
if kind == 'TOCEntry':
|
||||
(level, text, pageNum) = stuff
|
||||
self.addEntry(level, text, pageNum)
|
||||
#print 'TOC notified of ', stuff
|
||||
## elif kind == 'debug':
|
||||
## # hack to watch its state
|
||||
## import pprint
|
||||
## print 'Last Entries first 5:'
|
||||
## for (level, text, pageNum) in self._lastEntries[0:5]:
|
||||
## print (level, text[0:30], pageNum),
|
||||
## print
|
||||
## print 'Current Entries first 5:'
|
||||
## for (level, text, pageNum) in self._lastEntries[0:5]:
|
||||
## print (level, text[0:30], pageNum),
|
||||
|
||||
|
||||
def clearEntries(self):
|
||||
self._entries = []
|
||||
|
||||
def addEntry(self, level, text, pageNum):
|
||||
"""Adds one entry; allows incremental buildup by a doctemplate.
|
||||
Requires that enough styles are defined."""
|
||||
assert type(level) == type(1), "Level must be an integer"
|
||||
assert level < len(self.levelStyles), \
|
||||
"Table of contents must have a style defined " \
|
||||
"for paragraph level %d before you add an entry" % level
|
||||
self._entries.append((level, text, pageNum))
|
||||
|
||||
def addEntries(self, listOfEntries):
|
||||
"""Bulk creation. If you knew the titles but
|
||||
not the page numbers, you could supply them to
|
||||
get sensible output on the first run."""
|
||||
for (level, text, pageNum) in listOfEntries:
|
||||
self.addEntry(level, text, pageNum)
|
||||
|
||||
def wrap(self, availWidth, availHeight):
|
||||
"""All table properties should be known by now."""
|
||||
widths = (availWidth - self.rightColumnWidth,
|
||||
self.rightColumnWidth)
|
||||
|
||||
# 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)]
|
||||
else:
|
||||
_tempEntries = self._lastEntries
|
||||
tableData = []
|
||||
for (level, text, pageNum) in _tempEntries:
|
||||
leftColStyle = self.levelStyles[level]
|
||||
#right col style is right aligned
|
||||
rightColStyle = ParagraphStyle(name='leftColLevel%d' % level,
|
||||
parent=leftColStyle,
|
||||
leftIndent=0,
|
||||
alignment=enums.TA_RIGHT)
|
||||
leftPara = Paragraph(text, leftColStyle)
|
||||
rightPara = Paragraph(str(pageNum), rightColStyle)
|
||||
tableData.append([leftPara, rightPara])
|
||||
self._table = tables.Table(tableData, colWidths=widths,
|
||||
style=self.tableStyle)
|
||||
self.width, self.height = self._table.wrap(availWidth, availHeight)
|
||||
return (self.width, self.height)
|
||||
|
||||
def split(self, availWidth, availHeight):
|
||||
"""At this stage we do not care about splitting the entries,
|
||||
we wil 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.split(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)
|
||||
|
||||
|
||||
|
||||
#################################################################################
|
||||
#
|
||||
# everything from here down is concerned with creating a good example document
|
||||
# i.e. test code as well as tutorial
|
||||
PAGE_HEIGHT = defaultPageSize[1]
|
||||
def getSampleTOCData(depth=3):
|
||||
"""Returns a longish block of page numbers and headings over 3 levels"""
|
||||
from random import randint
|
||||
pgNum = 2
|
||||
data = []
|
||||
for chapter in range(1,8):
|
||||
data.append((0, """Chapter %d with a really long name which will hopefully
|
||||
wrap onto a second line, fnding out if the right things happen with
|
||||
full paragraphs n the table of contents""" % chapter, pgNum))
|
||||
pgNum = pgNum + randint(0,2)
|
||||
if depth > 1:
|
||||
for section in range(1,5):
|
||||
data.append((1, 'Chapter %d Section %d' % (chapter, section), pgNum))
|
||||
pgNum = pgNum + randint(0,2)
|
||||
if depth > 2:
|
||||
for subSection in range(1,6):
|
||||
data.append(2, 'Chapter %d Section %d Subsection %d' %
|
||||
(chapter, section, subSection),
|
||||
pgNum)
|
||||
pgNum = pgNum + randint(0,1)
|
||||
from pprint import pprint as pp
|
||||
pp(data)
|
||||
return data
|
||||
|
||||
|
||||
def getSampleStory(depth=3):
|
||||
"""Makes a story with lots of paragraphs. Uses the random
|
||||
TOC data and makes paragraphs to correspond to each."""
|
||||
from reportlab.platypus.doctemplate import randomText
|
||||
from random import randint
|
||||
|
||||
styles = getSampleStyleSheet()
|
||||
TOCData = getSampleTOCData(depth)
|
||||
|
||||
story = [Paragraph("This is a demo of the table of contents object",
|
||||
styles['Heading1'])]
|
||||
|
||||
toc = TableOfContents0() # empty on first pass
|
||||
#toc.addEntries(TOCData) # init with random page numbers
|
||||
story.append(toc)
|
||||
|
||||
# the next full page should switch to internal page style
|
||||
story.append(NextPageTemplate("Body"))
|
||||
|
||||
# now make some paragraphs to correspond to it
|
||||
for (level, text, pageNum) in TOCData:
|
||||
if level == 0:
|
||||
#page break before chapter
|
||||
story.append(PageBreak())
|
||||
|
||||
headingStyle = (styles['Heading1'], styles['Heading2'], styles['Heading3'])[level]
|
||||
headingPara = Paragraph(text, headingStyle)
|
||||
story.append(headingPara)
|
||||
# now make some body text
|
||||
for i in range(randint(1,6)):
|
||||
bodyPara = Paragraph(randomText(),
|
||||
styles['Normal'])
|
||||
story.append(bodyPara)
|
||||
|
||||
return story
|
||||
|
||||
class MyDocTemplate(BaseDocTemplate):
|
||||
"""Example of how to do the indexing. Need the onDraw hook
|
||||
to find out which things are drawn on which pages"""
|
||||
def afterInit(self):
|
||||
"""Set up the page templates"""
|
||||
frameT = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='normal')
|
||||
self.addPageTemplates([PageTemplate(id='Front',frames=frameT),
|
||||
PageTemplate(id='Body',frames=frameT)
|
||||
])
|
||||
# just need a unique key generator for outline entries;
|
||||
# easiest is to count all flowables in afterFlowable
|
||||
# and set up a counter variable here
|
||||
self._uniqueKey = 0
|
||||
|
||||
|
||||
def afterFlowable(self, flowable):
|
||||
"""Our rule for the table of contents is simply to take
|
||||
the text of H1, H2 and H3 elements. We broadcast a
|
||||
notification to the DocTemplate, which should inform
|
||||
the TOC and let it pull them out. Also build an outline"""
|
||||
self._uniqueKey = self._uniqueKey + 1
|
||||
|
||||
if hasattr(flowable, 'style'):
|
||||
if flowable.style.name == 'Heading1':
|
||||
self.notify('TOCEntry', (0, flowable.getPlainText(), self.page))
|
||||
self.canv.bookmarkPage(str(self._uniqueKey))
|
||||
self.canv.addOutlineEntry(flowable.getPlainText()[0:10], str(self._uniqueKey), 0)
|
||||
|
||||
elif flowable.style.name == 'Heading2':
|
||||
self.notify('TOCEntry', (1, flowable.getPlainText(), self.page))
|
||||
self.canv.bookmarkPage(str(self._uniqueKey))
|
||||
self.canv.addOutlineEntry(flowable.getPlainText(), str(self._uniqueKey), 1)
|
||||
|
||||
elif flowable.style.name == 'Heading3':
|
||||
self.notify('TOCEntry', (2, flowable.getPlainText(), self.page))
|
||||
self.canv.bookmarkPage(str(self._uniqueKey))
|
||||
self.canv.addOutlineEntry(flowable.getPlainText(), str(self._uniqueKey), 2)
|
||||
|
||||
def beforePage(self):
|
||||
"""decorate the page"""
|
||||
self.canv.saveState()
|
||||
self.canv.setStrokeColor(colors.red)
|
||||
self.canv.setLineWidth(5)
|
||||
self.canv.line(66,72,66,PAGE_HEIGHT-72)
|
||||
self.canv.setFont('Times-Roman',12)
|
||||
self.canv.drawString(4 * inch, 0.75 * inch, "Page %d" % doc.page)
|
||||
self.canv.restoreState()
|
||||
|
||||
if __name__=='__main__':
|
||||
from reportlab.platypus import SimpleDocTemplate
|
||||
doc = MyDocTemplate('tocindex.pdf')
|
||||
|
||||
#change this to depth=3 for a BIG document
|
||||
story = getSampleStory(depth=2)
|
||||
|
||||
doc.multiBuild(story, 'tocindex.pdf')
|
|
@ -1,23 +0,0 @@
|
|||
#! /usr/bin/python2.3
|
||||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/units.py
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
inch = 72.0
|
||||
cm = inch / 2.54
|
||||
mm = cm * 0.1
|
||||
pica = 12.0
|
||||
|
||||
def toLength(s):
|
||||
'''convert a string to a length'''
|
||||
try:
|
||||
if s[-2:]=='cm': return float(s[:-2])*cm
|
||||
if s[-2:]=='in': return float(s[:-2])*inch
|
||||
if s[-2:]=='pt': return float(s[:-2])
|
||||
if s[-1:]=='i': return float(s[:-1])*inch
|
||||
if s[-2:]=='mm': return float(s[:-2])*mm
|
||||
if s[-4:]=='pica': return float(s[:-2])*pica
|
||||
return float(s)
|
||||
except:
|
||||
raise ValueError, "Can't convert '%s' to length" % s
|
|
@ -1,776 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/utils.py
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
import string, os, sys, imp
|
||||
from types import *
|
||||
from reportlab.lib.logger import warnOnce
|
||||
SeqTypes = (ListType,TupleType)
|
||||
if sys.hexversion<0x2030000:
|
||||
True = 1
|
||||
False = 0
|
||||
|
||||
def _findFiles(dirList,ext='.ttf'):
|
||||
from os.path import isfile, isdir, join as path_join
|
||||
from os import listdir
|
||||
ext = ext.lower()
|
||||
R = []
|
||||
A = R.append
|
||||
for D in dirList:
|
||||
if not isdir(D): continue
|
||||
for fn in listdir(D):
|
||||
fn = path_join(D,fn)
|
||||
if isfile(fn) and (not ext or fn.lower().endswith(ext)): A(fn)
|
||||
return R
|
||||
|
||||
try:
|
||||
_UserDict = dict
|
||||
except:
|
||||
from UserDict import UserDict as _UserDict
|
||||
|
||||
class CIDict(_UserDict):
|
||||
def __init__(self,*a,**kw):
|
||||
map(self.update, a)
|
||||
self.update(kw)
|
||||
|
||||
def update(self,D):
|
||||
for k,v in D.items(): self[k] = v
|
||||
|
||||
def __setitem__(self,k,v):
|
||||
try:
|
||||
k = k.lower()
|
||||
except:
|
||||
pass
|
||||
_UserDict.__setitem__(self,k,v)
|
||||
|
||||
def __getitem__(self,k):
|
||||
try:
|
||||
k = k.lower()
|
||||
except:
|
||||
pass
|
||||
return _UserDict.__getitem__(self,k)
|
||||
|
||||
def __delitem__(self,k):
|
||||
try:
|
||||
k = k.lower()
|
||||
except:
|
||||
pass
|
||||
return _UserDict.__delitem__(self,k)
|
||||
|
||||
def get(self,k,dv=None):
|
||||
try:
|
||||
return self[k]
|
||||
except KeyError:
|
||||
return dv
|
||||
|
||||
def has_key(self,k):
|
||||
try:
|
||||
self[k]
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def pop(self,k,*a):
|
||||
try:
|
||||
k = k.lower()
|
||||
except:
|
||||
pass
|
||||
return _UserDict.pop(*((self,k)+a))
|
||||
|
||||
def setdefault(self,k,*a):
|
||||
try:
|
||||
k = k.lower()
|
||||
except:
|
||||
pass
|
||||
return _UserDict.setdefault(*((self,k)+a))
|
||||
|
||||
if os.name == 'mac':
|
||||
#with the Mac, we need to tag the file in a special
|
||||
#way so the system knows it is a PDF file.
|
||||
#This supplied by Joe Strout
|
||||
import macfs, macostools
|
||||
_KNOWN_MAC_EXT = {
|
||||
'BMP' : ('ogle','BMP '),
|
||||
'EPS' : ('ogle','EPSF'),
|
||||
'EPSF': ('ogle','EPSF'),
|
||||
'GIF' : ('ogle','GIFf'),
|
||||
'JPG' : ('ogle','JPEG'),
|
||||
'JPEG': ('ogle','JPEG'),
|
||||
'PCT' : ('ttxt','PICT'),
|
||||
'PICT': ('ttxt','PICT'),
|
||||
'PNG' : ('ogle','PNGf'),
|
||||
'PPM' : ('ogle','.PPM'),
|
||||
'TIF' : ('ogle','TIFF'),
|
||||
'TIFF': ('ogle','TIFF'),
|
||||
'PDF' : ('CARO','PDF '),
|
||||
'HTML': ('MSIE','TEXT'),
|
||||
}
|
||||
def markfilename(filename,creatorcode=None,filetype=None,ext='PDF'):
|
||||
try:
|
||||
if creatorcode is None or filetype is None and ext is not None:
|
||||
try:
|
||||
creatorcode, filetype = _KNOWN_MAC_EXT[string.upper(ext)]
|
||||
except:
|
||||
return
|
||||
macfs.FSSpec(filename).SetCreatorType(creatorcode,filetype)
|
||||
macostools.touched(filename)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
def markfilename(filename,creatorcode=None,filetype=None):
|
||||
pass
|
||||
|
||||
import reportlab
|
||||
__RL_DIR=os.path.dirname(reportlab.__file__) #possibly relative
|
||||
_RL_DIR=os.path.isabs(__RL_DIR) and __RL_DIR or os.path.abspath(__RL_DIR)
|
||||
del reportlab
|
||||
|
||||
#Attempt to detect if this copy of reportlab is running in a
|
||||
#file system (as opposed to mostly running in a zip or McMillan
|
||||
#archive or Jar file). This is used by test cases, so that
|
||||
#we can write test cases that don't get activated in a compiled
|
||||
try:
|
||||
__file__
|
||||
except:
|
||||
__file__ = sys.argv[0]
|
||||
import glob, fnmatch
|
||||
try:
|
||||
_isFSD = not __loader__
|
||||
_archive = __loader__.archive
|
||||
_archivepfx = _archive + os.sep
|
||||
_archivedir = os.path.dirname(__loader__.archive)
|
||||
_archivedirpfx = _archivedir + os.sep
|
||||
_archivepfxlen = len(_archivepfx)
|
||||
_archivedirpfxlen = len(_archivedirpfx)
|
||||
def __startswith_rl(fn,
|
||||
_archivepfx=os.path.normcase(_archivepfx),
|
||||
_archivedirpfx=os.path.normcase(_archivedirpfx),
|
||||
_archive=os.path.normcase(_archive),
|
||||
_archivedir=os.path.normcase(_archivedir),
|
||||
os_path_normpath=os.path.normpath,
|
||||
os_path_normcase=os.path.normcase,
|
||||
os_getcwd=os.getcwd,
|
||||
os_sep=os.sep,
|
||||
os_sep_len = len(os.sep)):
|
||||
'''if the name starts with a known prefix strip it off'''
|
||||
fn = os_path_normpath(fn.replace('/',os_sep))
|
||||
nfn = os_path_normcase(fn)
|
||||
if nfn in (_archivedir,_archive): return 1,''
|
||||
if nfn.startswith(_archivepfx): return 1,fn[_archivepfxlen:]
|
||||
if nfn.startswith(_archivedirpfx): return 1,fn[_archivedirpfxlen:]
|
||||
cwd = os_path_normcase(os_getcwd())
|
||||
n = len(cwd)
|
||||
if nfn.startswith(cwd):
|
||||
if fn[n:].startswith(os_sep): return 1, fn[n+os_sep_len:]
|
||||
if n==len(fn): return 1,''
|
||||
return not os.path.isabs(fn),fn
|
||||
|
||||
def _startswith_rl(fn):
|
||||
return __startswith_rl(fn)[1]
|
||||
|
||||
def rl_glob(pattern,glob=glob.glob,fnmatch=fnmatch.fnmatch, _RL_DIR=_RL_DIR,pjoin=os.path.join):
|
||||
c, pfn = __startswith_rl(pattern)
|
||||
r = glob(pfn)
|
||||
if c or r==[]:
|
||||
r += map(lambda x,D=_archivepfx,pjoin=pjoin: pjoin(_archivepfx,x),filter(lambda x,pfn=pfn,fnmatch=fnmatch: fnmatch(x,pfn),__loader__._files.keys()))
|
||||
return r
|
||||
except:
|
||||
_isFSD = os.path.isfile(__file__) #slight risk of wrong path
|
||||
__loader__ = None
|
||||
def _startswith_rl(fn):
|
||||
return fn
|
||||
def rl_glob(pattern,glob=glob.glob):
|
||||
return glob(pattern)
|
||||
del glob, fnmatch
|
||||
_isFSSD = _isFSD and os.path.isfile(os.path.splitext(__file__)[0] +'.py')
|
||||
|
||||
def isFileSystemDistro():
|
||||
'''return truth if a file system distribution'''
|
||||
return _isFSD
|
||||
|
||||
def isCompactDistro():
|
||||
'''return truth if not a file system distribution'''
|
||||
return not _isFSD
|
||||
|
||||
def isSourceDistro():
|
||||
'''return truth if a source file system distribution'''
|
||||
return _isFSSD
|
||||
|
||||
try:
|
||||
#raise ImportError
|
||||
### NOTE! FP_STR SHOULD PROBABLY ALWAYS DO A PYTHON STR() CONVERSION ON ARGS
|
||||
### IN CASE THEY ARE "LAZY OBJECTS". ACCELLERATOR DOESN'T DO THIS (YET)
|
||||
try:
|
||||
from _rl_accel import fp_str # in case of builtin version
|
||||
except ImportError:
|
||||
from reportlab.lib._rl_accel import fp_str # specific
|
||||
except ImportError:
|
||||
from math import log
|
||||
_log_10 = lambda x,log=log,_log_e_10=log(10.0): log(x)/_log_e_10
|
||||
_fp_fmts = "%.0f", "%.1f", "%.2f", "%.3f", "%.4f", "%.5f", "%.6f"
|
||||
import re
|
||||
_tz_re = re.compile('0+$')
|
||||
del re
|
||||
def fp_str(*a):
|
||||
if len(a)==1 and type(a[0]) in SeqTypes: a = a[0]
|
||||
s = []
|
||||
A = s.append
|
||||
for i in a:
|
||||
sa =abs(i)
|
||||
if sa<=1e-7: A('0')
|
||||
else:
|
||||
l = sa<=1 and 6 or min(max(0,(6-int(_log_10(sa)))),6)
|
||||
n = _fp_fmts[l]%i
|
||||
if l:
|
||||
n = _tz_re.sub('',n)
|
||||
try:
|
||||
if n[-1]=='.': n = n[:-1]
|
||||
except:
|
||||
print i, n
|
||||
raise
|
||||
A((n[0]!='0' or len(n)==1) and n or n[1:])
|
||||
return string.join(s)
|
||||
|
||||
#hack test for comma users
|
||||
if ',' in fp_str(0.25):
|
||||
_FP_STR = fp_str
|
||||
def fp_str(*a):
|
||||
return string.replace(apply(_FP_STR,a),',','.')
|
||||
|
||||
_rl_tempdir=None
|
||||
def get_rl_tempdir(*subdirs):
|
||||
global _rl_tempdir
|
||||
if _rl_tempdir is None:
|
||||
import tempfile
|
||||
_rl_tempdir = os.path.join(tempfile.gettempdir(),'ReportLab_tmp%s' % (sys.platform=='unix' and `os.getuid()` or ''))
|
||||
d = _rl_tempdir
|
||||
if subdirs: d = os.path.join(*((d,)+subdirs))
|
||||
try:
|
||||
os.makedirs(d)
|
||||
except:
|
||||
pass
|
||||
return d
|
||||
|
||||
def get_rl_tempfile(fn=None):
|
||||
if not fn:
|
||||
import tempfile
|
||||
fn = tempfile.mktemp()
|
||||
return os.path.join(get_rl_tempdir(),fn)
|
||||
|
||||
def recursiveImport(modulename, baseDir=None, noCWD=0, debug=0):
|
||||
"""Dynamically imports possible packagized module, or raises ImportError"""
|
||||
normalize = lambda x: os.path.normcase(os.path.abspath(os.path.normpath(x)))
|
||||
path = map(normalize,sys.path)
|
||||
if baseDir:
|
||||
if type(baseDir) not in SeqTypes:
|
||||
tp = [baseDir]
|
||||
else:
|
||||
tp = filter(None,list(baseDir))
|
||||
for p in tp:
|
||||
p = normalize(p)
|
||||
if p not in path: path.insert(0,p)
|
||||
|
||||
if noCWD:
|
||||
for p in ('','.',normalize('.')):
|
||||
while p in path:
|
||||
if debug: print 'removed "%s" from path' % p
|
||||
path.remove(p)
|
||||
elif '.' not in path:
|
||||
path.insert(0,'.')
|
||||
|
||||
if debug:
|
||||
import pprint
|
||||
pp = pprint.pprint
|
||||
print 'path=',
|
||||
pp(path)
|
||||
|
||||
#make import errors a bit more informative
|
||||
opath = sys.path
|
||||
try:
|
||||
sys.path = path
|
||||
exec 'import %s\nm = %s\n' % (modulename,modulename) in locals()
|
||||
sys.path = opath
|
||||
return m
|
||||
except ImportError:
|
||||
sys.path = opath
|
||||
msg = "recursiveimport(%s,baseDir=%s) failed" % (modulename,baseDir)
|
||||
if baseDir:
|
||||
msg = msg + " under paths '%s'" % `path`
|
||||
raise ImportError, msg
|
||||
|
||||
def recursiveGetAttr(obj, name):
|
||||
"Can call down into e.g. object1.object2[4].attr"
|
||||
return eval(name, obj.__dict__)
|
||||
|
||||
def recursiveSetAttr(obj, name, value):
|
||||
"Can call down into e.g. object1.object2[4].attr = value"
|
||||
#get the thing above last.
|
||||
tokens = string.split(name, '.')
|
||||
if len(tokens) == 1:
|
||||
setattr(obj, name, value)
|
||||
else:
|
||||
most = string.join(tokens[:-1], '.')
|
||||
last = tokens[-1]
|
||||
parent = recursiveGetAttr(obj, most)
|
||||
setattr(parent, last, value)
|
||||
|
||||
def import_zlib():
|
||||
try:
|
||||
import zlib
|
||||
except ImportError:
|
||||
zlib = None
|
||||
from reportlab.rl_config import ZLIB_WARNINGS
|
||||
if ZLIB_WARNINGS: warnOnce('zlib not available')
|
||||
return zlib
|
||||
|
||||
|
||||
# Image Capability Detection. Set a flag haveImages
|
||||
# to tell us if either PIL or Java imaging libraries present.
|
||||
# define PIL_Image as either None, or an alias for the PIL.Image
|
||||
# module, as there are 2 ways to import it
|
||||
|
||||
if sys.platform[0:4] == 'java':
|
||||
try:
|
||||
import javax.imageio
|
||||
import java.awt.image
|
||||
haveImages = 1
|
||||
except:
|
||||
haveImages = 0
|
||||
else:
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
try:
|
||||
import Image
|
||||
except ImportError:
|
||||
Image = None
|
||||
haveImages = Image is not None
|
||||
if haveImages: del Image
|
||||
|
||||
|
||||
__StringIO=None
|
||||
def getStringIO(buf=None):
|
||||
'''unified StringIO instance interface'''
|
||||
global __StringIO
|
||||
if not __StringIO:
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
__StringIO = StringIO
|
||||
return buf is not None and __StringIO(buf) or __StringIO()
|
||||
|
||||
class ArgvDictValue:
|
||||
'''A type to allow clients of getArgvDict to specify a conversion function'''
|
||||
def __init__(self,value,func):
|
||||
self.value = value
|
||||
self.func = func
|
||||
|
||||
def getArgvDict(**kw):
|
||||
''' Builds a dictionary from its keyword arguments with overrides from sys.argv.
|
||||
Attempts to be smart about conversions, but the value can be an instance
|
||||
of ArgDictValue to allow specifying a conversion function.
|
||||
'''
|
||||
def handleValue(v,av,func):
|
||||
if func:
|
||||
v = func(av)
|
||||
else:
|
||||
t = type(v)
|
||||
if t is StringType:
|
||||
v = av
|
||||
elif t is FloatType:
|
||||
v = float(av)
|
||||
elif t is IntType:
|
||||
v = int(av)
|
||||
elif t is ListType:
|
||||
v = list(eval(av))
|
||||
elif t is TupleType:
|
||||
v = tuple(eval(av))
|
||||
else:
|
||||
raise TypeError, "Can't convert string '%s' to %s" % (av,str(t))
|
||||
return v
|
||||
|
||||
A = sys.argv[1:]
|
||||
R = {}
|
||||
for k, v in kw.items():
|
||||
if isinstance(v,ArgvDictValue):
|
||||
v, func = v.value, v.func
|
||||
else:
|
||||
func = None
|
||||
handled = 0
|
||||
ke = k+'='
|
||||
for a in A:
|
||||
if string.find(a,ke)==0:
|
||||
av = a[len(ke):]
|
||||
A.remove(a)
|
||||
R[k] = handleValue(v,av,func)
|
||||
handled = 1
|
||||
break
|
||||
|
||||
if not handled: R[k] = handleValue(v,v,func)
|
||||
|
||||
return R
|
||||
|
||||
def getHyphenater(hDict=None):
|
||||
try:
|
||||
from reportlab.lib.pyHnj import Hyphen
|
||||
if hDict is None: hDict=os.path.join(os.path.dirname(__file__),'hyphen.mashed')
|
||||
return Hyphen(hDict)
|
||||
except ImportError, errMsg:
|
||||
if str(errMsg)!='No module named pyHnj': raise
|
||||
return None
|
||||
|
||||
def _className(self):
|
||||
'''Return a shortened class name'''
|
||||
try:
|
||||
name = self.__class__.__name__
|
||||
i=string.rfind(name,'.')
|
||||
if i>=0: return name[i+1:]
|
||||
return name
|
||||
except AttributeError:
|
||||
return str(self)
|
||||
|
||||
def open_for_read_by_name(name,mode='b'):
|
||||
if 'r' not in mode: mode = 'r'+mode
|
||||
try:
|
||||
return open(name,mode)
|
||||
except IOError:
|
||||
if _isFSD or __loader__ is None: raise
|
||||
#we have a __loader__, perhaps the filename starts with
|
||||
#the dirname(reportlab.__file__) or is relative
|
||||
name = _startswith_rl(name)
|
||||
s = __loader__.get_data(name)
|
||||
if 'b' not in mode and os.linesep!='\n': s = s.replace(os.linesep,'\n')
|
||||
return getStringIO(s)
|
||||
|
||||
import urllib
|
||||
def open_for_read(name,mode='b', urlopen=urllib.urlopen):
|
||||
'''attempt to open a file or URL for reading'''
|
||||
if hasattr(name,'read'): return name
|
||||
try:
|
||||
return open_for_read_by_name(name,mode)
|
||||
except:
|
||||
try:
|
||||
return getStringIO(urlopen(name).read())
|
||||
except:
|
||||
raise IOError('Cannot open resource "%s"' % name)
|
||||
del urllib
|
||||
|
||||
def open_and_read(name,mode='b'):
|
||||
return open_for_read(name,mode).read()
|
||||
|
||||
def open_and_readlines(name,mode='t'):
|
||||
return open_and_read(name,mode).split('\n')
|
||||
|
||||
def rl_isfile(fn,os_path_isfile=os.path.isfile):
|
||||
if hasattr(fn,'read'): return True
|
||||
if os_path_isfile(fn): return True
|
||||
if _isFSD or __loader__ is None: return False
|
||||
fn = _startswith_rl(fn)
|
||||
return fn in __loader__._files.keys()
|
||||
|
||||
def rl_isdir(pn,os_path_isdir=os.path.isdir,os_path_normpath=os.path.normpath):
|
||||
if os_path_isdir(pn): return True
|
||||
if _isFSD or __loader__ is None: return False
|
||||
pn = _startswith_rl(os_path_normpath(pn))
|
||||
if not pn.endswith(os.sep): pn += os.sep
|
||||
return len(filter(lambda x,pn=pn: x.startswith(pn),__loader__._files.keys()))>0
|
||||
|
||||
def rl_get_module(name,dir):
|
||||
if sys.modules.has_key(name):
|
||||
om = sys.modules[name]
|
||||
del sys.modules[name]
|
||||
else:
|
||||
om = None
|
||||
try:
|
||||
f = None
|
||||
try:
|
||||
f, p, desc= imp.find_module(name,[dir])
|
||||
return imp.load_module(name,f,p,desc)
|
||||
except:
|
||||
if isCompactDistro():
|
||||
#attempt a load from inside the zip archive
|
||||
import zipimport
|
||||
dir = _startswith_rl(dir)
|
||||
dir = (dir=='.' or not dir) and _archive or os.path.join(_archive,dir.replace('/',os.sep))
|
||||
zi = zipimport.zipimporter(dir)
|
||||
return zi.load_module(name)
|
||||
raise ImportError('%s[%s]' % (name,dir))
|
||||
finally:
|
||||
if om: sys.modules[name] = om
|
||||
del om
|
||||
if f: f.close()
|
||||
|
||||
class ImageReader:
|
||||
"Wraps up either PIL or Java to get data from bitmaps"
|
||||
def __init__(self, fileName):
|
||||
if not haveImages:
|
||||
warnOnce('Imaging Library not available, unable to import bitmaps')
|
||||
return
|
||||
#start wih lots of null private fields, to be populated by
|
||||
#the relevant engine.
|
||||
self.fileName = fileName
|
||||
self._image = None
|
||||
self._width = None
|
||||
self._height = None
|
||||
self._transparent = None
|
||||
self._data = None
|
||||
self.fp = open_for_read(fileName,'b')
|
||||
|
||||
#detect which library we are using and open the image
|
||||
if sys.platform[0:4] == 'java':
|
||||
from javax.imageio import ImageIO
|
||||
self._image = ImageIO.read(self.fp)
|
||||
else:
|
||||
import PIL.Image
|
||||
self._image = PIL.Image.open(self.fp)
|
||||
|
||||
def getSize(self):
|
||||
if (self._width is None or self._height is None):
|
||||
if sys.platform[0:4] == 'java':
|
||||
self._width = self._image.getWidth()
|
||||
self._height = self._image.getHeight()
|
||||
else:
|
||||
self._width, self._height = self._image.size
|
||||
return (self._width, self._height)
|
||||
|
||||
def getRGBData(self):
|
||||
"Return byte array of RGB data as string"
|
||||
if self._data is None:
|
||||
if sys.platform[0:4] == 'java':
|
||||
import jarray
|
||||
from java.awt.image import PixelGrabber
|
||||
width, height = self.getSize()
|
||||
buffer = jarray.zeros(width*height, 'i')
|
||||
pg = PixelGrabber(self._image, 0,0,width,height,buffer,0,width)
|
||||
pg.grabPixels()
|
||||
# there must be a way to do this with a cast not a byte-level loop,
|
||||
# I just haven't found it yet...
|
||||
pixels = []
|
||||
a = pixels.append
|
||||
for i in range(len(buffer)):
|
||||
rgb = buffer[i]
|
||||
a(chr((rgb>>16)&0xff))
|
||||
a(chr((rgb>>8)&0xff))
|
||||
a(chr(rgb&0xff))
|
||||
self._data = ''.join(pixels)
|
||||
else:
|
||||
rgb = self._image.convert('RGB')
|
||||
self._data = rgb.tostring()
|
||||
return self._data
|
||||
|
||||
def getImageData(self):
|
||||
width, height = self.getSize()
|
||||
return width, height, self.getRGBData()
|
||||
|
||||
def getTransparent(self):
|
||||
if sys.platform[0:4] == 'java':
|
||||
return None
|
||||
else:
|
||||
if self._image.info.has_key("transparency"):
|
||||
transparency = self._image.info["transparency"] * 3
|
||||
palette = self._image.palette
|
||||
try:
|
||||
palette = palette.palette
|
||||
except:
|
||||
palette = palette.data
|
||||
return map(ord, palette[transparency:transparency+3])
|
||||
else:
|
||||
return None
|
||||
|
||||
def getImageData(imageFileName):
|
||||
"Get width, height and RGB pixels from image file. Wraps Java/PIL"
|
||||
return ImageReader.getImageData(imageFileName)
|
||||
|
||||
class DebugMemo:
|
||||
'''Intended as a simple report back encapsulator
|
||||
|
||||
Typical usages
|
||||
1) To record error data
|
||||
dbg = DebugMemo(fn='dbgmemo.dbg',myVar=value)
|
||||
dbg.add(anotherPayload='aaaa',andagain='bbb')
|
||||
dbg.dump()
|
||||
|
||||
2) To show the recorded info
|
||||
dbg = DebugMemo(fn='dbgmemo.dbg',mode='r')
|
||||
dbg.load()
|
||||
dbg.show()
|
||||
|
||||
3) To re-use recorded information
|
||||
dbg = DebugMemo(fn='dbgmemo.dbg',mode='r')
|
||||
dbg.load()
|
||||
myTestFunc(dbg.payload('myVar'),dbg.payload('andagain'))
|
||||
|
||||
in addition to the payload variables the dump records many useful bits
|
||||
of information which are also printed in the show() method.
|
||||
'''
|
||||
def __init__(self,fn='rl_dbgmemo.dbg',mode='w',getScript=1,modules=(),capture_traceback=1, stdout=None, **kw):
|
||||
import time, socket
|
||||
self.fn = fn
|
||||
if mode!='w': return
|
||||
if not stdout:
|
||||
self.stdout = sys.stdout
|
||||
else:
|
||||
if hasattr(stdout,'write'):
|
||||
self.stdout = stdout
|
||||
else:
|
||||
self.stdout = open(stdout,'w')
|
||||
self.store = store = {}
|
||||
if capture_traceback and sys.exc_info() != (None,None,None):
|
||||
import traceback
|
||||
s = getStringIO()
|
||||
traceback.print_exc(None,s)
|
||||
store['__traceback'] = s.getvalue()
|
||||
cwd=os.getcwd()
|
||||
lcwd = os.listdir(cwd)
|
||||
exed = os.path.abspath(os.path.dirname(sys.argv[0]))
|
||||
store.update({ 'gmt': time.asctime(time.gmtime(time.time())),
|
||||
'platform': sys.platform,
|
||||
'version': sys.version,
|
||||
'hexversion': hex(sys.hexversion),
|
||||
'executable': sys.executable,
|
||||
'exec_prefix': sys.exec_prefix,
|
||||
'prefix': sys.prefix,
|
||||
'path': sys.path,
|
||||
'argv': sys.argv,
|
||||
'cwd': cwd,
|
||||
'hostname': socket.gethostname(),
|
||||
'lcwd': lcwd,
|
||||
'byteorder': sys.byteorder,
|
||||
'maxint': sys.maxint,
|
||||
'maxint': getattr(sys,'maxunicode','????'),
|
||||
'api_version': getattr(sys,'api_version','????'),
|
||||
'version_info': getattr(sys,'version_info','????'),
|
||||
'winver': getattr(sys,'winver','????'),
|
||||
})
|
||||
for M,A in (
|
||||
(sys,('getwindowsversion','getfilesystemencoding')),
|
||||
(os,('uname', 'ctermid', 'getgid', 'getuid', 'getegid',
|
||||
'geteuid', 'getlogin', 'getgroups', 'getpgrp', 'getpid', 'getppid',
|
||||
)),
|
||||
):
|
||||
for a in A:
|
||||
if hasattr(M,a):
|
||||
try:
|
||||
store[a] = getattr(M,a)()
|
||||
except:
|
||||
pass
|
||||
if exed!=cwd:
|
||||
try:
|
||||
store.update({'exed': exed, 'lexed': os.listdir(exed),})
|
||||
except:
|
||||
pass
|
||||
if getScript:
|
||||
fn = os.path.abspath(sys.argv[0])
|
||||
if os.path.isfile(fn):
|
||||
try:
|
||||
store['__script'] = (fn,open(fn,'r').read())
|
||||
except:
|
||||
pass
|
||||
module_versions = {}
|
||||
for n,m in sys.modules.items():
|
||||
if n=='reportlab' or n=='rlextra' or n[:10]=='reportlab.' or n[:8]=='rlextra.':
|
||||
v = getattr(m,'__version__',None)
|
||||
if v: module_versions[n] = v
|
||||
store['__module_versions'] = module_versions
|
||||
self.store['__payload'] = {}
|
||||
self._add(kw)
|
||||
|
||||
def _add(self,D):
|
||||
payload = self.store['__payload']
|
||||
for k, v in D.items():
|
||||
payload[k] = v
|
||||
|
||||
def add(self,**kw):
|
||||
self._add(kw)
|
||||
|
||||
def dump(self):
|
||||
import pickle
|
||||
pickle.dump(self.store,open(self.fn,'wb'))
|
||||
|
||||
def load(self):
|
||||
import pickle
|
||||
self.store = pickle.load(open(self.fn,'rb'))
|
||||
|
||||
def _show_module_versions(self,k,v):
|
||||
self._writeln(k[2:])
|
||||
K = v.keys()
|
||||
K.sort()
|
||||
for k in K:
|
||||
vk = v[k]
|
||||
try:
|
||||
m = recursiveImport(k,sys.path[:],1)
|
||||
d = getattr(m,'__version__',None)==vk and 'SAME' or 'DIFFERENT'
|
||||
except:
|
||||
m = None
|
||||
d = '??????unknown??????'
|
||||
self._writeln(' %s = %s (%s)' % (k,vk,d))
|
||||
|
||||
def _banner(self,k,what):
|
||||
self._writeln('###################%s %s##################' % (what,k[2:]))
|
||||
|
||||
def _start(self,k):
|
||||
self._banner(k,'Start ')
|
||||
|
||||
def _finish(self,k):
|
||||
self._banner(k,'Finish ')
|
||||
|
||||
def _show_lines(self,k,v):
|
||||
self._start(k)
|
||||
self._writeln(v)
|
||||
self._finish(k)
|
||||
|
||||
def _show_file(self,k,v):
|
||||
k = '%s %s' % (k,os.path.basename(v[0]))
|
||||
self._show_lines(k,v[1])
|
||||
|
||||
def _show_payload(self,k,v):
|
||||
if v:
|
||||
import pprint
|
||||
self._start(k)
|
||||
pprint.pprint(v,self.stdout)
|
||||
self._finish(k)
|
||||
|
||||
specials = {'__module_versions': _show_module_versions,
|
||||
'__payload': _show_payload,
|
||||
'__traceback': _show_lines,
|
||||
'__script': _show_file,
|
||||
}
|
||||
def show(self):
|
||||
K = self.store.keys()
|
||||
K.sort()
|
||||
for k in K:
|
||||
if k not in self.specials.keys(): self._writeln('%-15s = %s' % (k,self.store[k]))
|
||||
for k in K:
|
||||
if k in self.specials.keys(): apply(self.specials[k],(self,k,self.store[k]))
|
||||
|
||||
def payload(self,name):
|
||||
return self.store['__payload'][name]
|
||||
|
||||
def __setitem__(self,name,value):
|
||||
self.store['__payload'][name] = value
|
||||
|
||||
def __getitem__(self,name):
|
||||
return self.store['__payload'][name]
|
||||
|
||||
def _writeln(self,msg):
|
||||
self.stdout.write(msg+'\n')
|
||||
|
||||
def _flatten(L,a):
|
||||
for x in L:
|
||||
if type(x) in SeqTypes: _flatten(x,a)
|
||||
else: a(x)
|
||||
|
||||
def flatten(L):
|
||||
'''recursively flatten the list or tuple L'''
|
||||
R = []
|
||||
_flatten(L,R.append)
|
||||
return R
|
||||
|
||||
def find_locals(func,depth=0):
|
||||
'''apply func to the locals at each stack frame till func returns a non false value'''
|
||||
while 1:
|
||||
_ = func(sys._getframe(depth).f_locals)
|
||||
if _: return _
|
||||
depth += 1
|
|
@ -1,260 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/validators.py
|
||||
__version__=''' $Id$ '''
|
||||
"""
|
||||
This module contains some standard verifying functions which can be
|
||||
used in an attribute map.
|
||||
"""
|
||||
|
||||
import string, sys
|
||||
from types import *
|
||||
_SequenceTypes = (ListType,TupleType)
|
||||
_NumberTypes = (FloatType,IntType)
|
||||
from reportlab.lib import colors
|
||||
if sys.hexversion<0x2030000:
|
||||
True = 1
|
||||
False = 0
|
||||
|
||||
class Validator:
|
||||
"base validator class"
|
||||
def __call__(self,x):
|
||||
return self.test(x)
|
||||
|
||||
def __str__(self):
|
||||
return getattr(self,'_str',self.__class__.__name__)
|
||||
|
||||
def normalize(self,x):
|
||||
return x
|
||||
|
||||
def normalizeTest(self,x):
|
||||
try:
|
||||
self.normalize(x)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
class _isAnything(Validator):
|
||||
def test(self,x):
|
||||
return True
|
||||
|
||||
class _isNothing(Validator):
|
||||
def test(self,x):
|
||||
return False
|
||||
|
||||
class _isBoolean(Validator):
|
||||
if sys.hexversion>=0x2030000:
|
||||
def test(self,x):
|
||||
if type(x) in (IntType,BooleanType): return x in (0,1)
|
||||
return self.normalizeTest(x)
|
||||
else:
|
||||
def test(self,x):
|
||||
if type(x) is IntType: return x in (0,1)
|
||||
return self.normalizeTest(x)
|
||||
|
||||
def normalize(self,x):
|
||||
if x in (0,1): return x
|
||||
try:
|
||||
S = string.upper(x)
|
||||
except:
|
||||
raise ValueError, 'Must be boolean'
|
||||
if S in ('YES','TRUE'): return True
|
||||
if S in ('NO','FALSE',None): return False
|
||||
raise ValueError, 'Must be boolean'
|
||||
|
||||
class _isString(Validator):
|
||||
def test(self,x):
|
||||
return type(x) is StringType
|
||||
|
||||
class _isNumber(Validator):
|
||||
def test(self,x):
|
||||
if type(x) in _NumberTypes: return True
|
||||
return self.normalizeTest(x)
|
||||
|
||||
def normalize(self,x):
|
||||
try:
|
||||
return float(x)
|
||||
except:
|
||||
return int(x)
|
||||
|
||||
class _isInt(Validator):
|
||||
def test(self,x):
|
||||
if type(x) not in (IntType,StringType): return False
|
||||
return self.normalizeTest(x)
|
||||
|
||||
def normalize(self,x):
|
||||
return int(x)
|
||||
|
||||
class _isNumberOrNone(_isNumber):
|
||||
def test(self,x):
|
||||
return x is None or isNumber(x)
|
||||
|
||||
def normalize(self,x):
|
||||
if x is None: return x
|
||||
return _isNumber.normalize(x)
|
||||
|
||||
class _isListOfNumbersOrNone(Validator):
|
||||
"ListOfNumbersOrNone validator class."
|
||||
def test(self, x):
|
||||
if x is None: return True
|
||||
return isListOfNumbers(x)
|
||||
|
||||
class _isListOfShapes(Validator):
|
||||
"ListOfShapes validator class."
|
||||
def test(self, x):
|
||||
from reportlab.graphics.shapes import Shape
|
||||
if type(x) in _SequenceTypes:
|
||||
answer = 1
|
||||
for element in x:
|
||||
if not isinstance(x, Shape):
|
||||
answer = 0
|
||||
return answer
|
||||
else:
|
||||
return False
|
||||
|
||||
class _isListOfStringsOrNone(Validator):
|
||||
"ListOfStringsOrNone validator class."
|
||||
|
||||
def test(self, x):
|
||||
if x is None: return True
|
||||
return isListOfStrings(x)
|
||||
|
||||
class _isTransform(Validator):
|
||||
"Transform validator class."
|
||||
def test(self, x):
|
||||
if type(x) in _SequenceTypes:
|
||||
if len(x) == 6:
|
||||
for element in x:
|
||||
if not isNumber(element):
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
class _isColor(Validator):
|
||||
"Color validator class."
|
||||
def test(self, x):
|
||||
return isinstance(x, colors.Color)
|
||||
|
||||
class _isColorOrNone(Validator):
|
||||
"ColorOrNone validator class."
|
||||
def test(self, x):
|
||||
if x is None: return True
|
||||
return isColor(x)
|
||||
|
||||
class _isValidChild(Validator):
|
||||
"ValidChild validator class."
|
||||
def test(self, x):
|
||||
"""Is this child allowed in a drawing or group?
|
||||
I.e. does it descend from Shape or UserNode?
|
||||
"""
|
||||
|
||||
from reportlab.graphics.shapes import UserNode, Shape
|
||||
return isinstance(x, UserNode) or isinstance(x, Shape)
|
||||
|
||||
class _isValidChildOrNone(_isValidChild):
|
||||
def test(self,x):
|
||||
return _isValidChild.test(self,x) or x is None
|
||||
|
||||
class _isCallable(Validator):
|
||||
def test(self, x):
|
||||
return callable(x)
|
||||
|
||||
class OneOf(Validator):
|
||||
"""Make validator functions for list of choices.
|
||||
|
||||
Usage:
|
||||
f = reportlab.lib.validators.OneOf('happy','sad')
|
||||
or
|
||||
f = reportlab.lib.validators.OneOf(('happy','sad'))
|
||||
f('sad'),f('happy'), f('grumpy')
|
||||
(1,1,0)
|
||||
"""
|
||||
def __init__(self, enum,*args):
|
||||
if type(enum) in [ListType,TupleType]:
|
||||
if args!=():
|
||||
raise ValueError, "Either all singleton args or a single sequence argument"
|
||||
self._enum = tuple(enum)+args
|
||||
else:
|
||||
self._enum = (enum,)+args
|
||||
|
||||
def test(self, x):
|
||||
return x in self._enum
|
||||
|
||||
class SequenceOf(Validator):
|
||||
def __init__(self,elemTest,name=None,emptyOK=1, NoneOK=0, lo=0,hi=0x7fffffff):
|
||||
self._elemTest = elemTest
|
||||
self._emptyOK = emptyOK
|
||||
self._NoneOK = NoneOK
|
||||
self._lo, self._hi = lo, hi
|
||||
if name: self._str = name
|
||||
|
||||
def test(self, x):
|
||||
if type(x) not in _SequenceTypes:
|
||||
if x is None: return self._NoneOK
|
||||
return False
|
||||
if x==[] or x==():
|
||||
return self._emptyOK
|
||||
elif not self._lo<=len(x)<=self._hi: return False
|
||||
for e in x:
|
||||
if not self._elemTest(e): return False
|
||||
return True
|
||||
|
||||
class EitherOr(Validator):
|
||||
def __init__(self,tests,name=None):
|
||||
if type(tests) not in _SequenceTypes: tests = (tests,)
|
||||
self._tests = tests
|
||||
if name: self._str = name
|
||||
|
||||
def test(self, x):
|
||||
for t in self._tests:
|
||||
if t(x): return True
|
||||
return False
|
||||
|
||||
class NoneOr(Validator):
|
||||
def __init__(self,elemTest,name=None):
|
||||
self._elemTest = elemTest
|
||||
if name: self._str = name
|
||||
|
||||
def test(self, x):
|
||||
if x is None: return True
|
||||
return self._elemTest(x)
|
||||
|
||||
class isInstanceOf(Validator):
|
||||
def __init__(self,klass=None):
|
||||
self._klass = klass
|
||||
def test(self,x):
|
||||
return isinstance(x,self._klass)
|
||||
|
||||
isBoolean = _isBoolean()
|
||||
isString = _isString()
|
||||
isNumber = _isNumber()
|
||||
isInt = _isInt()
|
||||
isNoneOrInt = NoneOr(isInt,'isNoneOrInt')
|
||||
isNumberOrNone = _isNumberOrNone()
|
||||
isTextAnchor = OneOf('start','middle','end','boxauto')
|
||||
isListOfNumbers = SequenceOf(isNumber,'isListOfNumbers')
|
||||
isListOfNumbersOrNone = _isListOfNumbersOrNone()
|
||||
isListOfShapes = _isListOfShapes()
|
||||
isListOfStrings = SequenceOf(isString,'isListOfStrings')
|
||||
isListOfStringsOrNone = _isListOfStringsOrNone()
|
||||
isTransform = _isTransform()
|
||||
isColor = _isColor()
|
||||
isListOfColors = SequenceOf(isColor,'isListOfColors')
|
||||
isColorOrNone = _isColorOrNone()
|
||||
isShape = isValidChild = _isValidChild()
|
||||
isNoneOrShape = isValidChildOrNone = _isValidChildOrNone()
|
||||
isAnything = _isAnything()
|
||||
isNothing = _isNothing()
|
||||
isXYCoord = SequenceOf(isNumber,lo=2,hi=2,emptyOK=0)
|
||||
isBoxAnchor = OneOf('nw','n','ne','w','c','e','sw','s','se', 'autox', 'autoy')
|
||||
isNoneOrString = NoneOr(isString,'NoneOrString')
|
||||
isNoneOrListOfNoneOrStrings=SequenceOf(isNoneOrString,'isNoneOrListOfNoneOrStrings',NoneOK=1)
|
||||
isListOfNoneOrString=SequenceOf(isNoneOrString,'isListOfNoneOrString',NoneOK=0)
|
||||
isNoneOrListOfNoneOrNumbers=SequenceOf(isNumberOrNone,'isNoneOrListOfNoneOrNumbers',NoneOK=1)
|
||||
isCallable = _isCallable()
|
||||
isStringOrCallable=EitherOr((isString,isCallable),'isStringOrCallable')
|
||||
isStringOrCallableOrNone=NoneOr(isStringOrCallable,'isStringOrCallableNone')
|
||||
isStringOrNone=NoneOr(isString,'isStringOrNone')
|
|
@ -1,770 +0,0 @@
|
|||
# A parser for XML, using the derived class as static DTD.
|
||||
# Author: Sjoerd Mullender.
|
||||
|
||||
# sgmlop support added by fredrik@pythonware.com (May 19, 1998)
|
||||
|
||||
import re
|
||||
import string
|
||||
|
||||
try:
|
||||
from _xmlplus.parsers import sgmlop
|
||||
#import sgmlop # this works for both builtin on the path or relative
|
||||
except ImportError:
|
||||
sgmlop = None
|
||||
|
||||
# standard entity defs
|
||||
|
||||
ENTITYDEFS = {
|
||||
'lt': '<',
|
||||
'gt': '>',
|
||||
'amp': '&',
|
||||
'quot': '"',
|
||||
'apos': '\''
|
||||
}
|
||||
|
||||
# XML parser base class -- find tags and call handler functions.
|
||||
# Usage: p = XMLParser(); p.feed(data); ...; p.close().
|
||||
# The dtd is defined by deriving a class which defines methods with
|
||||
# special names to handle tags: start_foo and end_foo to handle <foo>
|
||||
# and </foo>, respectively. The data between tags is passed to the
|
||||
# parser by calling self.handle_data() with some data as argument (the
|
||||
# data may be split up in arbutrary chunks). Entity references are
|
||||
# passed by calling self.handle_entityref() with the entity reference
|
||||
# as argument.
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# original re-based XML parser
|
||||
|
||||
_S = '[ \t\r\n]+'
|
||||
_opS = '[ \t\r\n]*'
|
||||
_Name = '[a-zA-Z_:][-a-zA-Z0-9._:]*'
|
||||
interesting = re.compile('[&<]')
|
||||
incomplete = re.compile('&(' + _Name + '|#[0-9]*|#x[0-9a-fA-F]*)?|'
|
||||
'<([a-zA-Z_:][^<>]*|'
|
||||
'/([a-zA-Z_:][^<>]*)?|'
|
||||
'![^<>]*|'
|
||||
'\?[^<>]*)?')
|
||||
|
||||
ref = re.compile('&(' + _Name + '|#[0-9]+|#x[0-9a-fA-F]+);?')
|
||||
entityref = re.compile('&(?P<name>' + _Name + ')[^-a-zA-Z0-9._:]')
|
||||
charref = re.compile('&#(?P<char>[0-9]+[^0-9]|x[0-9a-fA-F]+[^0-9a-fA-F])')
|
||||
space = re.compile(_S)
|
||||
newline = re.compile('\n')
|
||||
|
||||
starttagopen = re.compile('<' + _Name)
|
||||
endtagopen = re.compile('</')
|
||||
starttagend = re.compile(_opS + '(?P<slash>/?)>')
|
||||
endbracket = re.compile('>')
|
||||
tagfind = re.compile(_Name)
|
||||
cdataopen = re.compile('<!\[CDATA\[')
|
||||
cdataclose = re.compile('\]\]>')
|
||||
special = re.compile('<!(?P<special>[^<>]*)>')
|
||||
procopen = re.compile('<\?(?P<proc>' + _Name + ')' + _S)
|
||||
procclose = re.compile('\?>')
|
||||
commentopen = re.compile('<!--')
|
||||
commentclose = re.compile('-->')
|
||||
doubledash = re.compile('--')
|
||||
attrfind = re.compile(
|
||||
_opS + '(?P<name>' + _Name + ')'
|
||||
'(' + _opS + '=' + _opS +
|
||||
'(?P<value>\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9.:+*%?!()_#=~]+))')
|
||||
|
||||
class SlowXMLParser:
|
||||
|
||||
# Interface -- initialize and reset this instance
|
||||
def __init__(self, verbose=0):
|
||||
self.verbose = verbose
|
||||
self.reset()
|
||||
|
||||
# Interface -- reset this instance. Loses all unprocessed data
|
||||
def reset(self):
|
||||
self.rawdata = ''
|
||||
self.stack = []
|
||||
self.lasttag = '???'
|
||||
self.nomoretags = 0
|
||||
self.literal = 0
|
||||
self.lineno = 1
|
||||
|
||||
# For derived classes only -- enter literal mode (CDATA) till EOF
|
||||
def setnomoretags(self):
|
||||
self.nomoretags = self.literal = 1
|
||||
|
||||
# For derived classes only -- enter literal mode (CDATA)
|
||||
def setliteral(self, *args):
|
||||
self.literal = 1
|
||||
|
||||
# Interface -- feed some data to the parser. Call this as
|
||||
# often as you want, with as little or as much text as you
|
||||
# want (may include '\n'). (This just saves the text, all the
|
||||
# processing is done by goahead().)
|
||||
def feed(self, data):
|
||||
self.rawdata = self.rawdata + data
|
||||
self.goahead(0)
|
||||
|
||||
# Interface -- handle the remaining data
|
||||
def close(self):
|
||||
self.goahead(1)
|
||||
|
||||
# Interface -- translate references
|
||||
def translate_references(self, data):
|
||||
newdata = []
|
||||
i = 0
|
||||
while 1:
|
||||
res = ref.search(data, i)
|
||||
if res is None:
|
||||
newdata.append(data[i:])
|
||||
return string.join(newdata, '')
|
||||
if data[res.end(0) - 1] != ';':
|
||||
self.syntax_error(self.lineno,
|
||||
'; missing after entity/char reference')
|
||||
newdata.append(data[i:res.start(0)])
|
||||
str = res.group(1)
|
||||
if str[0] == '#':
|
||||
if str[1] == 'x':
|
||||
newdata.append(chr(string.atoi(str[2:], 16)))
|
||||
else:
|
||||
newdata.append(chr(string.atoi(str[1:])))
|
||||
else:
|
||||
try:
|
||||
newdata.append(self.entitydefs[str])
|
||||
except KeyError:
|
||||
# can't do it, so keep the entity ref in
|
||||
newdata.append('&' + str + ';')
|
||||
i = res.end(0)
|
||||
|
||||
# Internal -- handle data as far as reasonable. May leave state
|
||||
# and data to be processed by a subsequent call. If 'end' is
|
||||
# true, force handling all data as if followed by EOF marker.
|
||||
def goahead(self, end):
|
||||
rawdata = self.rawdata
|
||||
i = 0
|
||||
n = len(rawdata)
|
||||
while i < n:
|
||||
if self.nomoretags:
|
||||
data = rawdata[i:n]
|
||||
self.handle_data(data)
|
||||
self.lineno = self.lineno + string.count(data, '\n')
|
||||
i = n
|
||||
break
|
||||
res = interesting.search(rawdata, i)
|
||||
if res:
|
||||
j = res.start(0)
|
||||
else:
|
||||
j = n
|
||||
if i < j:
|
||||
data = rawdata[i:j]
|
||||
self.handle_data(data)
|
||||
self.lineno = self.lineno + string.count(data, '\n')
|
||||
i = j
|
||||
if i == n: break
|
||||
if rawdata[i] == '<':
|
||||
if starttagopen.match(rawdata, i):
|
||||
if self.literal:
|
||||
data = rawdata[i]
|
||||
self.handle_data(data)
|
||||
self.lineno = self.lineno + string.count(data, '\n')
|
||||
i = i+1
|
||||
continue
|
||||
k = self.parse_starttag(i)
|
||||
if k < 0: break
|
||||
self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
|
||||
i = k
|
||||
continue
|
||||
if endtagopen.match(rawdata, i):
|
||||
k = self.parse_endtag(i)
|
||||
if k < 0: break
|
||||
self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
|
||||
i = k
|
||||
self.literal = 0
|
||||
continue
|
||||
if commentopen.match(rawdata, i):
|
||||
if self.literal:
|
||||
data = rawdata[i]
|
||||
self.handle_data(data)
|
||||
self.lineno = self.lineno + string.count(data, '\n')
|
||||
i = i+1
|
||||
continue
|
||||
k = self.parse_comment(i)
|
||||
if k < 0: break
|
||||
self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
|
||||
i = k
|
||||
continue
|
||||
if cdataopen.match(rawdata, i):
|
||||
k = self.parse_cdata(i)
|
||||
if k < 0: break
|
||||
self.lineno = self.lineno + string.count(rawdata[i:i], '\n')
|
||||
i = k
|
||||
continue
|
||||
res = procopen.match(rawdata, i)
|
||||
if res:
|
||||
k = self.parse_proc(i, res)
|
||||
if k < 0: break
|
||||
self.lineno = self.lineno + string.count(rawdata[i:k], '\n')
|
||||
i = k
|
||||
continue
|
||||
res = special.match(rawdata, i)
|
||||
if res:
|
||||
if self.literal:
|
||||
data = rawdata[i]
|
||||
self.handle_data(data)
|
||||
self.lineno = self.lineno + string.count(data, '\n')
|
||||
i = i+1
|
||||
continue
|
||||
self.handle_special(res.group('special'))
|
||||
self.lineno = self.lineno + string.count(res.group(0), '\n')
|
||||
i = res.end(0)
|
||||
continue
|
||||
elif rawdata[i] == '&':
|
||||
res = charref.match(rawdata, i)
|
||||
if res is not None:
|
||||
i = res.end(0)
|
||||
if rawdata[i-1] != ';':
|
||||
self.syntax_error(self.lineno, '; missing in charref')
|
||||
i = i-1
|
||||
self.handle_charref(res.group('char')[:-1])
|
||||
self.lineno = self.lineno + string.count(res.group(0), '\n')
|
||||
continue
|
||||
res = entityref.match(rawdata, i)
|
||||
if res is not None:
|
||||
i = res.end(0)
|
||||
if rawdata[i-1] != ';':
|
||||
self.syntax_error(self.lineno, '; missing in entityref')
|
||||
i = i-1
|
||||
self.handle_entityref(res.group('name'))
|
||||
self.lineno = self.lineno + string.count(res.group(0), '\n')
|
||||
continue
|
||||
else:
|
||||
raise RuntimeError, 'neither < nor & ??'
|
||||
# We get here only if incomplete matches but
|
||||
# nothing else
|
||||
res = incomplete.match(rawdata, i)
|
||||
if not res:
|
||||
data = rawdata[i]
|
||||
self.handle_data(data)
|
||||
self.lineno = self.lineno + string.count(data, '\n')
|
||||
i = i+1
|
||||
continue
|
||||
j = res.end(0)
|
||||
if j == n:
|
||||
break # Really incomplete
|
||||
self.syntax_error(self.lineno, 'bogus < or &')
|
||||
data = res.group(0)
|
||||
self.handle_data(data)
|
||||
self.lineno = self.lineno + string.count(data, '\n')
|
||||
i = j
|
||||
# end while
|
||||
if end and i < n:
|
||||
data = rawdata[i:n]
|
||||
self.handle_data(data)
|
||||
self.lineno = self.lineno + string.count(data, '\n')
|
||||
i = n
|
||||
self.rawdata = rawdata[i:]
|
||||
# XXX if end: check for empty stack
|
||||
|
||||
# Internal -- parse comment, return length or -1 if not terminated
|
||||
def parse_comment(self, i):
|
||||
rawdata = self.rawdata
|
||||
if rawdata[i:i+4] <> '<!--':
|
||||
raise RuntimeError, 'unexpected call to handle_comment'
|
||||
res = commentclose.search(rawdata, i+4)
|
||||
if not res:
|
||||
return -1
|
||||
# doubledash search will succeed because it's a subset of commentclose
|
||||
if doubledash.search(rawdata, i+4).start(0) < res.start(0):
|
||||
self.syntax_error(self.lineno, "`--' inside comment")
|
||||
self.handle_comment(rawdata[i+4: res.start(0)])
|
||||
return res.end(0)
|
||||
|
||||
# Internal -- handle CDATA tag, return lenth or -1 if not terminated
|
||||
def parse_cdata(self, i):
|
||||
rawdata = self.rawdata
|
||||
if rawdata[i:i+9] <> '<![CDATA[':
|
||||
raise RuntimeError, 'unexpected call to handle_cdata'
|
||||
res = cdataclose.search(rawdata, i+9)
|
||||
if not res:
|
||||
return -1
|
||||
self.handle_cdata(rawdata[i+9:res.start(0)])
|
||||
return res.end(0)
|
||||
|
||||
def parse_proc(self, i, res):
|
||||
rawdata = self.rawdata
|
||||
if not res:
|
||||
raise RuntimeError, 'unexpected call to parse_proc'
|
||||
name = res.group('proc')
|
||||
res = procclose.search(rawdata, res.end(0))
|
||||
if not res:
|
||||
return -1
|
||||
self.handle_proc(name, rawdata[res.pos:res.start(0)])
|
||||
return res.end(0)
|
||||
|
||||
# Internal -- handle starttag, return length or -1 if not terminated
|
||||
def parse_starttag(self, i):
|
||||
rawdata = self.rawdata
|
||||
# i points to start of tag
|
||||
end = endbracket.search(rawdata, i+1)
|
||||
if not end:
|
||||
return -1
|
||||
j = end.start(0)
|
||||
# Now parse the data between i+1 and j into a tag and attrs
|
||||
attrdict = {}
|
||||
res = tagfind.match(rawdata, i+1)
|
||||
if not res:
|
||||
raise RuntimeError, 'unexpected call to parse_starttag'
|
||||
k = res.end(0)
|
||||
tag = res.group(0)
|
||||
if hasattr(self, tag + '_attributes'):
|
||||
attrlist = getattr(self, tag + '_attributes')
|
||||
else:
|
||||
attrlist = None
|
||||
self.lasttag = tag
|
||||
while k < j:
|
||||
res = attrfind.match(rawdata, k)
|
||||
if not res: break
|
||||
attrname, attrvalue = res.group('name', 'value')
|
||||
if attrvalue is None:
|
||||
self.syntax_error(self.lineno, 'no attribute value specified')
|
||||
attrvalue = attrname
|
||||
elif attrvalue[:1] == "'" == attrvalue[-1:] or \
|
||||
attrvalue[:1] == '"' == attrvalue[-1:]:
|
||||
attrvalue = attrvalue[1:-1]
|
||||
else:
|
||||
self.syntax_error(self.lineno, 'attribute value not quoted')
|
||||
if attrlist is not None and attrname not in attrlist:
|
||||
self.syntax_error(self.lineno,
|
||||
'unknown attribute %s of element %s' %
|
||||
(attrname, tag))
|
||||
if attrdict.has_key(attrname):
|
||||
self.syntax_error(self.lineno, 'attribute specified twice')
|
||||
attrdict[attrname] = self.translate_references(attrvalue)
|
||||
k = res.end(0)
|
||||
res = starttagend.match(rawdata, k)
|
||||
if not res:
|
||||
self.syntax_error(self.lineno, 'garbage in start tag')
|
||||
self.finish_starttag(tag, attrdict)
|
||||
if res and res.group('slash') == '/':
|
||||
self.finish_endtag(tag)
|
||||
return end.end(0)
|
||||
|
||||
# Internal -- parse endtag
|
||||
def parse_endtag(self, i):
|
||||
rawdata = self.rawdata
|
||||
end = endbracket.search(rawdata, i+1)
|
||||
if not end:
|
||||
return -1
|
||||
res = tagfind.match(rawdata, i+2)
|
||||
if not res:
|
||||
self.syntax_error(self.lineno, 'no name specified in end tag')
|
||||
tag = ''
|
||||
k = i+2
|
||||
else:
|
||||
tag = res.group(0)
|
||||
k = res.end(0)
|
||||
if k != end.start(0):
|
||||
# check that there is only white space at end of tag
|
||||
res = space.match(rawdata, k)
|
||||
if res is None or res.end(0) != end.start(0):
|
||||
self.syntax_error(self.lineno, 'garbage in end tag')
|
||||
self.finish_endtag(tag)
|
||||
return end.end(0)
|
||||
|
||||
# Internal -- finish processing of start tag
|
||||
# Return -1 for unknown tag, 1 for balanced tag
|
||||
def finish_starttag(self, tag, attrs):
|
||||
self.stack.append(tag)
|
||||
try:
|
||||
method = getattr(self, 'start_' + tag)
|
||||
except AttributeError:
|
||||
self.unknown_starttag(tag, attrs)
|
||||
return -1
|
||||
else:
|
||||
self.handle_starttag(tag, method, attrs)
|
||||
return 1
|
||||
|
||||
# Internal -- finish processing of end tag
|
||||
def finish_endtag(self, tag):
|
||||
if not tag:
|
||||
found = len(self.stack) - 1
|
||||
if found < 0:
|
||||
self.unknown_endtag(tag)
|
||||
return
|
||||
else:
|
||||
if tag not in self.stack:
|
||||
try:
|
||||
method = getattr(self, 'end_' + tag)
|
||||
except AttributeError:
|
||||
self.unknown_endtag(tag)
|
||||
return
|
||||
found = len(self.stack)
|
||||
for i in range(found):
|
||||
if self.stack[i] == tag: found = i
|
||||
while len(self.stack) > found:
|
||||
tag = self.stack[-1]
|
||||
try:
|
||||
method = getattr(self, 'end_' + tag)
|
||||
except AttributeError:
|
||||
method = None
|
||||
if method:
|
||||
self.handle_endtag(tag, method)
|
||||
else:
|
||||
self.unknown_endtag(tag)
|
||||
del self.stack[-1]
|
||||
|
||||
# Overridable -- handle start tag
|
||||
def handle_starttag(self, tag, method, attrs):
|
||||
method(attrs)
|
||||
|
||||
# Overridable -- handle end tag
|
||||
def handle_endtag(self, tag, method):
|
||||
method()
|
||||
|
||||
# Example -- handle character reference, no need to override
|
||||
def handle_charref(self, name):
|
||||
try:
|
||||
if name[0] == 'x':
|
||||
n = string.atoi(name[1:], 16)
|
||||
else:
|
||||
n = string.atoi(name)
|
||||
except string.atoi_error:
|
||||
self.unknown_charref(name)
|
||||
return
|
||||
if not 0 <= n <= 255:
|
||||
self.unknown_charref(name)
|
||||
return
|
||||
self.handle_data(chr(n))
|
||||
|
||||
# Definition of entities -- derived classes may override
|
||||
entitydefs = ENTITYDEFS
|
||||
|
||||
# Example -- handle entity reference, no need to override
|
||||
def handle_entityref(self, name):
|
||||
table = self.entitydefs
|
||||
if table.has_key(name):
|
||||
self.handle_data(table[name])
|
||||
else:
|
||||
self.unknown_entityref(name)
|
||||
return
|
||||
|
||||
# Example -- handle data, should be overridden
|
||||
def handle_data(self, data):
|
||||
pass
|
||||
|
||||
# Example -- handle cdata, could be overridden
|
||||
def handle_cdata(self, data):
|
||||
pass
|
||||
|
||||
# Example -- handle comment, could be overridden
|
||||
def handle_comment(self, data):
|
||||
pass
|
||||
|
||||
# Example -- handle processing instructions, could be overridden
|
||||
def handle_proc(self, name, data):
|
||||
pass
|
||||
|
||||
# Example -- handle special instructions, could be overridden
|
||||
def handle_special(self, data):
|
||||
pass
|
||||
|
||||
# Example -- handle relatively harmless syntax errors, could be overridden
|
||||
def syntax_error(self, lineno, message):
|
||||
raise RuntimeError, 'Syntax error at line %d: %s' % (lineno, message)
|
||||
|
||||
# To be overridden -- handlers for unknown objects
|
||||
def unknown_starttag(self, tag, attrs): pass
|
||||
def unknown_endtag(self, tag): pass
|
||||
def unknown_charref(self, ref): pass
|
||||
def unknown_entityref(self, ref): pass
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# accelerated XML parser
|
||||
|
||||
class FastXMLParser:
|
||||
|
||||
# Interface -- initialize and reset this instance
|
||||
def __init__(self, verbose=0):
|
||||
self.verbose = verbose
|
||||
self.reset()
|
||||
|
||||
# Interface -- reset this instance. Loses all unprocessed data
|
||||
def reset(self):
|
||||
self.rawdata = ''
|
||||
self.stack = []
|
||||
self.lasttag = '???'
|
||||
self.nomoretags = 0
|
||||
self.literal = 0
|
||||
self.lineno = 1
|
||||
self.parser = sgmlop.XMLParser()
|
||||
self.feed = self.parser.feed
|
||||
self.parser.register(self)
|
||||
|
||||
# For derived classes only -- enter literal mode (CDATA) till EOF
|
||||
def setnomoretags(self):
|
||||
self.nomoretags = self.literal = 1
|
||||
|
||||
# For derived classes only -- enter literal mode (CDATA)
|
||||
def setliteral(self, *args):
|
||||
self.literal = 1
|
||||
|
||||
# Interface -- feed some data to the parser. Call this as
|
||||
# often as you want, with as little or as much text as you
|
||||
# want (may include '\n'). (This just saves the text, all the
|
||||
# processing is done by goahead().)
|
||||
def feed(self, data): # overridden by reset
|
||||
self.parser.feed(data)
|
||||
|
||||
# Interface -- handle the remaining data
|
||||
def close(self):
|
||||
try:
|
||||
self.parser.close()
|
||||
finally:
|
||||
self.parser = None
|
||||
|
||||
# Interface -- translate references
|
||||
def translate_references(self, data):
|
||||
newdata = []
|
||||
i = 0
|
||||
while 1:
|
||||
res = ref.search(data, i)
|
||||
if res is None:
|
||||
newdata.append(data[i:])
|
||||
return string.join(newdata, '')
|
||||
if data[res.end(0) - 1] != ';':
|
||||
self.syntax_error(self.lineno,
|
||||
'; missing after entity/char reference')
|
||||
newdata.append(data[i:res.start(0)])
|
||||
str = res.group(1)
|
||||
if str[0] == '#':
|
||||
if str[1] == 'x':
|
||||
newdata.append(chr(string.atoi(str[2:], 16)))
|
||||
else:
|
||||
newdata.append(chr(string.atoi(str[1:])))
|
||||
else:
|
||||
try:
|
||||
newdata.append(self.entitydefs[str])
|
||||
except KeyError:
|
||||
# can't do it, so keep the entity ref in
|
||||
newdata.append('&' + str + ';')
|
||||
i = res.end(0)
|
||||
|
||||
# Internal -- finish processing of start tag
|
||||
# Return -1 for unknown tag, 1 for balanced tag
|
||||
def finish_starttag(self, tag, attrs):
|
||||
self.stack.append(tag)
|
||||
try:
|
||||
method = getattr(self, 'start_' + tag)
|
||||
except AttributeError:
|
||||
self.unknown_starttag(tag, attrs)
|
||||
return -1
|
||||
else:
|
||||
self.handle_starttag(tag, method, attrs)
|
||||
return 1
|
||||
|
||||
# Internal -- finish processing of end tag
|
||||
def finish_endtag(self, tag):
|
||||
if not tag:
|
||||
found = len(self.stack) - 1
|
||||
if found < 0:
|
||||
self.unknown_endtag(tag)
|
||||
return
|
||||
else:
|
||||
if tag not in self.stack:
|
||||
try:
|
||||
method = getattr(self, 'end_' + tag)
|
||||
except AttributeError:
|
||||
self.unknown_endtag(tag)
|
||||
return
|
||||
found = len(self.stack)
|
||||
for i in range(found):
|
||||
if self.stack[i] == tag: found = i
|
||||
while len(self.stack) > found:
|
||||
tag = self.stack[-1]
|
||||
try:
|
||||
method = getattr(self, 'end_' + tag)
|
||||
except AttributeError:
|
||||
method = None
|
||||
if method:
|
||||
self.handle_endtag(tag, method)
|
||||
else:
|
||||
self.unknown_endtag(tag)
|
||||
del self.stack[-1]
|
||||
|
||||
# Overridable -- handle start tag
|
||||
def handle_starttag(self, tag, method, attrs):
|
||||
method(attrs)
|
||||
|
||||
# Overridable -- handle end tag
|
||||
def handle_endtag(self, tag, method):
|
||||
method()
|
||||
|
||||
# Example -- handle character reference, no need to override
|
||||
def handle_charref(self, name):
|
||||
try:
|
||||
if name[0] == 'x':
|
||||
n = string.atoi(name[1:], 16)
|
||||
else:
|
||||
n = string.atoi(name)
|
||||
except string.atoi_error:
|
||||
self.unknown_charref(name)
|
||||
return
|
||||
if not 0 <= n <= 255:
|
||||
self.unknown_charref(name)
|
||||
return
|
||||
self.handle_data(chr(n))
|
||||
|
||||
# Definition of entities -- derived classes may override
|
||||
entitydefs = ENTITYDEFS
|
||||
|
||||
# Example -- handle entity reference, no need to override
|
||||
def handle_entityref(self, name):
|
||||
table = self.entitydefs
|
||||
if table.has_key(name):
|
||||
self.handle_data(table[name])
|
||||
else:
|
||||
self.unknown_entityref(name)
|
||||
return
|
||||
|
||||
# Example -- handle data, should be overridden
|
||||
def handle_data(self, data):
|
||||
pass
|
||||
|
||||
# Example -- handle cdata, could be overridden
|
||||
def handle_cdata(self, data):
|
||||
pass
|
||||
|
||||
# Example -- handle comment, could be overridden
|
||||
def handle_comment(self, data):
|
||||
pass
|
||||
|
||||
# Example -- handle processing instructions, could be overridden
|
||||
def handle_proc(self, name, data):
|
||||
pass
|
||||
|
||||
# Example -- handle special instructions, could be overridden
|
||||
def handle_special(self, data):
|
||||
pass
|
||||
|
||||
# Example -- handle relatively harmless syntax errors, could be overridden
|
||||
def syntax_error(self, lineno, message):
|
||||
raise RuntimeError, 'Syntax error at line %d: %s' % (lineno, message)
|
||||
|
||||
# To be overridden -- handlers for unknown objects
|
||||
def unknown_starttag(self, tag, attrs): pass
|
||||
def unknown_endtag(self, tag): pass
|
||||
def unknown_charref(self, ref): pass
|
||||
def unknown_entityref(self, ref): pass
|
||||
|
||||
|
||||
#sgmlop = None
|
||||
|
||||
# pick a suitable parser
|
||||
if sgmlop:
|
||||
XMLParser = FastXMLParser
|
||||
else:
|
||||
XMLParser = SlowXMLParser
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# test stuff
|
||||
|
||||
class TestXMLParser(XMLParser):
|
||||
|
||||
def __init__(self, verbose=0):
|
||||
self.testdata = ""
|
||||
XMLParser.__init__(self, verbose)
|
||||
|
||||
def handle_data(self, data):
|
||||
self.testdata = self.testdata + data
|
||||
if len(`self.testdata`) >= 70:
|
||||
self.flush()
|
||||
|
||||
def flush(self):
|
||||
data = self.testdata
|
||||
if data:
|
||||
self.testdata = ""
|
||||
print 'data:', `data`
|
||||
|
||||
def handle_cdata(self, data):
|
||||
self.flush()
|
||||
print 'cdata:', `data`
|
||||
|
||||
def handle_proc(self, name, data):
|
||||
self.flush()
|
||||
print 'processing:',name,`data`
|
||||
|
||||
def handle_special(self, data):
|
||||
self.flush()
|
||||
print 'special:',`data`
|
||||
|
||||
def handle_comment(self, data):
|
||||
self.flush()
|
||||
r = `data`
|
||||
if len(r) > 68:
|
||||
r = r[:32] + '...' + r[-32:]
|
||||
print 'comment:', r
|
||||
|
||||
def syntax_error(self, lineno, message):
|
||||
print 'error at line %d:' % lineno, message
|
||||
|
||||
def unknown_starttag(self, tag, attrs):
|
||||
self.flush()
|
||||
if not attrs:
|
||||
print 'start tag: <' + tag + '>'
|
||||
else:
|
||||
print 'start tag: <' + tag,
|
||||
for name, value in attrs.items():
|
||||
print name + '=' + '"' + value + '"',
|
||||
print '>'
|
||||
|
||||
def unknown_endtag(self, tag):
|
||||
self.flush()
|
||||
print 'end tag: </' + tag + '>'
|
||||
|
||||
def unknown_entityref(self, ref):
|
||||
self.flush()
|
||||
print '*** unknown entity ref: &' + ref + ';'
|
||||
|
||||
def unknown_charref(self, ref):
|
||||
self.flush()
|
||||
print '*** unknown char ref: &#' + ref + ';'
|
||||
|
||||
def close(self):
|
||||
XMLParser.close(self)
|
||||
self.flush()
|
||||
|
||||
def test(args = None):
|
||||
import sys
|
||||
|
||||
if not args:
|
||||
args = sys.argv[1:]
|
||||
|
||||
if args and args[0] == '-s':
|
||||
args = args[1:]
|
||||
klass = XMLParser
|
||||
else:
|
||||
klass = TestXMLParser
|
||||
|
||||
if args:
|
||||
file = args[0]
|
||||
else:
|
||||
file = 'test.xml'
|
||||
|
||||
if file == '-':
|
||||
f = sys.stdin
|
||||
else:
|
||||
try:
|
||||
f = open(file, 'r')
|
||||
except IOError, msg:
|
||||
print file, ":", msg
|
||||
sys.exit(1)
|
||||
|
||||
data = f.read()
|
||||
if f is not sys.stdin:
|
||||
f.close()
|
||||
|
||||
x = klass()
|
||||
for c in data:
|
||||
x.feed(c)
|
||||
x.close()
|
||||
|
||||
|
||||
if __name__ == '__main__': #NO_REPORTLAB_TEST
|
||||
test()
|
|
@ -1,188 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/yaml.py
|
||||
# parses "Yet Another Markup Language" into a list of tuples.
|
||||
# Each tuple says what the data is e.g.
|
||||
# ('Paragraph', 'Heading1', 'Why Reportlab Rules')
|
||||
# and the pattern depends on type.
|
||||
"""
|
||||
.h1 Welcome to YAML!
|
||||
YAML is "Yet Another Markup Language" - a markup language
|
||||
which is easier to type in than XML, yet gives us a
|
||||
reasonable selection of formats.
|
||||
|
||||
The general rule is that if a line begins with a '.',
|
||||
it requires special processing. Otherwise lines
|
||||
are concatenated to paragraphs, and blank lines
|
||||
separate paragraphs.
|
||||
|
||||
If the line ".foo bar bletch" is encountered,
|
||||
it immediately ends and writes out any current
|
||||
paragraph.
|
||||
|
||||
It then looks for a parser method called 'foo';
|
||||
if found, it is called with arguments (bar, bletch).
|
||||
|
||||
If this is not found, it assumes that 'foo' is a
|
||||
paragraph style, and the text for the first line
|
||||
of the paragraph is 'bar bletch'. It would be
|
||||
up to the formatter to decide whether on not 'foo'
|
||||
was a valid paragraph.
|
||||
|
||||
Special commands understood at present are:
|
||||
dot image filename
|
||||
- adds the image to the document
|
||||
dot beginPre Code
|
||||
- begins a Preformatted object in style 'Code'
|
||||
dot endPre
|
||||
- ends a preformatted object.
|
||||
"""
|
||||
__version__=''' $Id$ '''
|
||||
|
||||
|
||||
import sys
|
||||
import string
|
||||
|
||||
#modes:
|
||||
PLAIN = 1
|
||||
PREFORMATTED = 2
|
||||
|
||||
BULLETCHAR = '\267' # assumes font Symbol, but works on all platforms
|
||||
|
||||
class BaseParser:
|
||||
""""Simplest possible parser with only the most basic options.
|
||||
|
||||
This defines the line-handling abilities and basic mechanism.
|
||||
The class YAMLParser includes capabilities for a fairly rich
|
||||
story."""
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self._lineNo = 0
|
||||
self._style = 'Normal' # the default
|
||||
self._results = []
|
||||
self._buf = []
|
||||
self._mode = PLAIN
|
||||
|
||||
def parseFile(self, filename):
|
||||
#returns list of objects
|
||||
data = open(filename, 'r').readlines()
|
||||
|
||||
for line in data:
|
||||
#strip trailing newlines
|
||||
self.readLine(line[:-1])
|
||||
self.endPara()
|
||||
return self._results
|
||||
|
||||
def parseText(self, textBlock):
|
||||
"Parses the a possible multi-line text block"
|
||||
lines = string.split(textBlock, '\n')
|
||||
for line in lines:
|
||||
self.readLine(line)
|
||||
self.endPara()
|
||||
return self._results
|
||||
|
||||
def readLine(self, line):
|
||||
#this is the inner loop
|
||||
self._lineNo = self._lineNo + 1
|
||||
stripped = string.lstrip(line)
|
||||
if len(stripped) == 0:
|
||||
if self._mode == PLAIN:
|
||||
self.endPara()
|
||||
else: #preformatted, append it
|
||||
self._buf.append(line)
|
||||
elif line[0]=='.':
|
||||
# we have a command of some kind
|
||||
self.endPara()
|
||||
words = string.split(stripped[1:])
|
||||
cmd, args = words[0], words[1:]
|
||||
|
||||
#is it a parser method?
|
||||
if hasattr(self.__class__, cmd):
|
||||
method = eval('self.'+cmd)
|
||||
#this was very bad; any type error in the method was hidden
|
||||
#we have to hack the traceback
|
||||
try:
|
||||
apply(method, tuple(args))
|
||||
except TypeError, err:
|
||||
sys.stderr.write("Parser method: apply(%s,%s) %s at line %d\n" % (cmd, tuple(args), err, self._lineNo))
|
||||
raise
|
||||
else:
|
||||
# assume it is a paragraph style -
|
||||
# becomes the formatter's problem
|
||||
self.endPara() #end the last one
|
||||
words = string.split(stripped, ' ', 1)
|
||||
assert len(words)==2, "Style %s but no data at line %d" % (words[0], self._lineNo)
|
||||
(styletag, data) = words
|
||||
self._style = styletag[1:]
|
||||
self._buf.append(data)
|
||||
else:
|
||||
#we have data, add to para
|
||||
self._buf.append(line)
|
||||
|
||||
def endPara(self):
|
||||
#ends the current paragraph, or preformatted block
|
||||
|
||||
text = string.join(self._buf, ' ')
|
||||
if text:
|
||||
if self._mode == PREFORMATTED:
|
||||
#item 3 is list of lines
|
||||
self._results.append(('PREFORMATTED', self._style,
|
||||
string.join(self._buf,'\n')))
|
||||
else:
|
||||
self._results.append(('PARAGRAPH', self._style, text))
|
||||
self._buf = []
|
||||
self._style = 'Normal'
|
||||
|
||||
def beginPre(self, stylename):
|
||||
self._mode = PREFORMATTED
|
||||
self._style = stylename
|
||||
|
||||
def endPre(self):
|
||||
self.endPara()
|
||||
self._mode = PLAIN
|
||||
|
||||
def image(self, filename):
|
||||
self.endPara()
|
||||
self._results.append(('IMAGE', filename))
|
||||
|
||||
|
||||
class Parser(BaseParser):
|
||||
"""This adds a basic set of "story" components compatible with HTML & PDF.
|
||||
|
||||
Images, spaces"""
|
||||
|
||||
def vSpace(self, points):
|
||||
"""Inserts a vertical spacer"""
|
||||
self._results.append(('VSpace', points))
|
||||
|
||||
def pageBreak(self):
|
||||
"""Inserts a frame break"""
|
||||
self._results.append(('PageBreak','blah')) # must be a tuple
|
||||
|
||||
def custom(self, moduleName, funcName):
|
||||
"""Goes and gets the Python object and adds it to the story"""
|
||||
self.endPara()
|
||||
self._results.append(('Custom',moduleName, funcName))
|
||||
|
||||
def nextPageTemplate(self, templateName):
|
||||
self._results.append(('NextPageTemplate',templateName))
|
||||
|
||||
def parseFile(filename):
|
||||
p = Parser()
|
||||
return p.parseFile(filename)
|
||||
|
||||
def parseText(textBlock):
|
||||
p = Parser()
|
||||
return p.parseText(textBlock)
|
||||
|
||||
|
||||
if __name__=='__main__': #NORUNTESTS
|
||||
if len(sys.argv) <> 2:
|
||||
results = parseText(__doc__)
|
||||
else:
|
||||
results = parseFile(sys.argv[1])
|
||||
import pprint
|
||||
pprint.pprint(results)
|
|
@ -1,6 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfbase/__init__.py
|
||||
__version__=''' $Id$ '''
|
||||
__doc__="""
|
||||
"""
|
|
@ -1,452 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfbase/_cidfontdata.py
|
||||
#$Header $
|
||||
__version__=''' $Id$ '''
|
||||
__doc__="""
|
||||
This defines additional static data to support CID fonts.
|
||||
|
||||
Canned data is provided for the Japanese fonts supported by Adobe. We
|
||||
can add Chinese, Korean and Vietnamese in due course. The data was
|
||||
extracted by creating very simple postscript documents and running
|
||||
through Distiller, then examining the resulting PDFs.
|
||||
|
||||
Each font is described as a big nested dictionary. This lets us keep
|
||||
code out of the module altogether and avoid circular dependencies.
|
||||
|
||||
The encoding and font data are grouped by some standard 'language
|
||||
prefixes':
|
||||
chs = Chinese Simplified (mainland)
|
||||
cht = Chinese Traditional (Taiwan)
|
||||
kor = Korean
|
||||
jpn = Japanese
|
||||
"""
|
||||
|
||||
|
||||
languages = ['jpn', 'kor', 'cht', 'chs']
|
||||
|
||||
#breaking down the lists let us check if something is present
|
||||
#for a specific language
|
||||
typeFaces_chs = ['STSong-Light'] # to do
|
||||
typeFaces_cht = ['MSung-Light', 'MHei-Medium'] # to do
|
||||
typeFaces_jpn = ['HeiseiMin-W3', 'HeiseiKakuGo-W5']
|
||||
typeFaces_kor = ['HYSMyeongJo-Medium','HYGothic-Medium']
|
||||
|
||||
allowedTypeFaces = typeFaces_chs + typeFaces_cht + typeFaces_jpn + typeFaces_kor
|
||||
|
||||
|
||||
|
||||
|
||||
encodings_jpn = [
|
||||
# official encoding names, comments taken verbatim from PDF Spec
|
||||
'83pv-RKSJ-H', #Macintosh, JIS X 0208 character set with KanjiTalk6
|
||||
#extensions, Shift-JIS encoding, Script Manager code 1
|
||||
'90ms-RKSJ-H', #Microsoft Code Page 932 (lfCharSet 0x80), JIS X 0208
|
||||
#character set with NEC and IBM extensions
|
||||
'90ms-RKSJ-V', #Vertical version of 90ms-RKSJ-H
|
||||
'90msp-RKSJ-H', #Same as 90ms-RKSJ-H, but replaces half-width Latin
|
||||
#characters with proportional forms
|
||||
'90msp-RKSJ-V', #Vertical version of 90msp-RKSJ-H
|
||||
'90pv-RKSJ-H', #Macintosh, JIS X 0208 character set with KanjiTalk7
|
||||
#extensions, Shift-JIS encoding, Script Manager code 1
|
||||
'Add-RKSJ-H', #JIS X 0208 character set with Fujitsu FMR extensions,
|
||||
#Shift-JIS encoding
|
||||
'Add-RKSJ-V', #Vertical version of Add-RKSJ-H
|
||||
'EUC-H', #JIS X 0208 character set, EUC-JP encoding
|
||||
'EUC-V', #Vertical version of EUC-H
|
||||
'Ext-RKSJ-H', #JIS C 6226 (JIS78) character set with NEC extensions,
|
||||
#Shift-JIS encoding
|
||||
'Ext-RKSJ-V', #Vertical version of Ext-RKSJ-H
|
||||
'H', #JIS X 0208 character set, ISO-2022-JP encoding,
|
||||
'V', #Vertical version of H
|
||||
'UniJIS-UCS2-H', #Unicode (UCS-2) encoding for the Adobe-Japan1 character
|
||||
#collection
|
||||
'UniJIS-UCS2-V', #Vertical version of UniJIS-UCS2-H
|
||||
'UniJIS-UCS2-HW-H', #Same as UniJIS-UCS2-H, but replaces proportional Latin
|
||||
#characters with half-width forms
|
||||
'UniJIS-UCS2-HW-V' #Vertical version of UniJIS-UCS2-HW-H
|
||||
]
|
||||
encodings_kor = [
|
||||
'KSC-EUC-H', # KS X 1001:1992 character set, EUC-KR encoding
|
||||
'KSC-EUC-V', # Vertical version of KSC-EUC-H
|
||||
'KSCms-UHC-H', # Microsoft Code Page 949 (lfCharSet 0x81), KS X 1001:1992
|
||||
#character set plus 8,822 additional hangul, Unified Hangul
|
||||
#Code (UHC) encoding
|
||||
'KSCms-UHC-V', #Vertical version of KSCms-UHC-H
|
||||
'KSCms-UHC-HW-H', #Same as KSCms-UHC-H, but replaces proportional Latin
|
||||
# characters with halfwidth forms
|
||||
'KSCms-UHC-HW-V', #Vertical version of KSCms-UHC-HW-H
|
||||
'KSCpc-EUC-H', #Macintosh, KS X 1001:1992 character set with MacOS-KH
|
||||
#extensions, Script Manager Code 3
|
||||
'UniKS-UCS2-H', #Unicode (UCS-2) encoding for the Adobe-Korea1 character collection
|
||||
'UniKS-UCS2-V' #Vertical version of UniKS-UCS2-H
|
||||
|
||||
]
|
||||
|
||||
encodings_chs = [
|
||||
|
||||
'GB-EUC-H', # Microsoft Code Page 936 (lfCharSet 0x86), GB 2312-80
|
||||
# character set, EUC-CN encoding
|
||||
'GB-EUC-V', # Vertical version of GB-EUC-H
|
||||
'GBpc-EUC-H', # Macintosh, GB 2312-80 character set, EUC-CN encoding,
|
||||
# Script Manager code 2
|
||||
'GBpc-EUC-V', # Vertical version of GBpc-EUC-H
|
||||
'GBK-EUC-H', # Microsoft Code Page 936 (lfCharSet 0x86), GBK character
|
||||
# set, GBK encoding
|
||||
'GBK-EUC-V', # Vertical version of GBK-EUC-V
|
||||
'UniGB-UCS2-H', # Unicode (UCS-2) encoding for the Adobe-GB1
|
||||
# character collection
|
||||
'UniGB-UCS2-V' # Vertical version of UniGB-UCS2-H.
|
||||
]
|
||||
|
||||
encodings_cht = [
|
||||
'B5pc-H', # Macintosh, Big Five character set, Big Five encoding,
|
||||
# Script Manager code 2
|
||||
'B5pc-V', # Vertical version of B5pc-H
|
||||
'ETen-B5-H', # Microsoft Code Page 950 (lfCharSet 0x88), Big Five
|
||||
# character set with ETen extensions
|
||||
'ETen-B5-V', # Vertical version of ETen-B5-H
|
||||
'ETenms-B5-H', # Microsoft Code Page 950 (lfCharSet 0x88), Big Five
|
||||
# character set with ETen extensions; this uses proportional
|
||||
# forms for half-width Latin characters.
|
||||
'ETenms-B5-V', # Vertical version of ETenms-B5-H
|
||||
'CNS-EUC-H', # CNS 11643-1992 character set, EUC-TW encoding
|
||||
'CNS-EUC-V', # Vertical version of CNS-EUC-H
|
||||
'UniCNS-UCS2-H', # Unicode (UCS-2) encoding for the Adobe-CNS1
|
||||
# character collection
|
||||
'UniCNS-UCS2-V' # Vertical version of UniCNS-UCS2-H.
|
||||
]
|
||||
|
||||
# the Identity encodings simply dump out all character
|
||||
# in the font in the order they were defined.
|
||||
allowedEncodings = (['Identity-H', 'Identity-V'] +
|
||||
encodings_chs +
|
||||
encodings_cht +
|
||||
encodings_jpn +
|
||||
encodings_kor
|
||||
)
|
||||
|
||||
|
||||
CIDFontInfo = {}
|
||||
#statically describe the fonts in Adobe's Japanese Language Packs
|
||||
CIDFontInfo['HeiseiMin-W3'] = {
|
||||
'Type':'/Font',
|
||||
'Subtype':'/Type0',
|
||||
'Name': '/%(internalName)s' , #<-- the internal name
|
||||
'BaseFont': '/HeiseiMin-W3',
|
||||
'Encoding': '/%(encodings)s',
|
||||
|
||||
#there could be several descendant fonts if it is an old-style
|
||||
#type 0 compound font. For CID fonts there is just one.
|
||||
'DescendantFonts': [{
|
||||
'Type':'/Font',
|
||||
'Subtype':'/CIDFontType0',
|
||||
'BaseFont':'/HeiseiMin-W3',
|
||||
'FontDescriptor': {
|
||||
'Type': '/FontDescriptor',
|
||||
'Ascent': 723,
|
||||
'CapHeight': 709,
|
||||
'Descent': -241,
|
||||
'Flags': 6,
|
||||
'FontBBox': (-123, -257, 1001, 910),
|
||||
'FontName': '/HeiseiMin-W3',
|
||||
'ItalicAngle': 0,
|
||||
'StemV': 69,
|
||||
'XHeight': 450#,
|
||||
# 'Style': {'Panose': '<010502020400000000000000>'}
|
||||
},
|
||||
'CIDSystemInfo': {
|
||||
'Registry': '(Adobe)',
|
||||
'Ordering': '(Japan1)',
|
||||
'Supplement': 2
|
||||
},
|
||||
#default width is 1000 em units
|
||||
'DW': 1000,
|
||||
#widths of any which are not the default.
|
||||
'W': [1, [250, 333, 408, 500],
|
||||
5, [500, 833, 778, 180, 333],
|
||||
10, [333, 500, 564, 250, 333, 250, 278, 500],
|
||||
18, 26, 500, 27, 28, 278, 29, 31, 564,
|
||||
32, [444, 921, 722, 667],
|
||||
36, [667, 722, 611, 556, 722],
|
||||
41, [722, 333, 389, 722, 611, 889, 722],
|
||||
48, [722, 556, 722, 667, 556, 611, 722],
|
||||
55, [722, 944, 722],
|
||||
58, [722, 611, 333, 500, 333, 469, 500, 333,
|
||||
444, 500, 444, 500, 444, 333, 500],
|
||||
73, [500, 278],
|
||||
75, [278, 500, 278, 778, 500], 80, 82, 500,
|
||||
83, [333, 389, 278, 500],
|
||||
87, [500, 722, 500],
|
||||
90, [500, 444, 480, 200, 480, 333],
|
||||
97, [278], 99, [200], 101, [333, 500], 103, [500, 167],
|
||||
107, [500], 109, [500, 333], 111, [333, 556],
|
||||
113, [556, 500], 117, [250], 119, [350, 333, 444],
|
||||
123, [500], 126, [444, 333], 128, 137, 333,
|
||||
138, [1000, 889, 276, 611, 722, 889, 310, 667, 278],
|
||||
147, [278, 500, 722, 500, 564, 760, 564, 760],
|
||||
157, 158, 300, 159, [500, 300, 750], 162, 163, 750,
|
||||
164, 169, 722, 170, [667, 611], 172, 174, 611, 175,
|
||||
178, 333, 179, 185, 722, 187, 191, 722, 192,
|
||||
[556, 444], 194, 203, 444, 204, 207, 278, 208,
|
||||
214, 500, 216, 222, 500,
|
||||
223, [556, 722, 611, 500, 389, 980, 444],
|
||||
231, [500], 323, [500], 325, [500],
|
||||
327, 389, 500]
|
||||
## 'W': (
|
||||
## # starting at character ID 1, next n characters have the widths given.
|
||||
## 1, (277,305,500,668,668,906,727,305,445,445,508,668,305,379,305,539),
|
||||
## # all Characters from ID 17 to 26 are 668 em units wide
|
||||
## 17, 26, 668,
|
||||
## 27, (305, 305, 668, 668, 668, 566, 871, 727, 637, 652, 699, 574, 555,
|
||||
## 676, 687, 242, 492, 664, 582, 789, 707, 734, 582, 734, 605, 605,
|
||||
## 641, 668, 727, 945, 609, 609, 574, 445, 668, 445, 668, 668, 590,
|
||||
## 555, 609, 547, 602, 574, 391, 609, 582, 234, 277, 539, 234, 895,
|
||||
## 582, 605, 602, 602, 387, 508, 441, 582, 562, 781, 531, 570, 555,
|
||||
## 449, 246, 449, 668),
|
||||
## # these must be half width katakana and the like.
|
||||
## 231, 632, 500
|
||||
## )
|
||||
}]# end list of descendant fonts
|
||||
} #end HeiseiMin-W3
|
||||
|
||||
CIDFontInfo['HeiseiKakuGo-W5'] = {'Type':'/Font',
|
||||
'Subtype':'/Type0',
|
||||
'Name': '/%(internalName)s', #<-- the internal name
|
||||
'BaseFont': '/HeiseiKakuGo-W5',
|
||||
'Encoding': '/%(encodings)s',
|
||||
'DescendantFonts': [{'Type':'/Font',
|
||||
'Subtype':'/CIDFontType0',
|
||||
'BaseFont':'/HeiseiKakuGo-W5',
|
||||
'FontDescriptor': {
|
||||
'Type': '/FontDescriptor',
|
||||
'Ascent': 752,
|
||||
'CapHeight': 737,
|
||||
'Descent': -221,
|
||||
'Flags': 4,
|
||||
'FontBBox': [-92, -250, 1010, 922],
|
||||
'FontName': '/HeiseKakuGo-W5',
|
||||
'ItalicAngle': 0,
|
||||
'StemH': 0,
|
||||
'StemV': 114,
|
||||
'XHeight': 553,
|
||||
## 'Style': {'Panose': '<0801020b0600000000000000>'}
|
||||
},
|
||||
'CIDSystemInfo': {
|
||||
'Registry': '(Adobe)',
|
||||
'Ordering': '(Japan1)',
|
||||
'Supplement': 2
|
||||
},
|
||||
'DW': 1000,
|
||||
'W': (
|
||||
1, (277,305,500,668,668,906,727,305,445,445,508,668,305,379,305,539),
|
||||
17, 26, 668,
|
||||
27, (305, 305, 668, 668, 668, 566, 871, 727, 637, 652, 699, 574, 555,
|
||||
676, 687, 242, 492, 664, 582, 789, 707, 734, 582, 734, 605, 605,
|
||||
641, 668, 727, 945, 609, 609, 574, 445, 668, 445, 668, 668, 590,
|
||||
555, 609, 547, 602, 574, 391, 609, 582, 234, 277, 539, 234, 895,
|
||||
582, 605, 602, 602, 387, 508, 441, 582, 562, 781, 531, 570, 555,
|
||||
449, 246, 449, 668),
|
||||
231, 632, 500
|
||||
)
|
||||
}] # end descendant fonts
|
||||
}
|
||||
|
||||
CIDFontInfo['HYGothic-Medium'] = {'Type':'/Font',
|
||||
'Subtype':'/Type0',
|
||||
'Name': '/%(internalName)s', #<-- the internal name
|
||||
'BaseFont': '/' + 'HYGothic-Medium',
|
||||
'Encoding': '/%(encodings)s',
|
||||
'DescendantFonts': [{'Type':'/Font',
|
||||
'Subtype':'/CIDFontType0',
|
||||
'BaseFont':'/'+'HYGothic-Medium',
|
||||
'FontDescriptor': {
|
||||
'Type': '/FontDescriptor',
|
||||
'Ascent': 752,
|
||||
'AvgWidth': -271,
|
||||
'CapHeight': 737,
|
||||
'Descent': -142,
|
||||
'Flags': 6,
|
||||
'FontBBox': [-6, -145, 1003, 880],
|
||||
'FontName': '/'+'HYSMyeongJo-Medium',
|
||||
'ItalicAngle': 0,
|
||||
'Leading': 148,
|
||||
'MaxWidth': 1000,
|
||||
'MissingWidth': 500,
|
||||
'StemH': 0,
|
||||
'StemV': 58,
|
||||
'XHeight': 553
|
||||
},
|
||||
'CIDSystemInfo': {
|
||||
'Registry': '(Adobe)',
|
||||
'Ordering': '(Korea1)',
|
||||
'Supplement': 1
|
||||
},
|
||||
'DW': 1000,
|
||||
'W': (1, 94, 500)
|
||||
}] # end descendant fonts
|
||||
}
|
||||
|
||||
CIDFontInfo['HYSMyeongJo-Medium'] = {'Type':'/Font',
|
||||
'Subtype':'/Type0',
|
||||
'Name': '/%(internalName)s', #<-- the internal name
|
||||
'BaseFont': '/' + 'HYSMyeongJo-Medium',
|
||||
'Encoding': '/%(encodings)s',
|
||||
'DescendantFonts': [{'Type':'/Font',
|
||||
'Subtype':'/CIDFontType2',
|
||||
'BaseFont':'/'+'HYSMyeongJo-Medium',
|
||||
'FontDescriptor': {
|
||||
'Type': '/FontDescriptor',
|
||||
'Ascent': 752,
|
||||
'AvgWidth': 500,
|
||||
'CapHeight': 737,
|
||||
'Descent': -271,
|
||||
'Flags': 6,
|
||||
'FontBBox': [0, -148, 1001, 880],
|
||||
'FontName': '/'+'HYSMyeongJo-Medium',
|
||||
'ItalicAngle': 0,
|
||||
'Leading': 148,
|
||||
'MaxWidth': 1000,
|
||||
'MissingWidth': 500,
|
||||
'StemH': 91,
|
||||
'StemV': 58,
|
||||
'XHeight': 553
|
||||
},
|
||||
'CIDSystemInfo': {
|
||||
'Registry': '(Adobe)',
|
||||
'Ordering': '(Korea1)',
|
||||
'Supplement': 1
|
||||
},
|
||||
'DW': 1000,
|
||||
'W': [1, [333, 416],
|
||||
3, [416, 833, 625, 916, 833, 250, 500],
|
||||
10, 11, 500,
|
||||
12, [833, 291, 833, 291, 375, 625],
|
||||
18, 26, 625, 27, 28, 333, 29, 30, 833,
|
||||
31, [916, 500, 1000, 791, 708],
|
||||
36, [708, 750, 708, 666, 750, 791, 375,
|
||||
500, 791, 666, 916, 791, 750, 666,
|
||||
750, 708, 666, 791],
|
||||
54, [791, 750, 1000, 708],
|
||||
58, [708, 666, 500, 375, 500],
|
||||
63, 64, 500,
|
||||
65, [333, 541, 583, 541, 583],
|
||||
70, [583, 375, 583],
|
||||
73, [583, 291, 333, 583, 291, 875, 583],
|
||||
80, 82, 583,
|
||||
83, [458, 541, 375, 583],
|
||||
87, [583, 833, 625],
|
||||
90, [625, 500, 583], 93, 94, 583,
|
||||
95, [750]
|
||||
]
|
||||
}] # end descendant fonts
|
||||
}
|
||||
|
||||
#WARNING - not checked, just copied Korean to get some output
|
||||
|
||||
CIDFontInfo['STSong-Light'] = {'Type':'/Font',
|
||||
'Subtype':'/Type0',
|
||||
'Name': '/%(internalName)s', #<-- the internal name
|
||||
'BaseFont': '/' + 'STSong-Light',
|
||||
'Encoding': '/%(encodings)s',
|
||||
'DescendantFonts': [{'Type':'/Font',
|
||||
'Subtype':'/CIDFontType0',
|
||||
'BaseFont':'/'+'STSong-Light',
|
||||
'FontDescriptor': {
|
||||
'Type': '/FontDescriptor',
|
||||
'Ascent': 752,
|
||||
'CapHeight': 737,
|
||||
'Descent': -271,
|
||||
'Flags': 6,
|
||||
'FontBBox': [-25, -254, 1000, 880],
|
||||
'FontName': '/'+'STSongStd-Light',
|
||||
'ItalicAngle': 0,
|
||||
'Leading': 148,
|
||||
'MaxWidth': 1000,
|
||||
'MissingWidth': 500,
|
||||
'StemH': 91,
|
||||
'StemV': 58,
|
||||
'XHeight': 553
|
||||
},
|
||||
'CIDSystemInfo': {
|
||||
'Registry': '(Adobe)',
|
||||
'Ordering': '(GB1)',
|
||||
'Supplement': 0
|
||||
},
|
||||
'DW': 1000,
|
||||
'W': [1, [207, 270, 342, 467, 462, 797, 710, 239, 374],
|
||||
10, [374, 423, 605, 238, 375, 238, 334, 462],
|
||||
18, 26, 462, 27, 28, 238, 29, 31, 605,
|
||||
32, [344, 748, 684, 560, 695, 739, 563, 511, 729,
|
||||
793, 318, 312, 666, 526, 896, 758, 772, 544,
|
||||
772, 628, 465, 607, 753, 711, 972, 647, 620,
|
||||
607, 374, 333, 374, 606, 500, 239, 417, 503,
|
||||
427, 529, 415, 264, 444, 518, 241, 230, 495,
|
||||
228, 793, 527, 524],
|
||||
81, [524, 504, 338, 336, 277, 517, 450, 652, 466,
|
||||
452, 407, 370, 258, 370, 605]
|
||||
]
|
||||
}] # end descendant fonts
|
||||
}
|
||||
CIDFontInfo['MSung-Light'] = {'Type':'/Font',
|
||||
'Subtype':'/Type0',
|
||||
'Name': '/%(internalName)s', #<-- the internal name
|
||||
'BaseFont': '/' + 'MSung-Light',
|
||||
'Encoding': '/%(encodings)s',
|
||||
'DescendantFonts': [{'Type':'/Font',
|
||||
'Subtype':'/CIDFontType0',
|
||||
'BaseFont':'/'+'MSung-Light',
|
||||
'FontDescriptor': {
|
||||
'Type': '/FontDescriptor',
|
||||
'Ascent': 752,
|
||||
'CapHeight': 737,
|
||||
'Descent': -271,
|
||||
'Flags': 6,
|
||||
'FontBBox': [-160, -249, 1015, 888],
|
||||
'FontName': '/'+'MSung-Light',
|
||||
'ItalicAngle': 0,
|
||||
'Leading': 148,
|
||||
'MaxWidth': 1000,
|
||||
'MissingWidth': 500,
|
||||
'StemH': 45,
|
||||
'StemV': 58,
|
||||
'XHeight': 553
|
||||
},
|
||||
'CIDSystemInfo': {
|
||||
'Registry': '(Adobe)',
|
||||
'Ordering': '(CNS1)',
|
||||
'Supplement': 1
|
||||
},
|
||||
'DW': 1000,
|
||||
'W': [1, 2, 250, 3, [408, 668, 490, 875, 698, 250, 240],
|
||||
10, [240, 417, 667, 250, 313, 250, 520, 500],
|
||||
18, 26, 500, 27, 28, 250, 29, 31, 667,
|
||||
32, [396, 921, 677, 615, 719, 760, 625, 552, 771,
|
||||
802, 354],
|
||||
43, [354, 781, 604, 927, 750, 823, 563, 823, 729,
|
||||
542, 698, 771, 729, 948, 771, 677, 635, 344,
|
||||
520, 344, 469, 500, 250, 469, 521, 427, 521,
|
||||
438, 271, 469, 531, 250],
|
||||
75, [250, 458, 240, 802, 531, 500, 521],
|
||||
82, [521, 365, 333, 292, 521, 458, 677, 479, 458,
|
||||
427, 480, 496, 480, 667]]
|
||||
|
||||
}] # end descendant fonts
|
||||
}
|
||||
|
||||
|
||||
|
||||
#shift-jis saying 'This is Heisei-Minchou'
|
||||
message1 = '\202\261\202\352\202\315\225\275\220\254\226\276\222\251\202\305\202\267\201B'
|
||||
message2 = '\202\261\202\352\202\315\225\275\220\254\212p\203S\203V\203b\203N\202\305\202\267\201B'
|
||||
|
||||
##def pswidths(text):
|
||||
## import string
|
||||
## words = string.split(text)
|
||||
## out = []
|
||||
## for word in words:
|
||||
## if word == '[':
|
||||
## out.append(word)
|
||||
## else:
|
||||
## out.append(word + ',')
|
||||
## return eval(string.join(out, ''))
|
File diff suppressed because it is too large
Load Diff
|
@ -1,399 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfbase/cidfonts.py
|
||||
#$Header $
|
||||
__version__=''' $Id$ '''
|
||||
__doc__="""CID (Asian multi-byte) font support.
|
||||
|
||||
This defines classes to represent CID fonts. They know how to calculate
|
||||
their own width and how to write themselves into PDF files."""
|
||||
|
||||
import os
|
||||
from types import ListType, TupleType, DictType
|
||||
from string import find, split, strip
|
||||
import marshal
|
||||
import md5
|
||||
import time
|
||||
|
||||
import reportlab
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase._cidfontdata import allowedTypeFaces, allowedEncodings, CIDFontInfo
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.pdfbase import pdfdoc
|
||||
from reportlab.rl_config import CMapSearchPath
|
||||
|
||||
|
||||
def findCMapFile(name):
|
||||
"Returns full filename, or raises error"
|
||||
for dirname in CMapSearchPath:
|
||||
cmapfile = dirname + os.sep + name
|
||||
if os.path.isfile(cmapfile):
|
||||
return cmapfile
|
||||
raise IOError, 'CMAP file for encodings "%s" not found!' % name
|
||||
|
||||
def structToPDF(structure):
|
||||
"Converts deeply nested structure to PDFdoc dictionary/array objects"
|
||||
if type(structure) is DictType:
|
||||
newDict = {}
|
||||
for k, v in structure.items():
|
||||
newDict[k] = structToPDF(v)
|
||||
return pdfdoc.PDFDictionary(newDict)
|
||||
elif type(structure) in (ListType, TupleType):
|
||||
newList = []
|
||||
for elem in structure:
|
||||
newList.append(structToPDF(elem))
|
||||
return pdfdoc.PDFArray(newList)
|
||||
else:
|
||||
return structure
|
||||
|
||||
class CIDEncoding(pdfmetrics.Encoding):
|
||||
"""Multi-byte encoding. These are loaded from CMAP files.
|
||||
|
||||
A CMAP file is like a mini-codec. It defines the correspondence
|
||||
between code points in the (multi-byte) input data and Character
|
||||
IDs. """
|
||||
# aims to do similar things to Brian Hooper's CMap class,
|
||||
# but I could not get it working and had to rewrite.
|
||||
# also, we should really rearrange our current encoding
|
||||
# into a SingleByteEncoding since many of its methods
|
||||
# should not apply here.
|
||||
|
||||
def __init__(self, name, useCache=1):
|
||||
self.name = name
|
||||
self._mapFileHash = None
|
||||
self._codeSpaceRanges = []
|
||||
self._notDefRanges = []
|
||||
self._cmap = {}
|
||||
self.source = None
|
||||
if useCache:
|
||||
from reportlab.lib.utils import get_rl_tempdir
|
||||
fontmapdir = get_rl_tempdir('FastCMAPS')
|
||||
if os.path.isfile(fontmapdir + os.sep + name + '.fastmap'):
|
||||
self.fastLoad(fontmapdir)
|
||||
self.source = fontmapdir + os.sep + name + '.fastmap'
|
||||
else:
|
||||
self.parseCMAPFile(name)
|
||||
self.source = 'CMAP: ' + name
|
||||
self.fastSave(fontmapdir)
|
||||
else:
|
||||
self.parseCMAPFile(name)
|
||||
|
||||
def _hash(self, text):
|
||||
hasher = md5.new()
|
||||
hasher.update(text)
|
||||
return hasher.digest()
|
||||
|
||||
def parseCMAPFile(self, name):
|
||||
"""This is a tricky one as CMAP files are Postscript
|
||||
ones. Some refer to others with a 'usecmap'
|
||||
command"""
|
||||
started = time.clock()
|
||||
cmapfile = findCMapFile(name)
|
||||
# this will CRAWL with the unicode encodings...
|
||||
rawdata = open(cmapfile, 'r').read()
|
||||
|
||||
self._mapFileHash = self._hash(rawdata)
|
||||
#if it contains the token 'usecmap', parse the other
|
||||
#cmap file first....
|
||||
usecmap_pos = find(rawdata, 'usecmap')
|
||||
if usecmap_pos > -1:
|
||||
#they tell us to look in another file
|
||||
#for the code space ranges. The one
|
||||
# to use will be the previous word.
|
||||
chunk = rawdata[0:usecmap_pos]
|
||||
words = split(chunk)
|
||||
otherCMAPName = words[-1]
|
||||
#print 'referred to another CMAP %s' % otherCMAPName
|
||||
self.parseCMAPFile(otherCMAPName)
|
||||
# now continue parsing this, as it may
|
||||
# override some settings
|
||||
|
||||
|
||||
words = split(rawdata)
|
||||
while words <> []:
|
||||
if words[0] == 'begincodespacerange':
|
||||
words = words[1:]
|
||||
while words[0] <> 'endcodespacerange':
|
||||
strStart, strEnd, words = words[0], words[1], words[2:]
|
||||
start = int(strStart[1:-1], 16)
|
||||
end = int(strEnd[1:-1], 16)
|
||||
self._codeSpaceRanges.append((start, end),)
|
||||
elif words[0] == 'beginnotdefrange':
|
||||
words = words[1:]
|
||||
while words[0] <> 'endnotdefrange':
|
||||
strStart, strEnd, strValue = words[0:3]
|
||||
start = int(strStart[1:-1], 16)
|
||||
end = int(strEnd[1:-1], 16)
|
||||
value = int(strValue)
|
||||
self._notDefRanges.append((start, end, value),)
|
||||
words = words[3:]
|
||||
elif words[0] == 'begincidrange':
|
||||
words = words[1:]
|
||||
while words[0] <> 'endcidrange':
|
||||
strStart, strEnd, strValue = words[0:3]
|
||||
start = int(strStart[1:-1], 16)
|
||||
end = int(strEnd[1:-1], 16)
|
||||
value = int(strValue)
|
||||
# this means that 'start' corresponds to 'value',
|
||||
# start+1 corresponds to value+1 and so on up
|
||||
# to end
|
||||
offset = 0
|
||||
while start + offset <= end:
|
||||
self._cmap[start + offset] = value + offset
|
||||
offset = offset + 1
|
||||
words = words[3:]
|
||||
|
||||
else:
|
||||
words = words[1:]
|
||||
finished = time.clock()
|
||||
print 'parsed CMAP %s in %0.4f seconds' % (self.name, finished - started)
|
||||
|
||||
def translate(self, text):
|
||||
"Convert a string into a list of CIDs"
|
||||
output = []
|
||||
cmap = self._cmap
|
||||
lastChar = ''
|
||||
for char in text:
|
||||
if lastChar <> '':
|
||||
#print 'convert character pair "%s"' % (lastChar + char)
|
||||
num = ord(lastChar) * 256 + ord(char)
|
||||
else:
|
||||
#print 'convert character "%s"' % char
|
||||
num = ord(char)
|
||||
lastChar = char
|
||||
found = 0
|
||||
for low, high in self._codeSpaceRanges:
|
||||
if low < num < high:
|
||||
try:
|
||||
cid = cmap[num]
|
||||
#print '%d -> %d' % (num, cid)
|
||||
except KeyError:
|
||||
#not defined. Try to find the appropriate
|
||||
# notdef character, or failing that return
|
||||
# zero
|
||||
cid = 0
|
||||
for low2, high2, notdef in self._notDefRanges:
|
||||
if low2 < num < high2:
|
||||
cid = notdef
|
||||
break
|
||||
output.append(cid)
|
||||
found = 1
|
||||
break
|
||||
if found:
|
||||
lastChar = ''
|
||||
else:
|
||||
lastChar = char
|
||||
return output
|
||||
|
||||
def fastSave(self, directory):
|
||||
f = open(os.path.join(directory, self.name + '.fastmap'), 'wb')
|
||||
marshal.dump(self._mapFileHash, f)
|
||||
marshal.dump(self._codeSpaceRanges, f)
|
||||
marshal.dump(self._notDefRanges, f)
|
||||
marshal.dump(self._cmap, f)
|
||||
f.close()
|
||||
|
||||
def fastLoad(self, directory):
|
||||
started = time.clock()
|
||||
f = open(os.path.join(directory, self.name + '.fastmap'), 'rb')
|
||||
self._mapFileHash = marshal.load(f)
|
||||
self._codeSpaceRanges = marshal.load(f)
|
||||
self._notDefRanges = marshal.load(f)
|
||||
self._cmap = marshal.load(f)
|
||||
f.close()
|
||||
finished = time.clock()
|
||||
#print 'loaded %s in %0.4f seconds' % (self.name, finished - started)
|
||||
|
||||
|
||||
class CIDTypeFace(pdfmetrics.TypeFace):
|
||||
"""Multi-byte type face.
|
||||
|
||||
Conceptually similar to a single byte typeface,
|
||||
but the glyphs are identified by a numeric Character
|
||||
ID (CID) and not a glyph name. """
|
||||
def __init__(self, name):
|
||||
"""Initialised from one of the canned dictionaries in allowedEncodings
|
||||
|
||||
Or rather, it will be shortly..."""
|
||||
pdfmetrics.TypeFace.__init__(self, name)
|
||||
self._extractDictInfo(name)
|
||||
def _extractDictInfo(self, name):
|
||||
try:
|
||||
fontDict = CIDFontInfo[name]
|
||||
except KeyError:
|
||||
raise KeyError, ("Unable to find information on CID typeface '%s'" % name +
|
||||
"Only the following font names work:" + repr(allowedTypeFaces)
|
||||
)
|
||||
descFont = fontDict['DescendantFonts'][0]
|
||||
self.ascent = descFont['FontDescriptor']['Ascent']
|
||||
self.descent = descFont['FontDescriptor']['Descent']
|
||||
self._defaultWidth = descFont['DW']
|
||||
self._explicitWidths = self._expandWidths(descFont['W'])
|
||||
|
||||
# should really support self.glyphWidths, self.glyphNames
|
||||
# but not done yet.
|
||||
|
||||
|
||||
def _expandWidths(self, compactWidthArray):
|
||||
"""Expands Adobe nested list structure to get a dictionary of widths.
|
||||
|
||||
Here is an example of such a structure.
|
||||
(
|
||||
# starting at character ID 1, next n characters have the widths given.
|
||||
1, (277,305,500,668,668,906,727,305,445,445,508,668,305,379,305,539),
|
||||
# all Characters from ID 17 to 26 are 668 em units wide
|
||||
17, 26, 668,
|
||||
27, (305, 305, 668, 668, 668, 566, 871, 727, 637, 652, 699, 574, 555,
|
||||
676, 687, 242, 492, 664, 582, 789, 707, 734, 582, 734, 605, 605,
|
||||
641, 668, 727, 945, 609, 609, 574, 445, 668, 445, 668, 668, 590,
|
||||
555, 609, 547, 602, 574, 391, 609, 582, 234, 277, 539, 234, 895,
|
||||
582, 605, 602, 602, 387, 508, 441, 582, 562, 781, 531, 570, 555,
|
||||
449, 246, 449, 668),
|
||||
# these must be half width katakana and the like.
|
||||
231, 632, 500
|
||||
)
|
||||
"""
|
||||
data = compactWidthArray[:]
|
||||
widths = {}
|
||||
while data:
|
||||
start, data = data[0], data[1:]
|
||||
if type(data[0]) in (ListType, TupleType):
|
||||
items, data = data[0], data[1:]
|
||||
for offset in range(len(items)):
|
||||
widths[start + offset] = items[offset]
|
||||
else:
|
||||
end, width, data = data[0], data[1], data[2:]
|
||||
for idx in range(start, end+1):
|
||||
widths[idx] = width
|
||||
return widths
|
||||
|
||||
def getCharWidth(self, characterId):
|
||||
return self._explicitWidths.get(characterId, self._defaultWidth)
|
||||
|
||||
class CIDFont(pdfmetrics.Font):
|
||||
"Represents a built-in multi-byte font"
|
||||
def __init__(self, face, encoding):
|
||||
|
||||
self._multiByte = 1
|
||||
assert face in allowedTypeFaces, "TypeFace '%s' not supported! Use any of these instead: %s" % (face, allowedTypeFaces)
|
||||
self.faceName = face
|
||||
#should cache in registry...
|
||||
self.face = CIDTypeFace(face)
|
||||
|
||||
assert encoding in allowedEncodings, "Encoding '%s' not supported! Use any of these instead: %s" % (encoding, allowedEncodings)
|
||||
self.encodingName = encoding
|
||||
self.encoding = CIDEncoding(encoding)
|
||||
|
||||
#legacy hack doing quick cut and paste.
|
||||
self.fontName = self.faceName + '-' + self.encodingName
|
||||
self.name = self.fontName
|
||||
|
||||
# need to know if it is vertical or horizontal
|
||||
self.isVertical = (self.encodingName[-1] == 'V')
|
||||
|
||||
|
||||
def stringWidth(self, text, size):
|
||||
cidlist = self.encoding.translate(text)
|
||||
if self.isVertical:
|
||||
#this part is "not checked!" but seems to work.
|
||||
#assume each is 1000 ems high
|
||||
return len(cidlist) * size
|
||||
else:
|
||||
w = 0
|
||||
for cid in cidlist:
|
||||
w = w + self.face.getCharWidth(cid)
|
||||
return 0.001 * w * size
|
||||
|
||||
|
||||
def addObjects(self, doc):
|
||||
"""The explicit code in addMinchoObjects and addGothicObjects
|
||||
will be replaced by something that pulls the data from
|
||||
_cidfontdata.py in the next few days."""
|
||||
internalName = 'F' + repr(len(doc.fontMapping)+1)
|
||||
|
||||
bigDict = CIDFontInfo[self.face.name]
|
||||
bigDict['Name'] = '/' + internalName
|
||||
bigDict['Encoding'] = '/' + self.encodingName
|
||||
|
||||
#convert to PDF dictionary/array objects
|
||||
cidObj = structToPDF(bigDict)
|
||||
|
||||
# link into document, and add to font map
|
||||
r = doc.Reference(cidObj, internalName)
|
||||
fontDict = doc.idToObject['BasicFonts'].dict
|
||||
fontDict[internalName] = r
|
||||
doc.fontMapping[self.name] = '/' + internalName
|
||||
|
||||
|
||||
|
||||
def precalculate(cmapdir):
|
||||
# crunches through all, making 'fastmap' files
|
||||
import os
|
||||
files = os.listdir(cmapdir)
|
||||
for file in files:
|
||||
if os.path.isfile(cmapdir + os.sep + self.name + '.fastmap'):
|
||||
continue
|
||||
try:
|
||||
enc = CIDEncoding(file)
|
||||
except:
|
||||
print 'cannot parse %s, skipping' % enc
|
||||
continue
|
||||
enc.fastSave(cmapdir)
|
||||
print 'saved %s.fastmap' % file
|
||||
|
||||
def test():
|
||||
# only works if you have cirrect encodings on your box!
|
||||
c = Canvas('test_japanese.pdf')
|
||||
c.setFont('Helvetica', 30)
|
||||
c.drawString(100,700, 'Japanese Font Support')
|
||||
|
||||
pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','90ms-RKSJ-H'))
|
||||
pdfmetrics.registerFont(CIDFont('HeiseiKakuGo-W5','90ms-RKSJ-H'))
|
||||
|
||||
|
||||
# the two typefaces
|
||||
c.setFont('HeiseiMin-W3-90ms-RKSJ-H', 16)
|
||||
# this says "This is HeiseiMincho" in shift-JIS. Not all our readers
|
||||
# have a Japanese PC, so I escaped it. On a Japanese-capable
|
||||
# system, print the string to see Kanji
|
||||
message1 = '\202\261\202\352\202\315\225\275\220\254\226\276\222\251\202\305\202\267\201B'
|
||||
c.drawString(100, 675, message1)
|
||||
c.save()
|
||||
print 'saved test_japanese.pdf'
|
||||
|
||||
|
||||
## print 'CMAP_DIR = ', CMAP_DIR
|
||||
## tf1 = CIDTypeFace('HeiseiMin-W3')
|
||||
## print 'ascent = ',tf1.ascent
|
||||
## print 'descent = ',tf1.descent
|
||||
## for cid in [1,2,3,4,5,18,19,28,231,1742]:
|
||||
## print 'width of cid %d = %d' % (cid, tf1.getCharWidth(cid))
|
||||
|
||||
encName = '90ms-RKSJ-H'
|
||||
enc = CIDEncoding(encName)
|
||||
print message1, '->', enc.translate(message1)
|
||||
|
||||
f = CIDFont('HeiseiMin-W3','90ms-RKSJ-H')
|
||||
print 'width = %0.2f' % f.stringWidth(message1, 10)
|
||||
|
||||
|
||||
#testing all encodings
|
||||
## import time
|
||||
## started = time.time()
|
||||
## import glob
|
||||
## for encName in _cidfontdata.allowedEncodings:
|
||||
## #encName = '90ms-RKSJ-H'
|
||||
## enc = CIDEncoding(encName)
|
||||
## print 'encoding %s:' % encName
|
||||
## print ' codeSpaceRanges = %s' % enc._codeSpaceRanges
|
||||
## print ' notDefRanges = %s' % enc._notDefRanges
|
||||
## print ' mapping size = %d' % len(enc._cmap)
|
||||
## finished = time.time()
|
||||
## print 'constructed all encodings in %0.2f seconds' % (finished - started)
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
||||
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,632 +0,0 @@
|
|||
|
||||
"""Support for Acrobat Forms in ReportLab documents
|
||||
|
||||
This module is somewhat experimental at this time.
|
||||
|
||||
Includes basic support for
|
||||
textfields,
|
||||
select fields (drop down lists), and
|
||||
check buttons.
|
||||
|
||||
The public interface consists of functions at the moment.
|
||||
At some later date these operations may be made into canvas
|
||||
methods. (comments?)
|
||||
|
||||
The ...Absolute(...) functions position the fields with respect
|
||||
to the absolute canvas coordinate space -- that is, they do not
|
||||
respect any coordinate transforms in effect for the canvas.
|
||||
|
||||
The ...Relative(...) functions position the ONLY THE LOWER LEFT
|
||||
CORNER of the field using the coordinate transform in effect for
|
||||
the canvas. THIS WILL ONLY WORK CORRECTLY FOR TRANSLATED COORDINATES
|
||||
-- THE SHAPE, SIZE, FONTSIZE, AND ORIENTATION OF THE FIELD WILL NOT BE EFFECTED
|
||||
BY SCALING, ROTATION, SKEWING OR OTHER NON-TRANSLATION COORDINATE
|
||||
TRANSFORMS.
|
||||
|
||||
Please note that all field names (titles) in a given document must be unique.
|
||||
Textfields and select fields only support the "base 14" canvas fonts
|
||||
at this time.
|
||||
|
||||
See individual function docstrings below for more information.
|
||||
|
||||
The function test1(...) generates a simple test file.
|
||||
|
||||
THIS CONTRIBUTION WAS COMMISSIONED BY REPORTLAB USERS
|
||||
WHO WISH TO REMAIN ANONYMOUS.
|
||||
"""
|
||||
|
||||
### NOTE: MAKE THE STRING FORMATS DYNAMIC IN PATTERNS TO SUPPORT ENCRYPTION XXXX
|
||||
|
||||
import string
|
||||
from reportlab.pdfbase.pdfdoc import LINEEND, PDFString, PDFStream, PDFDictionary, PDFName
|
||||
|
||||
#==========================public interfaces
|
||||
|
||||
def textFieldAbsolute(canvas, title, x, y, width, height, value="", maxlen=1000000, multiline=0):
|
||||
"""Place a text field on the current page
|
||||
with name title at ABSOLUTE position (x,y) with
|
||||
dimensions (width, height), using value as the default value and
|
||||
maxlen as the maximum permissible length. If multiline is set make
|
||||
it a multiline field.
|
||||
"""
|
||||
theform = getForm(canvas)
|
||||
return theform.textField(canvas, title, x, y, x+width, y+height, value, maxlen, multiline)
|
||||
|
||||
def textFieldRelative(canvas, title, xR, yR, width, height, value="", maxlen=1000000, multiline=0):
|
||||
"same as textFieldAbsolute except the x and y are relative to the canvas coordinate transform"
|
||||
(xA, yA) = canvas.absolutePosition(xR,yR)
|
||||
return textFieldAbsolute(canvas, title, xA, yA, width, height, value, maxlen, multiline)
|
||||
|
||||
def buttonFieldAbsolute(canvas, title, value, x, y):
|
||||
"""Place a check button field on the current page
|
||||
with name title and default value value (one of "Yes" or "Off")
|
||||
at ABSOLUTE position (x,y).
|
||||
"""
|
||||
theform = getForm(canvas)
|
||||
return theform.buttonField(canvas, title, value, x, y)
|
||||
|
||||
def buttonFieldRelative(canvas, title, value, xR, yR):
|
||||
"same as buttonFieldAbsolute except the x and y are relative to the canvas coordinate transform"
|
||||
(xA, yA) = canvas.absolutePosition(xR,yR)
|
||||
return buttonFieldAbsolute(canvas, title, value, xA, yA)
|
||||
|
||||
def selectFieldAbsolute(canvas, title, value, options, x, y, width, height):
|
||||
"""Place a select field (drop down list) on the current page
|
||||
with name title and
|
||||
with options listed in the sequence options
|
||||
default value value (must be one of options)
|
||||
at ABSOLUTE position (x,y) with dimensions (width, height)."""
|
||||
theform = getForm(canvas)
|
||||
theform.selectField(canvas, title, value, options, x, y, x+width, y+height)
|
||||
|
||||
def selectFieldRelative(canvas, title, value, options, xR, yR, width, height):
|
||||
"same as textFieldAbsolute except the x and y are relative to the canvas coordinate transform"
|
||||
(xA, yA) = canvas.absolutePosition(xR,yR)
|
||||
return selectFieldAbsolute(canvas, title, value, options, xA, yA, width, height)
|
||||
|
||||
def test1():
|
||||
from reportlab.pdfgen import canvas
|
||||
fn = "formtest1.pdf"
|
||||
c = canvas.Canvas(fn)
|
||||
# first page
|
||||
c.setFont("Courier", 10)
|
||||
c.drawString(100, 500, "hello world")
|
||||
textFieldAbsolute(c, "fieldA", 100, 600, 100, 20, "default value")
|
||||
textFieldAbsolute(c, "fieldB", 100, 300, 100, 50, "another default value", multiline=1)
|
||||
selectFieldAbsolute(c, "fieldC", "France", ["Canada", "France", "China"], 100, 200, 100, 20)
|
||||
c.rect(100, 600, 100, 20)
|
||||
buttonFieldAbsolute(c, "field2", "Yes", 100, 700)
|
||||
c.rect(100, 700, 20, 20)
|
||||
buttonFieldAbsolute(c, "field3", "Off", 100, 800)
|
||||
c.rect(100, 800, 20, 20)
|
||||
# second page
|
||||
c.showPage()
|
||||
c.setFont("Helvetica", 7)
|
||||
c.translate(50, 20)
|
||||
c.drawString(100, 500, "hello world")
|
||||
textFieldRelative(c, "fieldA_1", 100, 600, 100, 20, "default value 2")
|
||||
c.setStrokeColorRGB(1,0,0)
|
||||
c.setFillColorRGB(0,1,0.5)
|
||||
textFieldRelative(c, "fieldB_1", 100, 300, 100, 50, "another default value 2", multiline=1)
|
||||
selectFieldRelative(c, "fieldC_1", "France 1", ["Canada 0", "France 1", "China 2"], 100, 200, 100, 20)
|
||||
c.rect(100, 600, 100, 20)
|
||||
buttonFieldRelative(c, "field2_1", "Yes", 100, 700)
|
||||
c.rect(100, 700, 20, 20)
|
||||
buttonFieldRelative(c, "field3_1", "Off", 100, 800)
|
||||
c.rect(100, 800, 20, 20)
|
||||
c.save()
|
||||
print "wrote", fn
|
||||
|
||||
#==========================end of public interfaces
|
||||
|
||||
from pdfpattern import PDFPattern
|
||||
|
||||
def getForm(canvas):
|
||||
"get form from canvas, create the form if needed"
|
||||
try:
|
||||
return canvas.AcroForm
|
||||
except AttributeError:
|
||||
theform = canvas.AcroForm = AcroForm()
|
||||
# install the form in the document
|
||||
d = canvas._doc
|
||||
cat = d._catalog
|
||||
cat.AcroForm = theform
|
||||
return theform
|
||||
|
||||
class AcroForm:
|
||||
def __init__(self):
|
||||
self.fields = []
|
||||
def textField(self, canvas, title, xmin, ymin, xmax, ymax, value="", maxlen=1000000, multiline=0):
|
||||
# determine the page ref
|
||||
doc = canvas._doc
|
||||
page = doc.thisPageRef()
|
||||
# determine text info
|
||||
(R,G,B) = canvas._fillColorRGB
|
||||
#print "rgb", (R,G,B)
|
||||
font = canvas. _fontname
|
||||
fontsize = canvas. _fontsize
|
||||
field = TextField(title, value, xmin, ymin, xmax, ymax, page, maxlen,
|
||||
font, fontsize, R, G, B, multiline)
|
||||
self.fields.append(field)
|
||||
canvas._addAnnotation(field)
|
||||
def selectField(self, canvas, title, value, options, xmin, ymin, xmax, ymax):
|
||||
# determine the page ref
|
||||
doc = canvas._doc
|
||||
page = doc.thisPageRef()
|
||||
# determine text info
|
||||
(R,G,B) = canvas._fillColorRGB
|
||||
#print "rgb", (R,G,B)
|
||||
font = canvas. _fontname
|
||||
fontsize = canvas. _fontsize
|
||||
field = SelectField(title, value, options, xmin, ymin, xmax, ymax, page,
|
||||
font=font, fontsize=fontsize, R=R, G=G, B=B)
|
||||
self.fields.append(field)
|
||||
canvas._addAnnotation(field)
|
||||
def buttonField(self, canvas, title, value, xmin, ymin):
|
||||
# determine the page ref
|
||||
doc = canvas._doc
|
||||
page = doc.thisPageRef()
|
||||
field = ButtonField(title, value, xmin, ymin, page)
|
||||
self.fields.append(field)
|
||||
canvas._addAnnotation(field)
|
||||
def format(self, document):
|
||||
from reportlab.pdfbase.pdfdoc import PDFArray
|
||||
proxy = PDFPattern(FormPattern, Resources=GLOBALRESOURCES, fields=PDFArray(self.fields))
|
||||
return proxy.format(document)
|
||||
|
||||
FormPattern = [
|
||||
'<<', LINEEND,
|
||||
' /NeedAppearances true ', LINEEND,
|
||||
' /DA ', PDFString('/Helv 0 Tf 0 g '), LINEEND,
|
||||
' /DR ', LINEEND,
|
||||
["Resources"],
|
||||
' /Fields ', LINEEND,
|
||||
["fields"],
|
||||
'>>'
|
||||
]
|
||||
|
||||
def FormFontsDictionary():
|
||||
from reportlab.pdfbase.pdfdoc import PDFDictionary
|
||||
fontsdictionary = PDFDictionary()
|
||||
fontsdictionary.__RefOnly__ = 1
|
||||
for (fullname, shortname) in FORMFONTNAMES.items():
|
||||
fontsdictionary[shortname] = FormFont(fullname, shortname)
|
||||
fontsdictionary["ZaDb"] = ZADB
|
||||
return fontsdictionary
|
||||
|
||||
def FormResources():
|
||||
return PDFPattern(FormResourcesDictionaryPattern,
|
||||
Encoding=ENCODING, Font=GLOBALFONTSDICTIONARY)
|
||||
|
||||
ZaDbPattern = [
|
||||
' <<'
|
||||
' /BaseFont'
|
||||
' /ZapfDingbats'
|
||||
' /Name'
|
||||
' /ZaDb'
|
||||
' /Subtype'
|
||||
' /Type1'
|
||||
' /Type'
|
||||
' /Font'
|
||||
'>>']
|
||||
|
||||
ZADB = PDFPattern(ZaDbPattern)
|
||||
|
||||
FormResourcesDictionaryPattern = [
|
||||
'<<',
|
||||
' /Encoding ',
|
||||
["Encoding"], LINEEND,
|
||||
' /Font ',
|
||||
["Font"], LINEEND,
|
||||
'>>'
|
||||
]
|
||||
|
||||
FORMFONTNAMES = {
|
||||
"Helvetica": "Helv",
|
||||
"Helvetica-Bold": "HeBo",
|
||||
'Courier': "Cour",
|
||||
'Courier-Bold': "CoBo",
|
||||
'Courier-Oblique': "CoOb",
|
||||
'Courier-BoldOblique': "CoBO",
|
||||
'Helvetica-Oblique': "HeOb",
|
||||
'Helvetica-BoldOblique': "HeBO",
|
||||
'Times-Roman': "Time",
|
||||
'Times-Bold': "TiBo",
|
||||
'Times-Italic': "TiIt",
|
||||
'Times-BoldItalic': "TiBI",
|
||||
}
|
||||
|
||||
EncodingPattern = [
|
||||
'<<',
|
||||
' /PDFDocEncoding ',
|
||||
["PDFDocEncoding"], LINEEND,
|
||||
'>>',
|
||||
]
|
||||
|
||||
PDFDocEncodingPattern = [
|
||||
'<<'
|
||||
' /Differences'
|
||||
' ['
|
||||
' 24'
|
||||
' /breve'
|
||||
' /caron'
|
||||
' /circumflex'
|
||||
' /dotaccent'
|
||||
' /hungarumlaut'
|
||||
' /ogonek'
|
||||
' /ring'
|
||||
' /tilde'
|
||||
' 39'
|
||||
' /quotesingle'
|
||||
' 96'
|
||||
' /grave'
|
||||
' 128'
|
||||
' /bullet'
|
||||
' /dagger'
|
||||
' /daggerdbl'
|
||||
' /ellipsis'
|
||||
' /emdash'
|
||||
' /endash'
|
||||
' /florin'
|
||||
' /fraction'
|
||||
' /guilsinglleft'
|
||||
' /guilsinglright'
|
||||
' /minus'
|
||||
' /perthousand'
|
||||
' /quotedblbase'
|
||||
' /quotedblleft'
|
||||
' /quotedblright'
|
||||
' /quoteleft'
|
||||
' /quoteright'
|
||||
' /quotesinglbase'
|
||||
' /trademark'
|
||||
' /fi'
|
||||
' /fl'
|
||||
' /Lslash'
|
||||
' /OE'
|
||||
' /Scaron'
|
||||
' /Ydieresis'
|
||||
' /Zcaron'
|
||||
' /dotlessi'
|
||||
' /lslash'
|
||||
' /oe'
|
||||
' /scaron'
|
||||
' /zcaron'
|
||||
' 160'
|
||||
' /Euro'
|
||||
' 164'
|
||||
' /currency'
|
||||
' 166'
|
||||
' /brokenbar'
|
||||
' 168'
|
||||
' /dieresis'
|
||||
' /copyright'
|
||||
' /ordfeminine'
|
||||
' 172'
|
||||
' /logicalnot'
|
||||
' /.notdef'
|
||||
' /registered'
|
||||
' /macron'
|
||||
' /degree'
|
||||
' /plusminus'
|
||||
' /twosuperior'
|
||||
' /threesuperior'
|
||||
' /acute'
|
||||
' /mu'
|
||||
' 183'
|
||||
' /periodcentered'
|
||||
' /cedilla'
|
||||
' /onesuperior'
|
||||
' /ordmasculine'
|
||||
' 188'
|
||||
' /onequarter'
|
||||
' /onehalf'
|
||||
' /threequarters'
|
||||
' 192'
|
||||
' /Agrave'
|
||||
' /Aacute'
|
||||
' /Acircumflex'
|
||||
' /Atilde'
|
||||
' /Adieresis'
|
||||
' /Aring'
|
||||
' /AE'
|
||||
' /Ccedilla'
|
||||
' /Egrave'
|
||||
' /Eacute'
|
||||
' /Ecircumflex'
|
||||
' /Edieresis'
|
||||
' /Igrave'
|
||||
' /Iacute'
|
||||
' /Icircumflex'
|
||||
' /Idieresis'
|
||||
' /Eth'
|
||||
' /Ntilde'
|
||||
' /Ograve'
|
||||
' /Oacute'
|
||||
' /Ocircumflex'
|
||||
' /Otilde'
|
||||
' /Odieresis'
|
||||
' /multiply'
|
||||
' /Oslash'
|
||||
' /Ugrave'
|
||||
' /Uacute'
|
||||
' /Ucircumflex'
|
||||
' /Udieresis'
|
||||
' /Yacute'
|
||||
' /Thorn'
|
||||
' /germandbls'
|
||||
' /agrave'
|
||||
' /aacute'
|
||||
' /acircumflex'
|
||||
' /atilde'
|
||||
' /adieresis'
|
||||
' /aring'
|
||||
' /ae'
|
||||
' /ccedilla'
|
||||
' /egrave'
|
||||
' /eacute'
|
||||
' /ecircumflex'
|
||||
' /edieresis'
|
||||
' /igrave'
|
||||
' /iacute'
|
||||
' /icircumflex'
|
||||
' /idieresis'
|
||||
' /eth'
|
||||
' /ntilde'
|
||||
' /ograve'
|
||||
' /oacute'
|
||||
' /ocircumflex'
|
||||
' /otilde'
|
||||
' /odieresis'
|
||||
' /divide'
|
||||
' /oslash'
|
||||
' /ugrave'
|
||||
' /uacute'
|
||||
' /ucircumflex'
|
||||
' /udieresis'
|
||||
' /yacute'
|
||||
' /thorn'
|
||||
' /ydieresis'
|
||||
' ]'
|
||||
' /Type'
|
||||
' /Encoding'
|
||||
'>>']
|
||||
|
||||
# global constant
|
||||
PDFDOCENC = PDFPattern(PDFDocEncodingPattern)
|
||||
# global constant
|
||||
ENCODING = PDFPattern(EncodingPattern, PDFDocEncoding=PDFDOCENC)
|
||||
|
||||
|
||||
def FormFont(BaseFont, Name):
|
||||
from reportlab.pdfbase.pdfdoc import PDFName
|
||||
return PDFPattern(FormFontPattern, BaseFont=PDFName(BaseFont), Name=PDFName(Name), Encoding=PDFDOCENC)
|
||||
|
||||
FormFontPattern = [
|
||||
'<<',
|
||||
' /BaseFont ',
|
||||
["BaseFont"], LINEEND,
|
||||
' /Encoding ',
|
||||
["Encoding"], LINEEND,
|
||||
' /Name ',
|
||||
["Name"], LINEEND,
|
||||
' /Subtype '
|
||||
' /Type1 '
|
||||
' /Type '
|
||||
' /Font '
|
||||
'>>' ]
|
||||
|
||||
# global constants
|
||||
GLOBALFONTSDICTIONARY = FormFontsDictionary()
|
||||
GLOBALRESOURCES = FormResources()
|
||||
|
||||
|
||||
def TextField(title, value, xmin, ymin, xmax, ymax, page,
|
||||
maxlen=1000000, font="Helvetica-Bold", fontsize=9, R=0, G=0, B=0.627, multiline=0):
|
||||
from reportlab.pdfbase.pdfdoc import PDFString, PDFName
|
||||
Flags = 0
|
||||
if multiline:
|
||||
Flags = Flags | (1<<12) # bit 13 is at position 12 :)
|
||||
fontname = FORMFONTNAMES[font]
|
||||
return PDFPattern(TextFieldPattern,
|
||||
value=PDFString(value), maxlen=maxlen, page=page,
|
||||
title=PDFString(title),
|
||||
xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
|
||||
fontname=PDFName(fontname), fontsize=fontsize, R=R, G=G, B=B, Flags=Flags)
|
||||
|
||||
|
||||
TextFieldPattern = [
|
||||
'<<'
|
||||
' /DA'
|
||||
' (', ["fontname"],' ',["fontsize"],' Tf ',["R"],' ',["G"],' ',["B"],' rg)'
|
||||
' /DV ',
|
||||
["value"], LINEEND,
|
||||
' /F 4 /FT /Tx'
|
||||
'/MK << /BC [ 0 0 0 ] >>'
|
||||
' /MaxLen ',
|
||||
["maxlen"], LINEEND,
|
||||
' /P ',
|
||||
["page"], LINEEND,
|
||||
' /Rect '
|
||||
' [', ["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"], ' ]'
|
||||
'/Subtype /Widget'
|
||||
' /T ',
|
||||
["title"], LINEEND,
|
||||
' /Type'
|
||||
' /Annot'
|
||||
' /V ',
|
||||
["value"], LINEEND,
|
||||
' /Ff ',
|
||||
["Flags"],LINEEND,
|
||||
'>>']
|
||||
|
||||
def SelectField(title, value, options, xmin, ymin, xmax, ymax, page,
|
||||
font="Helvetica-Bold", fontsize=9, R=0, G=0, B=0.627):
|
||||
#print "ARGS", (title, value, options, xmin, ymin, xmax, ymax, page, font, fontsize, R, G, B)
|
||||
from reportlab.pdfbase.pdfdoc import PDFString, PDFName, PDFArray
|
||||
if value not in options:
|
||||
raise ValueError, "value %s must be one of options %s" % (repr(value), repr(options))
|
||||
fontname = FORMFONTNAMES[font]
|
||||
optionstrings = map(PDFString, options)
|
||||
optionarray = PDFArray(optionstrings)
|
||||
return PDFPattern(SelectFieldPattern,
|
||||
Options=optionarray,
|
||||
Selected=PDFString(value), Page=page,
|
||||
Name=PDFString(title),
|
||||
xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
|
||||
fontname=PDFName(fontname), fontsize=fontsize, R=R, G=G, B=B)
|
||||
|
||||
SelectFieldPattern = [
|
||||
'<< % a select list',LINEEND,
|
||||
' /DA ',
|
||||
' (', ["fontname"],' ',["fontsize"],' Tf ',["R"],' ',["G"],' ',["B"],' rg)',LINEEND,
|
||||
#' (/Helv 12 Tf 0 g)',LINEEND,
|
||||
' /DV ',
|
||||
["Selected"],LINEEND,
|
||||
' /F ',
|
||||
' 4',LINEEND,
|
||||
' /FT ',
|
||||
' /Ch',LINEEND,
|
||||
' /MK ',
|
||||
' <<',
|
||||
' /BC',
|
||||
' [',
|
||||
' 0',
|
||||
' 0',
|
||||
' 0',
|
||||
' ]',
|
||||
' /BG',
|
||||
' [',
|
||||
' 1',
|
||||
' 1',
|
||||
' 1',
|
||||
' ]',
|
||||
' >>',LINEEND,
|
||||
' /Opt ',
|
||||
["Options"],LINEEND,
|
||||
' /P ',
|
||||
["Page"],LINEEND,
|
||||
'/Rect',
|
||||
' [',["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"],
|
||||
' ] ',LINEEND,
|
||||
'/Subtype',
|
||||
' /Widget',LINEEND,
|
||||
' /T ',
|
||||
["Name"],LINEEND,
|
||||
' /Type ',
|
||||
' /Annot',
|
||||
' /V ',
|
||||
["Selected"],LINEEND,
|
||||
'>>']
|
||||
|
||||
def ButtonField(title, value, xmin, ymin, page):
|
||||
if value not in ("Yes", "Off"):
|
||||
raise ValueError, "button value must be 'Yes' or 'Off': "+repr(value)
|
||||
(dx, dy) = (16.77036, 14.90698)
|
||||
return PDFPattern(ButtonFieldPattern,
|
||||
Name=PDFString(title),
|
||||
xmin=xmin, ymin=ymin, xmax=xmin+dx, ymax=ymin+dy,
|
||||
Hide=HIDE,
|
||||
APDOff=APDOFF,
|
||||
APDYes=APDYES,
|
||||
APNYes=APNYES,
|
||||
Value=PDFName(value),
|
||||
Page=page)
|
||||
|
||||
ButtonFieldPattern = ['<< ',
|
||||
'/AA',
|
||||
' <<',
|
||||
' /D ',
|
||||
["Hide"], LINEEND,
|
||||
#' %(imported.18.0)s',
|
||||
' >> ',
|
||||
'/AP ',
|
||||
' <<',
|
||||
' /D',
|
||||
' <<',
|
||||
' /Off ',
|
||||
#' %(imported.40.0)s',
|
||||
["APDOff"], LINEEND,
|
||||
' /Yes ',
|
||||
#' %(imported.39.0)s',
|
||||
["APDYes"], LINEEND,
|
||||
' >>', LINEEND,
|
||||
' /N',
|
||||
' << ',
|
||||
' /Yes ',
|
||||
#' %(imported.38.0)s',
|
||||
["APNYes"], LINEEND,
|
||||
' >>',
|
||||
' >>', LINEEND,
|
||||
' /AS ',
|
||||
["Value"], LINEEND,
|
||||
' /DA ',
|
||||
PDFString('/ZaDb 0 Tf 0 g'), LINEEND,
|
||||
'/DV ',
|
||||
["Value"], LINEEND,
|
||||
'/F ',
|
||||
' 4 ',
|
||||
'/FT ',
|
||||
' /Btn ',
|
||||
'/H ',
|
||||
' /T ',
|
||||
'/MK ',
|
||||
' <<',
|
||||
' /AC (\\376\\377)',
|
||||
#PDFString('\376\377'),
|
||||
' /CA ',
|
||||
PDFString('4'),
|
||||
' /RC ',
|
||||
PDFString('\376\377'),
|
||||
' >> ',LINEEND,
|
||||
'/P ',
|
||||
["Page"], LINEEND,
|
||||
'/Rect',
|
||||
' [',["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"],
|
||||
' ] ',LINEEND,
|
||||
'/Subtype',
|
||||
' /Widget ',
|
||||
'/T ',
|
||||
["Name"], LINEEND,
|
||||
'/Type',
|
||||
' /Annot ',
|
||||
'/V ',
|
||||
["Value"], LINEEND,
|
||||
' >>']
|
||||
|
||||
HIDE = PDFPattern([
|
||||
'<< '
|
||||
'/S '
|
||||
' /Hide '
|
||||
'>>'])
|
||||
|
||||
def buttonStreamDictionary():
|
||||
"everything except the length for the button appearance streams"
|
||||
result = PDFDictionary()
|
||||
result["SubType"] = "/Form"
|
||||
result["BBox"] = "[0 0 16.77036 14.90698]"
|
||||
font = PDFDictionary()
|
||||
font["ZaDb"] = ZADB
|
||||
resources = PDFDictionary()
|
||||
resources["ProcSet"] = "[ /PDF /Text ]"
|
||||
resources["Font"] = font
|
||||
result["Resources"] = resources
|
||||
return result
|
||||
|
||||
def ButtonStream(content):
|
||||
dict = buttonStreamDictionary()
|
||||
result = PDFStream(dict, content)
|
||||
result.filters = []
|
||||
return result
|
||||
|
||||
APDOFF = ButtonStream('0.749 g 0 0 16.7704 14.907 re f'+LINEEND)
|
||||
APDYES = ButtonStream(
|
||||
'0.749 g 0 0 16.7704 14.907 re f q 1 1 14.7704 12.907 re W '+
|
||||
'n BT /ZaDb 11.3086 Tf 0 g 1 0 0 1 3.6017 3.3881 Tm (4) Tj ET'+LINEEND)
|
||||
APNYES = ButtonStream(
|
||||
'q 1 1 14.7704 12.907 re W n BT /ZaDb 11.3086 Tf 0 g 1 0 0 1 3.6017 3.3881 Tm (4) Tj ET Q'+LINEEND)
|
||||
|
||||
#==== script interpretation
|
||||
|
||||
if __name__=="__main__":
|
||||
test1()
|
|
@ -1,773 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfbase/pdfmetrics.py
|
||||
#$Header $
|
||||
__version__=''' $Id$ '''
|
||||
__doc__="""
|
||||
This provides a database of font metric information and
|
||||
efines Font, Encoding and TypeFace classes aimed at end users.
|
||||
|
||||
There are counterparts to some of these in pdfbase/pdfdoc.py, but
|
||||
the latter focus on constructing the right PDF objects. These
|
||||
classes are declarative and focus on letting the user construct
|
||||
and query font objects.
|
||||
|
||||
The module maintains a registry of font objects at run time.
|
||||
|
||||
It is independent of the canvas or any particular context. It keeps
|
||||
a registry of Font, TypeFace and Encoding objects. Ideally these
|
||||
would be pre-loaded, but due to a nasty circularity problem we
|
||||
trap attempts to access them and do it on first access.
|
||||
"""
|
||||
import string, os
|
||||
from types import StringType, ListType, TupleType
|
||||
from reportlab.pdfbase import _fontdata
|
||||
from reportlab.lib.logger import warnOnce
|
||||
from reportlab.lib.utils import rl_isfile, open_and_read, open_and_readlines
|
||||
from reportlab.rl_config import defaultEncoding
|
||||
|
||||
standardFonts = _fontdata.standardFonts
|
||||
standardEncodings = _fontdata.standardEncodings
|
||||
|
||||
_dummyEncoding=' _not an encoding_ '
|
||||
# conditional import - try both import techniques, and set a flag
|
||||
try:
|
||||
import _rl_accel
|
||||
try:
|
||||
_stringWidth = _rl_accel.stringWidth
|
||||
_rl_accel.defaultEncoding(_dummyEncoding)
|
||||
except:
|
||||
_stringWidth = None
|
||||
except ImportError:
|
||||
_stringWidth = None
|
||||
|
||||
_typefaces = {}
|
||||
_encodings = {}
|
||||
_fonts = {}
|
||||
|
||||
class FontError(Exception):
|
||||
pass
|
||||
class FontNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
def parseAFMFile(afmFileName):
|
||||
"""Quick and dirty - gives back a top-level dictionary
|
||||
with top-level items, and a 'widths' key containing
|
||||
a dictionary of glyph names and widths. Just enough
|
||||
needed for embedding. A better parser would accept
|
||||
options for what data you wwanted, and preserve the
|
||||
order."""
|
||||
|
||||
lines = open_and_readlines(afmFileName, 'r')
|
||||
if len(lines)<=1:
|
||||
#likely to be a MAC file
|
||||
lines = string.split(lines,'\r')
|
||||
if len(lines)<=1:
|
||||
raise ValueError, 'AFM file %s hasn\'t enough data' % afmFileName
|
||||
topLevel = {}
|
||||
glyphLevel = []
|
||||
|
||||
lines = map(string.strip, lines)
|
||||
#pass 1 - get the widths
|
||||
inMetrics = 0 # os 'TOP', or 'CHARMETRICS'
|
||||
for line in lines:
|
||||
if line[0:16] == 'StartCharMetrics':
|
||||
inMetrics = 1
|
||||
elif line[0:14] == 'EndCharMetrics':
|
||||
inMetrics = 0
|
||||
elif inMetrics:
|
||||
chunks = string.split(line, ';')
|
||||
chunks = map(string.strip, chunks)
|
||||
cidChunk, widthChunk, nameChunk = chunks[0:3]
|
||||
|
||||
# character ID
|
||||
l, r = string.split(cidChunk)
|
||||
assert l == 'C', 'bad line in font file %s' % line
|
||||
cid = string.atoi(r)
|
||||
|
||||
# width
|
||||
l, r = string.split(widthChunk)
|
||||
assert l == 'WX', 'bad line in font file %s' % line
|
||||
width = string.atoi(r)
|
||||
|
||||
# name
|
||||
l, r = string.split(nameChunk)
|
||||
assert l == 'N', 'bad line in font file %s' % line
|
||||
name = r
|
||||
|
||||
glyphLevel.append((cid, width, name))
|
||||
|
||||
# pass 2 font info
|
||||
inHeader = 0
|
||||
for line in lines:
|
||||
if line[0:16] == 'StartFontMetrics':
|
||||
inHeader = 1
|
||||
if line[0:16] == 'StartCharMetrics':
|
||||
inHeader = 0
|
||||
elif inHeader:
|
||||
if line[0:7] == 'Comment': pass
|
||||
try:
|
||||
left, right = string.split(line,' ',1)
|
||||
except:
|
||||
raise ValueError, "Header information error in afm %s: line='%s'" % (afmFileName, line)
|
||||
try:
|
||||
right = string.atoi(right)
|
||||
except:
|
||||
pass
|
||||
topLevel[left] = right
|
||||
|
||||
|
||||
return (topLevel, glyphLevel)
|
||||
|
||||
class TypeFace:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.glyphNames = []
|
||||
self.glyphWidths = {}
|
||||
self.ascent = 0
|
||||
self.descent = 0
|
||||
|
||||
|
||||
# all typefaces of whatever class should have these 3 attributes.
|
||||
# these are the basis for family detection.
|
||||
self.familyName = None # should set on load/construction if possible
|
||||
self.bold = 0 # bold faces should set this
|
||||
self.italic = 0 #italic faces should set this
|
||||
|
||||
|
||||
if name == 'ZapfDingbats':
|
||||
self.requiredEncoding = 'ZapfDingbatsEncoding'
|
||||
elif name == 'Symbol':
|
||||
self.requiredEncoding = 'SymbolEncoding'
|
||||
else:
|
||||
self.requiredEncoding = None
|
||||
if name in standardFonts:
|
||||
self.builtIn = 1
|
||||
self._loadBuiltInData(name)
|
||||
else:
|
||||
self.builtIn = 0
|
||||
|
||||
def _loadBuiltInData(self, name):
|
||||
"""Called for the built in 14 fonts. Gets their glyph data.
|
||||
We presume they never change so this can be a shared reference."""
|
||||
name = str(name) #needed for pycanvas&jython/2.1 compatibility
|
||||
self.glyphWidths = _fontdata.widthsByFontGlyph[name]
|
||||
self.glyphNames = self.glyphWidths.keys()
|
||||
self.ascent,self.descent = _fontdata.ascent_descent[name]
|
||||
|
||||
def getFontFiles(self):
|
||||
"Info function, return list of the font files this depends on."
|
||||
return []
|
||||
|
||||
def findT1File(self, ext='.pfb'):
|
||||
possible_exts = (string.lower(ext), string.upper(ext))
|
||||
if hasattr(self,'pfbFileName'):
|
||||
r_basename = os.path.splitext(self.pfbFileName)[0]
|
||||
for e in possible_exts:
|
||||
if rl_isfile(r_basename + e):
|
||||
return r_basename + e
|
||||
try:
|
||||
r = _fontdata.findT1File(self.name)
|
||||
except:
|
||||
afm = bruteForceSearchForAFM(self.name)
|
||||
if afm:
|
||||
if string.lower(ext) == '.pfb':
|
||||
for e in possible_exts:
|
||||
pfb = os.path.splitext(afm)[0] + e
|
||||
if rl_isfile(pfb):
|
||||
r = pfb
|
||||
else:
|
||||
r = None
|
||||
elif string.lower(ext) == '.afm':
|
||||
r = afm
|
||||
else:
|
||||
r = None
|
||||
if r is None:
|
||||
warnOnce("Can't find %s for face '%s'" % (ext, self.name))
|
||||
return r
|
||||
|
||||
def bruteForceSearchForAFM(faceName):
|
||||
"""Looks in all AFM files on path for face with given name.
|
||||
|
||||
Returns AFM file name or None. Ouch!"""
|
||||
import glob
|
||||
from reportlab.rl_config import T1SearchPath
|
||||
|
||||
for dirname in T1SearchPath:
|
||||
if not os.path.isdir(dirname):
|
||||
continue
|
||||
possibles = glob.glob(dirname + os.sep + '*.[aA][fF][mM]')
|
||||
for possible in possibles:
|
||||
(topDict, glyphDict) = parseAFMFile(possible)
|
||||
if topDict['FontName'] == faceName:
|
||||
return possible
|
||||
return None
|
||||
|
||||
|
||||
|
||||
#for faceName in standardFonts:
|
||||
# registerTypeFace(TypeFace(faceName))
|
||||
|
||||
|
||||
class Encoding:
|
||||
"""Object to help you create and refer to encodings."""
|
||||
def __init__(self, name, base=None):
|
||||
self.name = name
|
||||
self.frozen = 0
|
||||
if name in standardEncodings:
|
||||
assert base is None, "Can't have a base encoding for a standard encoding"
|
||||
self.baseEncodingName = name
|
||||
self.vector = _fontdata.encodings[name]
|
||||
elif base == None:
|
||||
# assume based on the usual one
|
||||
self.baseEncodingName = defaultEncoding
|
||||
self.vector = _fontdata.encodings[defaultEncoding]
|
||||
elif type(base) is StringType:
|
||||
baseEnc = getEncoding(base)
|
||||
self.baseEncodingName = baseEnc.name
|
||||
self.vector = baseEnc.vector[:]
|
||||
elif type(base) in (ListType, TupleType):
|
||||
self.baseEncodingName = defaultEncoding
|
||||
self.vector = base[:]
|
||||
elif isinstance(base, Encoding):
|
||||
# accept a vector
|
||||
self.baseEncodingName = base.name
|
||||
self.vector = base.vector[:]
|
||||
|
||||
def __getitem__(self, index):
|
||||
"Return glyph name for that code point, or None"
|
||||
# THIS SHOULD BE INLINED FOR SPEED
|
||||
return self.vector[index]
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
# should fail if they are frozen
|
||||
assert self.frozen == 0, 'Cannot modify a frozen encoding'
|
||||
if self.vector[index]!=value:
|
||||
L = list(self.vector)
|
||||
L[index] = value
|
||||
self.vector = tuple(L)
|
||||
|
||||
def freeze(self):
|
||||
self.vector = tuple(self.vector)
|
||||
self.frozen = 1
|
||||
|
||||
def isEqual(self, other):
|
||||
return ((self.name == other.name) and (self.vector == other.vector))
|
||||
|
||||
def modifyRange(self, base, newNames):
|
||||
"""Set a group of character names starting at the code point 'base'."""
|
||||
assert self.frozen == 0, 'Cannot modify a frozen encoding'
|
||||
idx = base
|
||||
for name in newNames:
|
||||
self.vector[idx] = name
|
||||
idx = idx + 1
|
||||
|
||||
def getDifferences(self, otherEnc):
|
||||
"""Return a compact list of the code points differing between two encodings
|
||||
|
||||
This is in the Adobe format: list of
|
||||
[[b1, name1, name2, name3],
|
||||
[b2, name4]]
|
||||
where b1...bn is the starting code point, and the glyph names following
|
||||
are assigned consecutive code points."""
|
||||
|
||||
ranges = []
|
||||
curRange = None
|
||||
for i in xrange(len(self.vector)):
|
||||
glyph = self.vector[i]
|
||||
if glyph==otherEnc.vector[i]:
|
||||
if curRange:
|
||||
ranges.append(curRange)
|
||||
curRange = []
|
||||
else:
|
||||
if curRange:
|
||||
curRange.append(glyph)
|
||||
elif glyph:
|
||||
curRange = [i, glyph]
|
||||
if curRange:
|
||||
ranges.append(curRange)
|
||||
return ranges
|
||||
|
||||
def makePDFObject(self):
|
||||
"Returns a PDF Object representing self"
|
||||
# avoid circular imports - this cannot go at module level
|
||||
from reportlab.pdfbase import pdfdoc
|
||||
|
||||
D = {}
|
||||
baseEnc = getEncoding(self.baseEncodingName)
|
||||
differences = self.getDifferences(baseEnc) #[None] * 256)
|
||||
|
||||
# if no differences, we just need the base name
|
||||
if differences == []:
|
||||
return pdfdoc.PDFName(self.baseEncodingName)
|
||||
else:
|
||||
#make up a dictionary describing the new encoding
|
||||
diffArray = []
|
||||
for range in differences:
|
||||
diffArray.append(range[0]) # numbers go 'as is'
|
||||
for glyphName in range[1:]:
|
||||
if glyphName is not None:
|
||||
# there is no way to 'unset' a character in the base font.
|
||||
diffArray.append('/' + glyphName)
|
||||
|
||||
#print 'diffArray = %s' % diffArray
|
||||
D["Differences"] = pdfdoc.PDFArray(diffArray)
|
||||
D["BaseEncoding"] = pdfdoc.PDFName(self.baseEncodingName)
|
||||
D["Type"] = pdfdoc.PDFName("Encoding")
|
||||
PD = pdfdoc.PDFDictionary(D)
|
||||
return PD
|
||||
|
||||
#for encName in standardEncodings:
|
||||
# registerEncoding(Encoding(encName))
|
||||
|
||||
class Font:
|
||||
"""Represents a font (i.e combination of face and encoding).
|
||||
|
||||
Defines suitable machinery for single byte fonts. This is
|
||||
a concrete class which can handle the basic built-in fonts;
|
||||
not clear yet if embedded ones need a new font class or
|
||||
just a new typeface class (which would do the job through
|
||||
composition)"""
|
||||
def __init__(self, name, faceName, encName):
|
||||
self.fontName = name
|
||||
self.face = getTypeFace(faceName)
|
||||
self.encoding= getEncoding(encName)
|
||||
self._calcWidths()
|
||||
|
||||
# multi byte fonts do their own stringwidth calculations.
|
||||
# signal this here.
|
||||
self._multiByte = 0
|
||||
|
||||
def _calcWidths(self):
|
||||
"""Vector of widths for stringWidth function"""
|
||||
#synthesize on first request
|
||||
w = [0] * 256
|
||||
gw = self.face.glyphWidths
|
||||
vec = self.encoding.vector
|
||||
for i in range(256):
|
||||
glyphName = vec[i]
|
||||
if glyphName is not None:
|
||||
try:
|
||||
width = gw[glyphName]
|
||||
w[i] = width
|
||||
except KeyError:
|
||||
import reportlab.rl_config
|
||||
if reportlab.rl_config.warnOnMissingFontGlyphs:
|
||||
print 'typeface "%s" does not have a glyph "%s", bad font!' % (self.face.name, glyphName)
|
||||
else:
|
||||
pass
|
||||
self.widths = w
|
||||
|
||||
if not _stringWidth:
|
||||
def stringWidth(self, text, size):
|
||||
"""This is the "purist" approach to width. The practical one
|
||||
is to use the stringWidth one which may be optimized
|
||||
in C."""
|
||||
w = 0
|
||||
widths = self.widths
|
||||
for ch in text:
|
||||
w = w + widths[ord(ch)]
|
||||
return w * 0.001 * size
|
||||
|
||||
def _formatWidths(self):
|
||||
"returns a pretty block in PDF Array format to aid inspection"
|
||||
text = '['
|
||||
for i in range(256):
|
||||
text = text + ' ' + str(self.widths[i])
|
||||
if i == 255:
|
||||
text = text + ' ]'
|
||||
if i % 16 == 15:
|
||||
text = text + '\n'
|
||||
return text
|
||||
|
||||
def addObjects(self, doc):
|
||||
"""Makes and returns one or more PDF objects to be added
|
||||
to the document. The caller supplies the internal name
|
||||
to be used (typically F1, F2... in sequence) """
|
||||
# avoid circular imports - this cannot go at module level
|
||||
from reportlab.pdfbase import pdfdoc
|
||||
|
||||
# construct a Type 1 Font internal object
|
||||
internalName = 'F' + repr(len(doc.fontMapping)+1)
|
||||
pdfFont = pdfdoc.PDFType1Font()
|
||||
pdfFont.Name = internalName
|
||||
pdfFont.BaseFont = self.face.name
|
||||
pdfFont.__Comment__ = 'Font %s' % self.fontName
|
||||
pdfFont.Encoding = self.encoding.makePDFObject()
|
||||
|
||||
# is it a built-in one? if not, need more stuff.
|
||||
if not self.face.name in standardFonts:
|
||||
pdfFont.FirstChar = 0
|
||||
pdfFont.LastChar = 255
|
||||
pdfFont.Widths = pdfdoc.PDFArray(self.widths)
|
||||
pdfFont.FontDescriptor = self.face.addObjects(doc)
|
||||
# now link it in
|
||||
ref = doc.Reference(pdfFont, internalName)
|
||||
|
||||
# also refer to it in the BasicFonts dictionary
|
||||
fontDict = doc.idToObject['BasicFonts'].dict
|
||||
fontDict[internalName] = pdfFont
|
||||
|
||||
# and in the font mappings
|
||||
doc.fontMapping[self.fontName] = '/' + internalName
|
||||
|
||||
PFB_MARKER=chr(0x80)
|
||||
PFB_ASCII=chr(1)
|
||||
PFB_BINARY=chr(2)
|
||||
PFB_EOF=chr(3)
|
||||
def _pfbSegLen(p,d):
|
||||
'''compute a pfb style length from the first 4 bytes of string d'''
|
||||
return ((((ord(d[p+3])<<8)|ord(d[p+2])<<8)|ord(d[p+1]))<<8)|ord(d[p])
|
||||
|
||||
def _pfbCheck(p,d,m,fn):
|
||||
if d[p]!=PFB_MARKER or d[p+1]!=m:
|
||||
raise ValueError, 'Bad pfb file\'%s\' expected chr(%d)chr(%d) at char %d, got chr(%d)chr(%d)' % (fn,ord(PFB_MARKER),ord(m),p,ord(d[p]),ord(d[p+1]))
|
||||
if m==PFB_EOF: return
|
||||
p = p + 2
|
||||
l = _pfbSegLen(p,d)
|
||||
p = p + 4
|
||||
if p+l>len(d):
|
||||
raise ValueError, 'Bad pfb file\'%s\' needed %d+%d bytes have only %d!' % (fn,p,l,len(d))
|
||||
return p, p+l
|
||||
|
||||
|
||||
class EmbeddedType1Face(TypeFace):
|
||||
"""A Type 1 font other than one of the basic 14.
|
||||
|
||||
Its glyph data will be embedded in the PDF file."""
|
||||
def __init__(self, afmFileName, pfbFileName):
|
||||
# ignore afm file for now
|
||||
TypeFace.__init__(self, None)
|
||||
#None is a hack, name will be supplied by AFM parse lower done
|
||||
#in this __init__ method.
|
||||
self.afmFileName = os.path.abspath(afmFileName)
|
||||
self.pfbFileName = os.path.abspath(pfbFileName)
|
||||
self.requiredEncoding = None
|
||||
self._loadGlyphs(pfbFileName)
|
||||
self._loadMetrics(afmFileName)
|
||||
|
||||
def getFontFiles(self):
|
||||
return [self.afmFileName, self.pfbFileName]
|
||||
|
||||
def _loadGlyphs(self, pfbFileName):
|
||||
"""Loads in binary glyph data, and finds the four length
|
||||
measurements needed for the font descriptor"""
|
||||
assert rl_isfile(pfbFileName), 'file %s not found' % pfbFileName
|
||||
d = open_and_read(pfbFileName, 'b')
|
||||
s1, l1 = _pfbCheck(0,d,PFB_ASCII,pfbFileName)
|
||||
s2, l2 = _pfbCheck(l1,d,PFB_BINARY,pfbFileName)
|
||||
s3, l3 = _pfbCheck(l2,d,PFB_ASCII,pfbFileName)
|
||||
_pfbCheck(l3,d,PFB_EOF,pfbFileName)
|
||||
self._binaryData = d[s1:l1]+d[s2:l2]+d[s3:l3]
|
||||
|
||||
self._length = len(self._binaryData)
|
||||
self._length1 = l1-s1
|
||||
self._length2 = l2-s2
|
||||
self._length3 = l3-s3
|
||||
|
||||
|
||||
def _loadMetrics(self, afmFileName):
|
||||
"""Loads in and parses font metrics"""
|
||||
#assert os.path.isfile(afmFileName), "AFM file %s not found" % afmFileName
|
||||
(topLevel, glyphData) = parseAFMFile(afmFileName)
|
||||
|
||||
self.name = topLevel['FontName']
|
||||
self.familyName = topLevel['FamilyName']
|
||||
self.ascent = topLevel.get('Ascender', 1000)
|
||||
self.descent = topLevel.get('Descender', 0)
|
||||
self.capHeight = topLevel.get('CapHeight', 1000)
|
||||
self.italicAngle = topLevel.get('ItalicAngle', 0)
|
||||
self.stemV = topLevel.get('stemV', 0)
|
||||
self.xHeight = topLevel.get('XHeight', 1000)
|
||||
|
||||
strBbox = topLevel.get('FontBBox', [0,0,1000,1000])
|
||||
tokens = string.split(strBbox)
|
||||
self.bbox = []
|
||||
for tok in tokens:
|
||||
self.bbox.append(string.atoi(tok))
|
||||
|
||||
glyphWidths = {}
|
||||
for (cid, width, name) in glyphData:
|
||||
glyphWidths[name] = width
|
||||
self.glyphWidths = glyphWidths
|
||||
self.glyphNames = glyphWidths.keys()
|
||||
self.glyphNames.sort()
|
||||
|
||||
# for font-specific encodings like Symbol, Dingbats, Carta we
|
||||
# need to make a new encoding as well....
|
||||
if topLevel.get('EncodingScheme', None) == 'FontSpecific':
|
||||
names = [None] * 256
|
||||
for (code, width, name) in glyphData:
|
||||
if code >=0 and code <=255:
|
||||
names[code] = name
|
||||
encName = self.name + 'Encoding'
|
||||
self.requiredEncoding = encName
|
||||
enc = Encoding(encName, names)
|
||||
registerEncoding(enc)
|
||||
|
||||
def addObjects(self, doc):
|
||||
"""Add whatever needed to PDF file, and return a FontDescriptor reference"""
|
||||
from reportlab.pdfbase import pdfdoc
|
||||
|
||||
fontFile = pdfdoc.PDFStream()
|
||||
fontFile.content = self._binaryData
|
||||
#fontFile.dictionary['Length'] = self._length
|
||||
fontFile.dictionary['Length1'] = self._length1
|
||||
fontFile.dictionary['Length2'] = self._length2
|
||||
fontFile.dictionary['Length3'] = self._length3
|
||||
#fontFile.filters = [pdfdoc.PDFZCompress]
|
||||
|
||||
fontFileRef = doc.Reference(fontFile, 'fontFile:' + self.pfbFileName)
|
||||
|
||||
fontDescriptor = pdfdoc.PDFDictionary({
|
||||
'Type': '/FontDescriptor',
|
||||
'Ascent':self.ascent,
|
||||
'CapHeight':self.capHeight,
|
||||
'Descent':self.descent,
|
||||
'Flags': 34,
|
||||
'FontBBox':pdfdoc.PDFArray(self.bbox),
|
||||
'FontName':pdfdoc.PDFName(self.name),
|
||||
'ItalicAngle':self.italicAngle,
|
||||
'StemV':self.stemV,
|
||||
'XHeight':self.xHeight,
|
||||
'FontFile': fontFileRef,
|
||||
})
|
||||
fontDescriptorRef = doc.Reference(fontDescriptor, 'fontDescriptor:' + self.name)
|
||||
return fontDescriptorRef
|
||||
|
||||
def registerTypeFace(face):
|
||||
assert isinstance(face, TypeFace), 'Not a TypeFace: %s' % face
|
||||
_typefaces[face.name] = face
|
||||
# HACK - bold/italic do not apply for type 1, so egister
|
||||
# all combinations of mappings.
|
||||
from reportlab.lib import fonts
|
||||
ttname = string.lower(face.name)
|
||||
if not face.name in standardFonts:
|
||||
fonts.addMapping(ttname, 0, 0, face.name)
|
||||
fonts.addMapping(ttname, 1, 0, face.name)
|
||||
fonts.addMapping(ttname, 0, 1, face.name)
|
||||
fonts.addMapping(ttname, 1, 1, face.name)
|
||||
|
||||
def registerEncoding(enc):
|
||||
assert isinstance(enc, Encoding), 'Not an Encoding: %s' % enc
|
||||
if _encodings.has_key(enc.name):
|
||||
# already got one, complain if they are not the same
|
||||
if enc.isEqual(_encodings[enc.name]):
|
||||
enc.freeze()
|
||||
else:
|
||||
raise FontError('Encoding "%s" already registered with a different name vector!' % enc.Name)
|
||||
else:
|
||||
_encodings[enc.name] = enc
|
||||
enc.freeze()
|
||||
# have not yet dealt with immutability!
|
||||
|
||||
def registerFont(font):
|
||||
"Registers a font, including setting up info for accelerated stringWidth"
|
||||
#assert isinstance(font, Font), 'Not a Font: %s' % font
|
||||
fontName = font.fontName
|
||||
_fonts[fontName] = font
|
||||
if font._multiByte:
|
||||
# CID fonts don't need to have typeface registered.
|
||||
#need to set mappings so it can go in a paragraph even if within
|
||||
# bold tags
|
||||
from reportlab.lib import fonts
|
||||
ttname = string.lower(font.fontName)
|
||||
fonts.addMapping(ttname, 0, 0, font.fontName)
|
||||
fonts.addMapping(ttname, 1, 0, font.fontName)
|
||||
fonts.addMapping(ttname, 0, 1, font.fontName)
|
||||
fonts.addMapping(ttname, 1, 1, font.fontName)
|
||||
#cannot accelerate these yet...
|
||||
else:
|
||||
if _stringWidth:
|
||||
_rl_accel.setFontInfo(string.lower(fontName),
|
||||
_dummyEncoding,
|
||||
font.face.ascent,
|
||||
font.face.descent,
|
||||
font.widths)
|
||||
|
||||
|
||||
def getTypeFace(faceName):
|
||||
"""Lazily construct known typefaces if not found"""
|
||||
try:
|
||||
return _typefaces[faceName]
|
||||
except KeyError:
|
||||
# not found, construct it if known
|
||||
if faceName in standardFonts:
|
||||
face = TypeFace(faceName)
|
||||
(face.familyName, face.bold, face.italic) = _fontdata.standardFontAttributes[faceName]
|
||||
registerTypeFace(face)
|
||||
## print 'auto-constructing type face %s with family=%s, bold=%d, italic=%d' % (
|
||||
## face.name, face.familyName, face.bold, face.italic)
|
||||
return face
|
||||
else:
|
||||
#try a brute force search
|
||||
afm = bruteForceSearchForAFM(faceName)
|
||||
if afm:
|
||||
for e in ('.pfb', '.PFB'):
|
||||
pfb = os.path.splitext(afm)[0] + e
|
||||
if rl_isfile(pfb): break
|
||||
assert rl_isfile(pfb), 'file %s not found!' % pfb
|
||||
face = EmbeddedType1Face(afm, pfb)
|
||||
registerTypeFace(face)
|
||||
return face
|
||||
else:
|
||||
raise
|
||||
|
||||
def getEncoding(encName):
|
||||
"""Lazily construct known encodings if not found"""
|
||||
try:
|
||||
return _encodings[encName]
|
||||
except KeyError:
|
||||
if encName in standardEncodings:
|
||||
enc = Encoding(encName)
|
||||
registerEncoding(enc)
|
||||
#print 'auto-constructing encoding %s' % encName
|
||||
return enc
|
||||
else:
|
||||
raise
|
||||
|
||||
def getFont(fontName):
|
||||
"""Lazily constructs known fonts if not found.
|
||||
|
||||
Names of form 'face-encoding' will be built if
|
||||
face and encoding are known. Also if the name is
|
||||
just one of the standard 14, it will make up a font
|
||||
in the default encoding."""
|
||||
try:
|
||||
return _fonts[fontName]
|
||||
except KeyError:
|
||||
#it might have a font-specific encoding e.g. Symbol
|
||||
# or Dingbats. If not, take the default.
|
||||
face = getTypeFace(fontName)
|
||||
if face.requiredEncoding:
|
||||
font = Font(fontName, fontName, face.requiredEncoding)
|
||||
else:
|
||||
font = Font(fontName, fontName, defaultEncoding)
|
||||
registerFont(font)
|
||||
return font
|
||||
|
||||
def getRegisteredFontNames():
|
||||
"Returns what's in there"
|
||||
reg = _fonts.keys()
|
||||
reg.sort()
|
||||
return reg
|
||||
|
||||
def _slowStringWidth(text, fontName, fontSize):
|
||||
"""Define this anyway so it can be tested, but whether it is used or not depends on _rl_accel"""
|
||||
font = getFont(fontName)
|
||||
return font.stringWidth(text, fontSize)
|
||||
#this is faster, but will need more special-casing for multi-byte fonts.
|
||||
#wid = getFont(fontName).widths
|
||||
#w = 0
|
||||
#for ch in text:
|
||||
# w = w + wid[ord(ch)]
|
||||
#return 0.001 * w * fontSize
|
||||
|
||||
|
||||
if _stringWidth:
|
||||
import new
|
||||
Font.stringWidth = new.instancemethod(_rl_accel._instanceStringWidth,None,Font)
|
||||
stringWidth = _stringWidth
|
||||
|
||||
#if accelerator present, make sure we at least
|
||||
#register Courier font, since it will fall back to Courier
|
||||
#as its default font.
|
||||
f = getFont('Courier')
|
||||
|
||||
|
||||
def _SWRecover(text, fontName, fontSize, encoding):
|
||||
'''This is called when _rl_accel's database doesn't know about a font.
|
||||
Currently encoding is always a dummy.
|
||||
'''
|
||||
try:
|
||||
font = getFont(fontName)
|
||||
if font._multiByte:
|
||||
return font.stringWidth(text, fontSize)
|
||||
else:
|
||||
registerFont(font)
|
||||
return _stringWidth(text,fontName,fontSize,encoding)
|
||||
except:
|
||||
warnOnce('Font %s:%s not found - using Courier:%s for widths'%(fontName,encoding,encoding))
|
||||
return _stringWidth(text,'courier',fontSize,encoding)
|
||||
|
||||
_rl_accel._SWRecover(_SWRecover)
|
||||
else:
|
||||
stringWidth = _slowStringWidth
|
||||
|
||||
def dumpFontData():
|
||||
print 'Registered Encodings:'
|
||||
keys = _encodings.keys()
|
||||
keys.sort()
|
||||
for encName in keys:
|
||||
print ' ',encName
|
||||
|
||||
print
|
||||
print 'Registered Typefaces:'
|
||||
faces = _typefaces.keys()
|
||||
faces.sort()
|
||||
for faceName in faces:
|
||||
print ' ',faceName
|
||||
|
||||
|
||||
print
|
||||
print 'Registered Fonts:'
|
||||
k = _fonts.keys()
|
||||
k.sort()
|
||||
for key in k:
|
||||
font = _fonts[key]
|
||||
print ' %s (%s/%s)' % (font.fontName, font.face.name, font.encoding.name)
|
||||
|
||||
|
||||
|
||||
def test3widths(texts):
|
||||
# checks all 3 algorithms give same answer, note speed
|
||||
import time
|
||||
for fontName in standardFonts[0:1]:
|
||||
t0 = time.time()
|
||||
for text in texts:
|
||||
l1 = _stringWidth(text, fontName, 10)
|
||||
t1 = time.time()
|
||||
print 'fast stringWidth took %0.4f' % (t1 - t0)
|
||||
|
||||
t0 = time.time()
|
||||
w = getFont(fontName).widths
|
||||
for text in texts:
|
||||
l2 = 0
|
||||
for ch in text:
|
||||
l2 = l2 + w[ord(ch)]
|
||||
t1 = time.time()
|
||||
print 'slow stringWidth took %0.4f' % (t1 - t0)
|
||||
|
||||
t0 = time.time()
|
||||
for text in texts:
|
||||
l3 = getFont(fontName).stringWidth(text, 10)
|
||||
t1 = time.time()
|
||||
print 'class lookup and stringWidth took %0.4f' % (t1 - t0)
|
||||
print
|
||||
|
||||
def testStringWidthAlgorithms():
|
||||
rawdata = open('../../rlextra/rml2pdf/doc/rml_user_guide.prep').read()
|
||||
print 'rawdata length %d' % len(rawdata)
|
||||
print 'test one huge string...'
|
||||
test3widths([rawdata])
|
||||
print
|
||||
words = string.split(rawdata)
|
||||
print 'test %d shorter strings (average length %0.2f chars)...' % (len(words), 1.0*len(rawdata)/len(words))
|
||||
test3widths(words)
|
||||
|
||||
|
||||
def test():
|
||||
helv = TypeFace('Helvetica')
|
||||
registerTypeFace(helv)
|
||||
print helv.glyphNames[0:30]
|
||||
|
||||
wombat = TypeFace('Wombat')
|
||||
print wombat.glyphNames
|
||||
registerTypeFace(wombat)
|
||||
|
||||
dumpFontData()
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
||||
testStringWidthAlgorithms()
|
|
@ -1,59 +0,0 @@
|
|||
"""
|
||||
helper for importing pdf structures into a ReportLab generated document
|
||||
"""
|
||||
from reportlab.pdfbase.pdfdoc import format
|
||||
|
||||
import string
|
||||
|
||||
class PDFPattern:
|
||||
__RefOnly__ = 1
|
||||
def __init__(self, pattern_sequence, **keywordargs):
|
||||
"""
|
||||
Description of a kind of PDF object using a pattern.
|
||||
|
||||
Pattern sequence should contain strings or singletons of form [string].
|
||||
Strings are literal strings to be used in the object.
|
||||
Singletons are names of keyword arguments to include.
|
||||
Keyword arguments can be non-instances which are substituted directly in string conversion,
|
||||
or they can be object instances in which case they should be pdfdoc.* style
|
||||
objects with a x.format(doc) method.
|
||||
Keyword arguments may be set on initialization or subsequently using __setitem__, before format.
|
||||
"constant object" instances can also be inserted in the patterns.
|
||||
"""
|
||||
self.pattern = pattern_sequence
|
||||
self.arguments = keywordargs
|
||||
from types import StringType, InstanceType
|
||||
toptypes = (StringType, InstanceType)
|
||||
for x in pattern_sequence:
|
||||
if type(x) not in toptypes:
|
||||
if len(x)!=1:
|
||||
raise ValueError, "sequence elts must be strings or singletons containing strings: "+repr(x)
|
||||
if type(x[0]) is not StringType:
|
||||
raise ValueError, "Singletons must contain strings or instances only: "+repr(x[0])
|
||||
def __setitem__(self, item, value):
|
||||
self.arguments[item] = value
|
||||
def __getitem__(self, item):
|
||||
return self.arguments[item]
|
||||
def format(self, document):
|
||||
L = []
|
||||
arguments = self.arguments
|
||||
from types import StringType, InstanceType
|
||||
for x in self.pattern:
|
||||
tx = type(x)
|
||||
if tx is StringType:
|
||||
L.append(x)
|
||||
elif tx is InstanceType:
|
||||
L.append( x.format(document) )
|
||||
else:
|
||||
name = x[0]
|
||||
value = arguments.get(name, None)
|
||||
if value is None:
|
||||
raise ValueError, "%s value not defined" % repr(name)
|
||||
if type(value) is InstanceType:
|
||||
#L.append( value.format(document) )
|
||||
L.append(format(value, document))
|
||||
else:
|
||||
L.append( str(value) )
|
||||
return string.join(L, "")
|
||||
|
||||
|
|
@ -1,453 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfbase/pdfutils.py
|
||||
__version__=''' $Id$ '''
|
||||
__doc__=''
|
||||
# pdfutils.py - everything to do with images, streams,
|
||||
# compression, and some constants
|
||||
|
||||
import os
|
||||
from reportlab import rl_config
|
||||
from string import join, replace, strip, split
|
||||
from reportlab.lib.utils import getStringIO, ImageReader
|
||||
|
||||
LINEEND = '\015\012'
|
||||
|
||||
def _chunker(src,dst=[],chunkSize=60):
|
||||
for i in xrange(0,len(src),chunkSize):
|
||||
dst.append(src[i:i+chunkSize])
|
||||
return dst
|
||||
|
||||
##########################################################
|
||||
#
|
||||
# Image compression helpers. Preprocessing a directory
|
||||
# of images will offer a vast speedup.
|
||||
#
|
||||
##########################################################
|
||||
|
||||
def cacheImageFile(filename, returnInMemory=0, IMG=None):
|
||||
"Processes image as if for encoding, saves to a file with .a85 extension."
|
||||
|
||||
from reportlab.lib.utils import open_for_read
|
||||
import zlib
|
||||
|
||||
cachedname = os.path.splitext(filename)[0] + '.a85'
|
||||
if filename==cachedname:
|
||||
if cachedImageExists(filename):
|
||||
if returnInMemory: return split(open_for_read(cachedname).read(),LINEEND)[:-1]
|
||||
else:
|
||||
raise IOError, 'No such cached image %s' % filename
|
||||
else:
|
||||
img = ImageReader(filename)
|
||||
if IMG is not None: IMG.append(img)
|
||||
|
||||
imgwidth, imgheight = img.getSize()
|
||||
raw = img.getRGBData()
|
||||
|
||||
code = []
|
||||
# this describes what is in the image itself
|
||||
code.append('BI')
|
||||
code.append('/W %s /H %s /BPC 8 /CS /RGB /F [/A85 /Fl]' % (imgwidth, imgheight))
|
||||
code.append('ID')
|
||||
#use a flate filter and Ascii Base 85
|
||||
assert(len(raw) == imgwidth * imgheight, "Wrong amount of data for image")
|
||||
compressed = zlib.compress(raw) #this bit is very fast...
|
||||
encoded = _AsciiBase85Encode(compressed) #...sadly this may not be
|
||||
|
||||
#append in blocks of 60 characters
|
||||
_chunker(encoded,code)
|
||||
|
||||
code.append('EI')
|
||||
if returnInMemory: return code
|
||||
|
||||
#save it to a file
|
||||
f = open(cachedname,'wb')
|
||||
f.write(join(code, LINEEND)+LINEEND)
|
||||
f.close()
|
||||
if rl_config.verbose:
|
||||
print 'cached image as %s' % cachedname
|
||||
|
||||
|
||||
def preProcessImages(spec):
|
||||
"""Preprocesses one or more image files.
|
||||
|
||||
Accepts either a filespec ('C:\mydir\*.jpg') or a list
|
||||
of image filenames, crunches them all to save time. Run this
|
||||
to save huge amounts of time when repeatedly building image
|
||||
documents."""
|
||||
|
||||
import types, glob
|
||||
|
||||
if type(spec) is types.StringType:
|
||||
filelist = glob.glob(spec)
|
||||
else: #list or tuple OK
|
||||
filelist = spec
|
||||
|
||||
for filename in filelist:
|
||||
if cachedImageExists(filename):
|
||||
if rl_config.verbose:
|
||||
print 'cached version of %s already exists' % filename
|
||||
else:
|
||||
cacheImageFile(filename)
|
||||
|
||||
|
||||
def cachedImageExists(filename):
|
||||
"""Determines if a cached image already exists for a given file.
|
||||
|
||||
Determines if a cached image exists which has the same name
|
||||
and equal or newer date to the given file."""
|
||||
cachedname = os.path.splitext(filename)[0] + '.a85'
|
||||
if os.path.isfile(cachedname):
|
||||
#see if it is newer
|
||||
original_date = os.stat(filename)[8]
|
||||
cached_date = os.stat(cachedname)[8]
|
||||
if original_date > cached_date:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
##############################################################
|
||||
#
|
||||
# PDF Helper functions
|
||||
#
|
||||
##############################################################
|
||||
|
||||
try:
|
||||
from _rl_accel import escapePDF, _instanceEscapePDF
|
||||
_escape = escapePDF
|
||||
except ImportError:
|
||||
try:
|
||||
from reportlab.lib._rl_accel import escapePDF, _instanceEscapePDF
|
||||
_escape = escapePDF
|
||||
except ImportError:
|
||||
_instanceEscapePDF=None
|
||||
if rl_config.sys_version>='2.1':
|
||||
_ESCAPEDICT={}
|
||||
for c in range(0,256):
|
||||
if c<32 or c>=127:
|
||||
_ESCAPEDICT[chr(c)]= '\\%03o' % c
|
||||
elif c in (ord('\\'),ord('('),ord(')')):
|
||||
_ESCAPEDICT[chr(c)] = '\\'+chr(c)
|
||||
else:
|
||||
_ESCAPEDICT[chr(c)] = chr(c)
|
||||
del c
|
||||
#Michael Hudson donated this
|
||||
def _escape(s):
|
||||
return join(map(lambda c, d=_ESCAPEDICT: d[c],s),'')
|
||||
else:
|
||||
def _escape(s):
|
||||
"""Escapes some PDF symbols (in fact, parenthesis).
|
||||
PDF escapes are almost like Python ones, but brackets
|
||||
need slashes before them too. Uses Python's repr function
|
||||
and chops off the quotes first."""
|
||||
s = repr(s)[1:-1]
|
||||
s = replace(s, '(','\(')
|
||||
s = replace(s, ')','\)')
|
||||
return s
|
||||
|
||||
def _normalizeLineEnds(text,desired=LINEEND):
|
||||
"""Normalizes different line end character(s).
|
||||
|
||||
Ensures all instances of CR, LF and CRLF end up as
|
||||
the specified one."""
|
||||
unlikely = '\000\001\002\003'
|
||||
text = replace(text, '\015\012', unlikely)
|
||||
text = replace(text, '\015', unlikely)
|
||||
text = replace(text, '\012', unlikely)
|
||||
text = replace(text, unlikely, desired)
|
||||
return text
|
||||
|
||||
|
||||
def _AsciiHexEncode(input):
|
||||
"""Encodes input using ASCII-Hex coding.
|
||||
|
||||
This is a verbose encoding used for binary data within
|
||||
a PDF file. One byte binary becomes two bytes of ASCII.
|
||||
Helper function used by images."""
|
||||
output = getStringIO()
|
||||
for char in input:
|
||||
output.write('%02x' % ord(char))
|
||||
output.write('>')
|
||||
return output.getvalue()
|
||||
|
||||
|
||||
def _AsciiHexDecode(input):
|
||||
"""Decodes input using ASCII-Hex coding.
|
||||
|
||||
Not used except to provide a test of the inverse function."""
|
||||
|
||||
#strip out all whitespace
|
||||
stripped = join(split(input),'')
|
||||
assert stripped[-1] == '>', 'Invalid terminator for Ascii Hex Stream'
|
||||
stripped = stripped[:-1] #chop off terminator
|
||||
assert len(stripped) % 2 == 0, 'Ascii Hex stream has odd number of bytes'
|
||||
|
||||
i = 0
|
||||
output = getStringIO()
|
||||
while i < len(stripped):
|
||||
twobytes = stripped[i:i+2]
|
||||
output.write(chr(eval('0x'+twobytes)))
|
||||
i = i + 2
|
||||
return output.getvalue()
|
||||
|
||||
|
||||
if 1: # for testing always define this
|
||||
def _AsciiBase85EncodePYTHON(input):
|
||||
"""Encodes input using ASCII-Base85 coding.
|
||||
|
||||
This is a compact encoding used for binary data within
|
||||
a PDF file. Four bytes of binary data become five bytes of
|
||||
ASCII. This is the default method used for encoding images."""
|
||||
outstream = getStringIO()
|
||||
# special rules apply if not a multiple of four bytes.
|
||||
whole_word_count, remainder_size = divmod(len(input), 4)
|
||||
cut = 4 * whole_word_count
|
||||
body, lastbit = input[0:cut], input[cut:]
|
||||
|
||||
for i in range(whole_word_count):
|
||||
offset = i*4
|
||||
b1 = ord(body[offset])
|
||||
b2 = ord(body[offset+1])
|
||||
b3 = ord(body[offset+2])
|
||||
b4 = ord(body[offset+3])
|
||||
|
||||
if b1<128:
|
||||
num = (((((b1<<8)|b2)<<8)|b3)<<8)|b4
|
||||
else:
|
||||
num = 16777216L * b1 + 65536 * b2 + 256 * b3 + b4
|
||||
|
||||
if num == 0:
|
||||
#special case
|
||||
outstream.write('z')
|
||||
else:
|
||||
#solve for five base-85 numbers
|
||||
temp, c5 = divmod(num, 85)
|
||||
temp, c4 = divmod(temp, 85)
|
||||
temp, c3 = divmod(temp, 85)
|
||||
c1, c2 = divmod(temp, 85)
|
||||
assert ((85**4) * c1) + ((85**3) * c2) + ((85**2) * c3) + (85*c4) + c5 == num, 'dodgy code!'
|
||||
outstream.write(chr(c1+33))
|
||||
outstream.write(chr(c2+33))
|
||||
outstream.write(chr(c3+33))
|
||||
outstream.write(chr(c4+33))
|
||||
outstream.write(chr(c5+33))
|
||||
|
||||
# now we do the final bit at the end. I repeated this separately as
|
||||
# the loop above is the time-critical part of a script, whereas this
|
||||
# happens only once at the end.
|
||||
|
||||
#encode however many bytes we have as usual
|
||||
if remainder_size > 0:
|
||||
while len(lastbit) < 4:
|
||||
lastbit = lastbit + '\000'
|
||||
b1 = ord(lastbit[0])
|
||||
b2 = ord(lastbit[1])
|
||||
b3 = ord(lastbit[2])
|
||||
b4 = ord(lastbit[3])
|
||||
|
||||
num = 16777216L * b1 + 65536 * b2 + 256 * b3 + b4
|
||||
|
||||
#solve for c1..c5
|
||||
temp, c5 = divmod(num, 85)
|
||||
temp, c4 = divmod(temp, 85)
|
||||
temp, c3 = divmod(temp, 85)
|
||||
c1, c2 = divmod(temp, 85)
|
||||
|
||||
#print 'encoding: %d %d %d %d -> %d -> %d %d %d %d %d' % (
|
||||
# b1,b2,b3,b4,num,c1,c2,c3,c4,c5)
|
||||
lastword = chr(c1+33) + chr(c2+33) + chr(c3+33) + chr(c4+33) + chr(c5+33)
|
||||
#write out most of the bytes.
|
||||
outstream.write(lastword[0:remainder_size + 1])
|
||||
|
||||
#terminator code for ascii 85
|
||||
outstream.write('~>')
|
||||
return outstream.getvalue()
|
||||
|
||||
def _AsciiBase85DecodePYTHON(input):
|
||||
"""Decodes input using ASCII-Base85 coding.
|
||||
|
||||
This is not used - Acrobat Reader decodes for you
|
||||
- but a round trip is essential for testing."""
|
||||
outstream = getStringIO()
|
||||
#strip all whitespace
|
||||
stripped = join(split(input),'')
|
||||
#check end
|
||||
assert stripped[-2:] == '~>', 'Invalid terminator for Ascii Base 85 Stream'
|
||||
stripped = stripped[:-2] #chop off terminator
|
||||
|
||||
#may have 'z' in it which complicates matters - expand them
|
||||
stripped = replace(stripped,'z','!!!!!')
|
||||
# special rules apply if not a multiple of five bytes.
|
||||
whole_word_count, remainder_size = divmod(len(stripped), 5)
|
||||
#print '%d words, %d leftover' % (whole_word_count, remainder_size)
|
||||
#assert remainder_size <> 1, 'invalid Ascii 85 stream!'
|
||||
cut = 5 * whole_word_count
|
||||
body, lastbit = stripped[0:cut], stripped[cut:]
|
||||
|
||||
for i in range(whole_word_count):
|
||||
offset = i*5
|
||||
c1 = ord(body[offset]) - 33
|
||||
c2 = ord(body[offset+1]) - 33
|
||||
c3 = ord(body[offset+2]) - 33
|
||||
c4 = ord(body[offset+3]) - 33
|
||||
c5 = ord(body[offset+4]) - 33
|
||||
|
||||
num = ((85L**4) * c1) + ((85**3) * c2) + ((85**2) * c3) + (85*c4) + c5
|
||||
|
||||
temp, b4 = divmod(num,256)
|
||||
temp, b3 = divmod(temp,256)
|
||||
b1, b2 = divmod(temp, 256)
|
||||
|
||||
assert num == 16777216 * b1 + 65536 * b2 + 256 * b3 + b4, 'dodgy code!'
|
||||
outstream.write(chr(b1))
|
||||
outstream.write(chr(b2))
|
||||
outstream.write(chr(b3))
|
||||
outstream.write(chr(b4))
|
||||
|
||||
#decode however many bytes we have as usual
|
||||
if remainder_size > 0:
|
||||
while len(lastbit) < 5:
|
||||
lastbit = lastbit + '!'
|
||||
c1 = ord(lastbit[0]) - 33
|
||||
c2 = ord(lastbit[1]) - 33
|
||||
c3 = ord(lastbit[2]) - 33
|
||||
c4 = ord(lastbit[3]) - 33
|
||||
c5 = ord(lastbit[4]) - 33
|
||||
num = (((85*c1+c2)*85+c3)*85+c4)*85L + (c5
|
||||
+(0,0,0xFFFFFF,0xFFFF,0xFF)[remainder_size])
|
||||
temp, b4 = divmod(num,256)
|
||||
temp, b3 = divmod(temp,256)
|
||||
b1, b2 = divmod(temp, 256)
|
||||
assert num == 16777216 * b1 + 65536 * b2 + 256 * b3 + b4, 'dodgy code!'
|
||||
#print 'decoding: %d %d %d %d %d -> %d -> %d %d %d %d' % (
|
||||
# c1,c2,c3,c4,c5,num,b1,b2,b3,b4)
|
||||
|
||||
#the last character needs 1 adding; the encoding loses
|
||||
#data by rounding the number to x bytes, and when
|
||||
#divided repeatedly we get one less
|
||||
if remainder_size == 2:
|
||||
lastword = chr(b1)
|
||||
elif remainder_size == 3:
|
||||
lastword = chr(b1) + chr(b2)
|
||||
elif remainder_size == 4:
|
||||
lastword = chr(b1) + chr(b2) + chr(b3)
|
||||
else:
|
||||
lastword = ''
|
||||
outstream.write(lastword)
|
||||
|
||||
#terminator code for ascii 85
|
||||
return outstream.getvalue()
|
||||
|
||||
try:
|
||||
from _rl_accel import _AsciiBase85Encode # builtin or on the path
|
||||
except ImportError:
|
||||
try:
|
||||
from reportlab.lib._rl_accel import _AsciiBase85Encode # where we think it should be
|
||||
except ImportError:
|
||||
_AsciiBase85Encode = _AsciiBase85EncodePYTHON
|
||||
|
||||
try:
|
||||
from _rl_accel import _AsciiBase85Decode # builtin or on the path
|
||||
except ImportError:
|
||||
try:
|
||||
from reportlab.lib._rl_accel import _AsciiBase85Decode # where we think it should be
|
||||
except ImportError:
|
||||
_AsciiBase85Decode = _AsciiBase85DecodePYTHON
|
||||
|
||||
def _wrap(input, columns=60):
|
||||
"Wraps input at a given column size by inserting LINEEND characters."
|
||||
|
||||
output = []
|
||||
length = len(input)
|
||||
i = 0
|
||||
pos = columns * i
|
||||
while pos < length:
|
||||
output.append(input[pos:pos+columns])
|
||||
i = i + 1
|
||||
pos = columns * i
|
||||
|
||||
return join(output, LINEEND)
|
||||
|
||||
|
||||
#########################################################################
|
||||
#
|
||||
# JPEG processing code - contributed by Eric Johnson
|
||||
#
|
||||
#########################################################################
|
||||
|
||||
# Read data from the JPEG file. We should probably be using PIL to
|
||||
# get this information for us -- but this way is more fun!
|
||||
# Returns (width, height, color components) as a triple
|
||||
# This is based on Thomas Merz's code from GhostScript (viewjpeg.ps)
|
||||
def readJPEGInfo(image):
|
||||
"Read width, height and number of components from open JPEG file."
|
||||
|
||||
import struct
|
||||
|
||||
#Acceptable JPEG Markers:
|
||||
# SROF0=baseline, SOF1=extended sequential or SOF2=progressive
|
||||
validMarkers = [0xC0, 0xC1, 0xC2]
|
||||
|
||||
#JPEG markers without additional parameters
|
||||
noParamMarkers = \
|
||||
[ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0x01 ]
|
||||
|
||||
#Unsupported JPEG Markers
|
||||
unsupportedMarkers = \
|
||||
[ 0xC3, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF ]
|
||||
|
||||
#read JPEG marker segments until we find SOFn marker or EOF
|
||||
done = 0
|
||||
while not done:
|
||||
x = struct.unpack('B', image.read(1))
|
||||
if x[0] == 0xFF: #found marker
|
||||
x = struct.unpack('B', image.read(1))
|
||||
#print "Marker: ", '%0.2x' % x[0]
|
||||
#check marker type is acceptable and process it
|
||||
if x[0] in validMarkers:
|
||||
image.seek(2, 1) #skip segment length
|
||||
x = struct.unpack('B', image.read(1)) #data precision
|
||||
if x[0] != 8:
|
||||
raise 'PDFError', ' JPEG must have 8 bits per component'
|
||||
y = struct.unpack('BB', image.read(2))
|
||||
height = (y[0] << 8) + y[1]
|
||||
y = struct.unpack('BB', image.read(2))
|
||||
width = (y[0] << 8) + y[1]
|
||||
y = struct.unpack('B', image.read(1))
|
||||
color = y[0]
|
||||
return width, height, color
|
||||
done = 1
|
||||
elif x[0] in unsupportedMarkers:
|
||||
raise 'PDFError', ' Unsupported JPEG marker: %0.2x' % x[0]
|
||||
elif x[0] not in noParamMarkers:
|
||||
#skip segments with parameters
|
||||
#read length and skip the data
|
||||
x = struct.unpack('BB', image.read(2))
|
||||
image.seek( (x[0] << 8) + x[1] - 2, 1)
|
||||
|
||||
class _fusc:
|
||||
def __init__(self,k, n):
|
||||
assert k, 'Argument k should be a non empty string'
|
||||
self._k = k
|
||||
self._klen = len(k)
|
||||
self._n = int(n) or 7
|
||||
|
||||
def encrypt(self,s):
|
||||
return self.__rotate(_AsciiBase85Encode(''.join(map(chr,self.__fusc(map(ord,s))))),self._n)
|
||||
|
||||
def decrypt(self,s):
|
||||
return ''.join(map(chr,self.__fusc(map(ord,_AsciiBase85Decode(self.__rotate(s,-self._n))))))
|
||||
|
||||
def __rotate(self,s,n):
|
||||
l = len(s)
|
||||
if n<0: n = l+n
|
||||
n %= l
|
||||
if not n: return s
|
||||
return s[-n:]+s[:l-n]
|
||||
|
||||
def __fusc(self,s):
|
||||
slen = len(s)
|
||||
return map(lambda x,y: x ^ y,s,map(ord,((int(slen/self._klen)+1)*self._k)[:slen]))
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfgen/__init__.py
|
||||
__version__=''' $Id$ '''
|
||||
__doc__=''
|
File diff suppressed because it is too large
Load Diff
|
@ -1,93 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfgen/pathobject.py
|
||||
__version__=''' $Id$ '''
|
||||
__doc__="""
|
||||
PDFPathObject is an efficient way to draw paths on a Canvas. Do not
|
||||
instantiate directly, obtain one from the Canvas instead.
|
||||
|
||||
Progress Reports:
|
||||
8.83, 2000-01-13, gmcm:
|
||||
created from pdfgen.py
|
||||
"""
|
||||
|
||||
import string
|
||||
from reportlab.pdfgen import pdfgeom
|
||||
from reportlab.lib.utils import fp_str
|
||||
|
||||
|
||||
class PDFPathObject:
|
||||
"""Represents a graphic path. There are certain 'modes' to PDF
|
||||
drawing, and making a separate object to expose Path operations
|
||||
ensures they are completed with no run-time overhead. Ask
|
||||
the Canvas for a PDFPath with getNewPathObject(); moveto/lineto/
|
||||
curveto wherever you want; add whole shapes; and then add it back
|
||||
into the canvas with one of the relevant operators.
|
||||
|
||||
Path objects are probably not long, so we pack onto one line"""
|
||||
|
||||
def __init__(self):
|
||||
self._code = []
|
||||
self._code.append('n') #newpath
|
||||
|
||||
def getCode(self):
|
||||
"pack onto one line; used internally"
|
||||
return string.join(self._code, ' ')
|
||||
|
||||
def moveTo(self, x, y):
|
||||
self._code.append('%s m' % fp_str(x,y))
|
||||
|
||||
def lineTo(self, x, y):
|
||||
self._code.append('%s l' % fp_str(x,y))
|
||||
|
||||
def curveTo(self, x1, y1, x2, y2, x3, y3):
|
||||
self._code.append('%s c' % fp_str(x1, y1, x2, y2, x3, y3))
|
||||
|
||||
def arc(self, x1,y1, x2,y2, startAng=0, extent=90):
|
||||
"""Contributed to piddlePDF by Robert Kern, 28/7/99.
|
||||
Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
|
||||
starting at startAng degrees and covering extent degrees. Angles
|
||||
start with 0 to the right (+x) and increase counter-clockwise.
|
||||
These should have x1<x2 and y1<y2.
|
||||
|
||||
The algorithm is an elliptical generalization of the formulae in
|
||||
Jim Fitzsimmon's TeX tutorial <URL: http://www.tinaja.com/bezarc1.pdf>."""
|
||||
|
||||
pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent)
|
||||
#move to first point
|
||||
self._code.append('%s m' % fp_str(pointList[0][:2]))
|
||||
for curve in pointList:
|
||||
self._code.append('%s c' % fp_str(curve[2:]))
|
||||
|
||||
def arcTo(self, x1,y1, x2,y2, startAng=0, extent=90):
|
||||
"""Like arc, but draws a line from the current point to
|
||||
the start if the start is not the current point."""
|
||||
pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent)
|
||||
self._code.append('%s l' % fp_str(pointList[0][:2]))
|
||||
for curve in pointList:
|
||||
self._code.append('%s c' % fp_str(curve[2:]))
|
||||
|
||||
def rect(self, x, y, width, height):
|
||||
"""Adds a rectangle to the path"""
|
||||
self._code.append('%s re' % fp_str((x, y, width, height)))
|
||||
|
||||
def ellipse(self, x, y, width, height):
|
||||
"""adds an ellipse to the path"""
|
||||
pointList = pdfgeom.bezierArc(x, y, x + width,y + height, 0, 360)
|
||||
self._code.append('%s m' % fp_str(pointList[0][:2]))
|
||||
for curve in pointList:
|
||||
self._code.append('%s c' % fp_str(curve[2:]))
|
||||
|
||||
def circle(self, x_cen, y_cen, r):
|
||||
"""adds a circle to the path"""
|
||||
x1 = x_cen - r
|
||||
#x2 = x_cen + r
|
||||
y1 = y_cen - r
|
||||
#y2 = y_cen + r
|
||||
width = height = 2*r
|
||||
#self.ellipse(x_cen - r, y_cen - r, x_cen + r, y_cen + r)
|
||||
self.ellipse(x1, y1, width, height)
|
||||
|
||||
def close(self):
|
||||
"draws a line back to where it started"
|
||||
self._code.append('h')
|
|
@ -1,77 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfgen/pdfgeom.py
|
||||
__version__=''' $Id$ '''
|
||||
__doc__="""
|
||||
This module includes any mathematical methods needed for PIDDLE.
|
||||
It should have no dependencies beyond the Python library.
|
||||
|
||||
So far, just Robert Kern's bezierArc.
|
||||
"""
|
||||
|
||||
from math import sin, cos, pi, ceil
|
||||
|
||||
|
||||
def bezierArc(x1,y1, x2,y2, startAng=0, extent=90):
|
||||
"""bezierArc(x1,y1, x2,y2, startAng=0, extent=90) --> List of Bezier
|
||||
curve control points.
|
||||
|
||||
(x1, y1) and (x2, y2) are the corners of the enclosing rectangle. The
|
||||
coordinate system has coordinates that increase to the right and down.
|
||||
Angles, measured in degress, start with 0 to the right (the positive X
|
||||
axis) and increase counter-clockwise. The arc extends from startAng
|
||||
to startAng+extent. I.e. startAng=0 and extent=180 yields an openside-down
|
||||
semi-circle.
|
||||
|
||||
The resulting coordinates are of the form (x1,y1, x2,y2, x3,y3, x4,y4)
|
||||
such that the curve goes from (x1, y1) to (x4, y4) with (x2, y2) and
|
||||
(x3, y3) as their respective Bezier control points."""
|
||||
|
||||
x1,y1, x2,y2 = min(x1,x2), max(y1,y2), max(x1,x2), min(y1,y2)
|
||||
|
||||
if abs(extent) <= 90:
|
||||
arcList = [startAng]
|
||||
fragAngle = float(extent)
|
||||
Nfrag = 1
|
||||
else:
|
||||
arcList = []
|
||||
Nfrag = int(ceil(abs(extent)/90.))
|
||||
fragAngle = float(extent) / Nfrag
|
||||
|
||||
x_cen = (x1+x2)/2.
|
||||
y_cen = (y1+y2)/2.
|
||||
rx = (x2-x1)/2.
|
||||
ry = (y2-y1)/2.
|
||||
halfAng = fragAngle * pi / 360.
|
||||
kappa = abs(4. / 3. * (1. - cos(halfAng)) / sin(halfAng))
|
||||
|
||||
if fragAngle < 0:
|
||||
sign = -1
|
||||
else:
|
||||
sign = 1
|
||||
|
||||
pointList = []
|
||||
|
||||
for i in range(Nfrag):
|
||||
theta0 = (startAng + i*fragAngle) * pi / 180.
|
||||
theta1 = (startAng + (i+1)*fragAngle) *pi / 180.
|
||||
if fragAngle > 0:
|
||||
pointList.append((x_cen + rx * cos(theta0),
|
||||
y_cen - ry * sin(theta0),
|
||||
x_cen + rx * (cos(theta0) - kappa * sin(theta0)),
|
||||
y_cen - ry * (sin(theta0) + kappa * cos(theta0)),
|
||||
x_cen + rx * (cos(theta1) + kappa * sin(theta1)),
|
||||
y_cen - ry * (sin(theta1) - kappa * cos(theta1)),
|
||||
x_cen + rx * cos(theta1),
|
||||
y_cen - ry * sin(theta1)))
|
||||
else:
|
||||
pointList.append((x_cen + rx * cos(theta0),
|
||||
y_cen - ry * sin(theta0),
|
||||
x_cen + rx * (cos(theta0) + kappa * sin(theta0)),
|
||||
y_cen - ry * (sin(theta0) - kappa * cos(theta0)),
|
||||
x_cen + rx * (cos(theta1) - kappa * sin(theta1)),
|
||||
y_cen - ry * (sin(theta1) + kappa * cos(theta1)),
|
||||
x_cen + rx * cos(theta1),
|
||||
y_cen - ry * sin(theta1)))
|
||||
|
||||
return pointList
|
|
@ -1,186 +0,0 @@
|
|||
#Copyright ReportLab Europe Ltd. 2000-2004
|
||||
#see license.txt for license details
|
||||
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfgen/pdfimages.py
|
||||
__version__=''' $Id$ '''
|
||||
__doc__="""
|
||||
Image functionality sliced out of canvas.py for generalization
|
||||
"""
|
||||
|
||||
import os
|
||||
import string
|
||||
from types import StringType
|
||||
import reportlab
|
||||
from reportlab.pdfbase import pdfutils
|
||||
from reportlab.pdfbase import pdfdoc
|
||||
from reportlab.lib.utils import fp_str, getStringIO
|
||||
from reportlab.lib.utils import import_zlib, haveImages
|
||||
|
||||
|
||||
class PDFImage:
|
||||
"""Wrapper around different "image sources". You can make images
|
||||
from a PIL Image object, a filename (in which case it uses PIL),
|
||||
an image we previously cached (optimisation, hardly used these
|
||||
days) or a JPEG (which PDF supports natively)."""
|
||||
|
||||
def __init__(self, image, x,y, width=None, height=None, caching=0):
|
||||
self.image = image
|
||||
self.point = (x,y)
|
||||
self.dimensions = (width, height)
|
||||
self.filename = None
|
||||
self.imageCaching = caching
|
||||
# the following facts need to be determined,
|
||||
# whatever the source. Declare what they are
|
||||
# here for clarity.
|
||||
self.colorSpace = 'DeviceRGB'
|
||||
self.bitsPerComponent = 8
|
||||
self.filters = []
|
||||
self.source = None # JPEG or PIL, set later
|
||||
self.getImageData()
|
||||
|
||||
def jpg_imagedata(self):
|
||||
#directly process JPEG files
|
||||
#open file, needs some error handling!!
|
||||
fp = open(self.image, 'rb')
|
||||
result = self._jpg_imagedata(fp)
|
||||
fp.close()
|
||||
return result
|
||||
|
||||
def _jpg_imagedata(self,imageFile):
|
||||
self.source = 'JPEG'
|
||||
info = pdfutils.readJPEGInfo(imageFile)
|
||||
imgwidth, imgheight = info[0], info[1]
|
||||
if info[2] == 1:
|
||||
colorSpace = 'DeviceGray'
|
||||
elif info[2] == 3:
|
||||
colorSpace = 'DeviceRGB'
|
||||
else: #maybe should generate an error, is this right for CMYK?
|
||||
colorSpace = 'DeviceCMYK'
|
||||
imageFile.seek(0) #reset file pointer
|
||||
imagedata = []
|
||||
#imagedata.append('BI /Width %d /Height /BitsPerComponent 8 /ColorSpace /%s /Filter [/Filter [ /ASCII85Decode /DCTDecode] ID' % (info[0], info[1], colorSpace))
|
||||
imagedata.append('BI /W %d /H %d /BPC 8 /CS /%s /F [/A85 /DCT] ID' % (imgwidth, imgheight, colorSpace))
|
||||
#write in blocks of (??) 60 characters per line to a list
|
||||
compressed = imageFile.read()
|
||||
encoded = pdfutils._AsciiBase85Encode(compressed)
|
||||
pdfutils._chunker(encoded,imagedata)
|
||||
imagedata.append('EI')
|
||||
return (imagedata, imgwidth, imgheight)
|
||||
|
||||
def cache_imagedata(self):
|
||||
image = self.image
|
||||
if not pdfutils.cachedImageExists(image):
|
||||
zlib = import_zlib()
|
||||
if not zlib: return
|
||||
if not haveImages: return
|
||||
pdfutils.cacheImageFile(image)
|
||||
|
||||
#now we have one cached, slurp it in
|
||||
cachedname = os.path.splitext(image)[0] + '.a85'
|
||||
imagedata = open(cachedname,'rb').readlines()
|
||||
#trim off newlines...
|
||||
imagedata = map(string.strip, imagedata)
|
||||
return imagedata
|
||||
|
||||
def PIL_imagedata(self):
|
||||
image = self.image
|
||||
if image.format=='JPEG':
|
||||
fp=image.fp
|
||||
fp.seek(0)
|
||||
return self._jpg_imagedata(fp)
|
||||
self.source = 'PIL'
|
||||
zlib = import_zlib()
|
||||
if not zlib: return
|
||||
myimage = image.convert('RGB')
|
||||
imgwidth, imgheight = myimage.size
|
||||
|
||||
# this describes what is in the image itself
|
||||
# *NB* according to the spec you can only use the short form in inline images
|
||||
#imagedata=['BI /Width %d /Height /BitsPerComponent 8 /ColorSpace /%s /Filter [/Filter [ /ASCII85Decode /FlateDecode] ID]' % (imgwidth, imgheight,'RGB')]
|
||||
imagedata=['BI /W %d /H %d /BPC 8 /CS /RGB /F [/A85 /Fl] ID' % (imgwidth, imgheight)]
|
||||
|
||||
#use a flate filter and Ascii Base 85 to compress
|
||||
raw = myimage.tostring()
|
||||
assert(len(raw) == imgwidth * imgheight, "Wrong amount of data for image")
|
||||
compressed = zlib.compress(raw) #this bit is very fast...
|
||||
encoded = pdfutils._AsciiBase85Encode(compressed) #...sadly this may not be
|
||||
#append in blocks of 60 characters
|
||||
pdfutils._chunker(encoded,imagedata)
|
||||
imagedata.append('EI')
|
||||
return (imagedata, imgwidth, imgheight)
|
||||
|
||||
def getImageData(self):
|
||||
"Gets data, height, width - whatever type of image"
|
||||
image = self.image
|
||||
(width, height) = self.dimensions
|
||||
|
||||
if type(image) == StringType:
|
||||
self.filename = image
|
||||
if os.path.splitext(image)[1] in ['.jpg', '.JPG', '.jpeg', '.JPEG']:
|
||||
(imagedata, imgwidth, imgheight) = self.jpg_imagedata()
|
||||
else:
|
||||
if not self.imageCaching:
|
||||
imagedata = pdfutils.cacheImageFile(image,returnInMemory=1)
|
||||
else:
|
||||
imagedata = self.cache_imagedata()
|
||||
#parse line two for width, height
|
||||
words = string.split(imagedata[1])
|
||||
imgwidth = string.atoi(words[1])
|
||||
imgheight = string.atoi(words[3])
|
||||
else:
|
||||
import sys
|
||||
if sys.platform[0:4] == 'java':
|
||||
#jython, PIL not available
|
||||
(imagedata, imgwidth, imgheight) = self.JAVA_imagedata()
|
||||
else:
|
||||
(imagedata, imgwidth, imgheight) = self.PIL_imagedata()
|
||||
#now build the PDF for the image.
|
||||
if not width:
|
||||
width = imgwidth
|
||||
if not height:
|
||||
height = imgheight
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.imageData = imagedata
|
||||
|
||||
def drawInlineImage(self, canvas): #, image, x,y, width=None,height=None):
|
||||
"""Draw an Image into the specified rectangle. If width and
|
||||
height are omitted, they are calculated from the image size.
|
||||
Also allow file names as well as images. This allows a
|
||||
caching mechanism"""
|
||||
(x,y) = self.point
|
||||
# this says where and how big to draw it
|
||||
if not canvas.bottomup: y = y+self.height
|
||||
canvas._code.append('q %s 0 0 %s cm' % (fp_str(self.width), fp_str(self.height, x, y)))
|
||||
# self._code.extend(imagedata) if >=python-1.5.2
|
||||
for line in self.imageData:
|
||||
canvas._code.append(line)
|
||||
canvas._code.append('Q')
|
||||
|
||||
def format(self, document):
|
||||
"""Allow it to be used within pdfdoc framework. This only
|
||||
defines how it is stored, not how it is drawn later."""
|
||||
|
||||
dict = pdfdoc.PDFDictionary()
|
||||
dict['Type'] = '/XObject'
|
||||
dict['Subtype'] = '/Image'
|
||||
dict['Width'] = self.width
|
||||
dict['Height'] = self.height
|
||||
dict['BitsPerComponent'] = 8
|
||||
dict['ColorSpace'] = pdfdoc.PDFName(self.colorSpace)
|
||||
content = string.join(self.imageData[3:-1], '\n') + '\n'
|
||||
strm = pdfdoc.PDFStream(dictionary=dict, content=content)
|
||||
return strm.format(document)
|
||||
|
||||
if __name__=='__main__':
|
||||
srcfile = os.path.join(
|
||||
os.path.dirname(reportlab.__file__),
|
||||
'test',
|
||||
'pythonpowered.gif'
|
||||
)
|
||||
assert os.path.isfile(srcfile), 'image not found'
|
||||
pdfdoc.LongFormat = 1
|
||||
img = PDFImage(srcfile, 100, 100)
|
||||
import pprint
|
||||
doc = pdfdoc.PDFDocument()
|
||||
print 'source=',img.source
|
||||
print img.format(doc)
|
|
@ -1,309 +0,0 @@
|
|||
# a Pythonesque Canvas v0.8
|
||||
# Author : Jerome Alet - <alet@librelogiciel.com>
|
||||
# License : ReportLab's license
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
__doc__ = """pycanvas.Canvas : a Canvas class which can also output Python source code.
|
||||
|
||||
pycanvas.Canvas class works exactly like canvas.Canvas, but you can
|
||||
call str() on pycanvas.Canvas instances. Doing so will return the
|
||||
Python source code equivalent to your own program, which would, when
|
||||
run, produce the same PDF document as your original program.
|
||||
|
||||
Generated Python source code defines a doIt() function which accepts
|
||||
a filename or file-like object as its first parameter, and an
|
||||
optional boolean parameter named "regenerate".
|
||||
|
||||
The doIt() function will generate a PDF document and save it in the
|
||||
file you specified in this argument. If the regenerate parameter is
|
||||
set then it will also return an automatically generated equivalent
|
||||
Python source code as a string of text, which you can run again to
|
||||
produce the very same PDF document and the Python source code, which
|
||||
you can run again... ad nauseam ! If the regenerate parameter is
|
||||
unset or not used at all (it then defaults to being unset) then None
|
||||
is returned and the doIt() function is much much faster, it is also
|
||||
much faster than the original non-serialized program.
|
||||
|
||||
the reportlab/test/test_pdfgen_pycanvas.py program is the test suite
|
||||
for pycanvas, you can do the following to run it :
|
||||
|
||||
First set verbose=1 in reportlab/rl_config.py
|
||||
|
||||
then from the command interpreter :
|
||||
|
||||
$ cd reportlab/test
|
||||
$ python test_pdfgen_pycanvas.py >n1.py
|
||||
|
||||
this will produce both n1.py and test_pdfgen_pycanvas.pdf
|
||||
|
||||
then :
|
||||
|
||||
$ python n1.py n1.pdf >n2.py
|
||||
$ python n2.py n2.pdf >n3.py
|
||||
$ ...
|
||||
|
||||
n1.py, n2.py, n3.py and so on will be identical files.
|
||||
they eventually may end being a bit different because of
|
||||
rounding problems, mostly in the comments, but this
|
||||
doesn't matter since the values really are the same
|
||||
(e.g. 0 instead of 0.0, or .53 instead of 0.53)
|
||||
|
||||
n1.pdf, n2.pdf, n3.pdf and so on will be PDF files
|
||||
similar to test_pdfgen_pycanvas.pdf.
|
||||
|
||||
Alternatively you can import n1.py (or n3.py, or n16384.py if you prefer)
|
||||
in your own program, and then call its doIt function :
|
||||
|
||||
import n1
|
||||
pythonsource = n1.doIt("myfile.pdf", regenerate=1)
|
||||
|
||||
Or if you don't need the python source code and want a faster result :
|
||||
|
||||
import n1
|
||||
n1.doIt("myfile.pdf")
|
||||
|
||||
When the generated source code is run directly as an independant program,
|
||||
then the equivalent python source code is printed to stdout, e.g. :
|
||||
|
||||
python n1.py
|
||||
|
||||
will print the python source code equivalent to n1.py
|
||||
|
||||
Why would you want to use such a beast ?
|
||||
|
||||
- To linearize (serialize?) a program : optimizing some complex
|
||||
parts for example.
|
||||
|
||||
- To debug : reading the generated Python source code may help you or
|
||||
the ReportLab team to diagnose problems. The generated code is now
|
||||
clearly commented and shows nesting levels, page numbers, and so
|
||||
on. You can use the generated script when asking for support : we
|
||||
can see the results you obtain without needing your datas or complete
|
||||
application.
|
||||
|
||||
- To create standalone scripts : say your program uses a high level
|
||||
environment to generate its output (databases, RML, etc...), using
|
||||
this class would give you an equivalent program but with complete
|
||||
independance from the high level environment (e.g. if you don't
|
||||
have Oracle).
|
||||
|
||||
- To contribute some nice looking PDF documents to the ReportLab website
|
||||
without having to send a complete application you don't want to
|
||||
distribute.
|
||||
|
||||
- ... Insert your own ideas here ...
|
||||
|
||||
- For fun because you can do it !
|
||||
"""
|
||||
|
||||
import cStringIO
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.pdfgen import pathobject
|
||||
from reportlab.pdfgen import textobject
|
||||
|
||||
PyHeader = '''#! /usr/bin/env python
|
||||
|
||||
#
|
||||
# This code was entirely generated by ReportLab (http://www.reportlab.com)
|
||||
#
|
||||
|
||||
import sys
|
||||
from reportlab.pdfgen import pathobject
|
||||
from reportlab.pdfgen import textobject
|
||||
from reportlab.lib.colors import Color
|
||||
|
||||
def doIt(file, regenerate=0) :
|
||||
"""Generates a PDF document, save it into file.
|
||||
|
||||
file : either a filename or a file-like object.
|
||||
|
||||
regenerate : if set then this function returns the Python source
|
||||
code which when run will produce the same result.
|
||||
if unset then this function returns None, and is
|
||||
much faster.
|
||||
"""
|
||||
if regenerate :
|
||||
from reportlab.pdfgen.pycanvas import Canvas
|
||||
else :
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
'''
|
||||
|
||||
PyFooter = '''
|
||||
# if we want the equivalent Python source code, then send it back
|
||||
if regenerate :
|
||||
return str(c)
|
||||
|
||||
if __name__ == "__main__" :
|
||||
if len(sys.argv) != 2 :
|
||||
# second argument must be the name of the PDF file to create
|
||||
sys.stderr.write("%s needs one and only one argument\\n" % sys.argv[0])
|
||||
sys.exit(-1)
|
||||
else :
|
||||
# we've got a filename, we can proceed.
|
||||
print doIt(sys.argv[1], regenerate=1)
|
||||
sys.exit(0)'''
|
||||
|
||||
def buildargs(*args, **kwargs) :
|
||||
"""Constructs a printable list of arguments suitable for use in source function calls."""
|
||||
arguments = ""
|
||||
for arg in args :
|
||||
arguments = arguments + ("%s, " % repr(arg))
|
||||
for (kw, val) in kwargs.items() :
|
||||
arguments = arguments+ ("%s=%s, " % (kw, repr(val)))
|
||||
if arguments[-2:] == ", " :
|
||||
arguments = arguments[:-2]
|
||||
return arguments
|
||||
|
||||
class PDFAction :
|
||||
"""Base class to fake method calls or attributes on PDF objects (Canvas, PDFPathObject, PDFTextObject)."""
|
||||
def __init__(self, parent, action) :
|
||||
"""Saves a pointer to the parent object, and the method name."""
|
||||
self._parent = parent
|
||||
self._action = action
|
||||
|
||||
def __getattr__(self, name) :
|
||||
"""Probably a method call on an attribute, returns the real one."""
|
||||
return getattr(getattr(self._parent._object, self._action), name)
|
||||
|
||||
def __call__(self, *args, **kwargs) :
|
||||
"""The fake method is called, print it then call the real one."""
|
||||
if not self._parent._parent._in :
|
||||
self._precomment()
|
||||
self._parent._parent._PyWrite(" %s.%s(%s)" % (self._parent._name, self._action, apply(buildargs, args, kwargs)))
|
||||
self._postcomment()
|
||||
self._parent._parent._in = self._parent._parent._in + 1
|
||||
retcode = apply(getattr(self._parent._object, self._action), args, kwargs)
|
||||
self._parent._parent._in = self._parent._parent._in - 1
|
||||
return retcode
|
||||
|
||||
def __hash__(self) :
|
||||
return hash(getattr(self._parent._object, self._action))
|
||||
|
||||
def __coerce__(self, other) :
|
||||
"""Needed."""
|
||||
return coerce(getattr(self._parent._object, self._action), other)
|
||||
|
||||
def _precomment(self) :
|
||||
"""To be overriden."""
|
||||
pass
|
||||
|
||||
def _postcomment(self) :
|
||||
"""To be overriden."""
|
||||
pass
|
||||
|
||||
class PDFObject :
|
||||
"""Base class for PDF objects like PDFPathObject and PDFTextObject."""
|
||||
_number = 0
|
||||
def __init__(self, parent) :
|
||||
"""Saves a pointer to the parent Canvas."""
|
||||
self._parent = parent
|
||||
self._initdone = 0
|
||||
|
||||
def __getattr__(self, name) :
|
||||
"""The user's programs wants to call one of our methods or get an attribute, fake it."""
|
||||
return PDFAction(self, name)
|
||||
|
||||
def __repr__(self) :
|
||||
"""Returns the name used in the generated source code (e.g. 'p' or 't')."""
|
||||
return self._name
|
||||
|
||||
def __call__(self, *args, **kwargs) :
|
||||
"""Real object initialisation is made here, because now we've got the arguments."""
|
||||
if not self._initdone :
|
||||
self.__class__._number = self.__class__._number + 1
|
||||
methodname = apply(self._postinit, args, kwargs)
|
||||
self._parent._PyWrite("\n # create PDF%sObject number %i\n %s = %s.%s(%s)" % (methodname[5:], self.__class__._number, self._name, self._parent._name, methodname, apply(buildargs, args, kwargs)))
|
||||
self._initdone = 1
|
||||
return self
|
||||
|
||||
class Canvas :
|
||||
"""Our fake Canvas class, which will intercept each and every method or attribute access."""
|
||||
class TextObject(PDFObject) :
|
||||
_name = "t"
|
||||
def _postinit(self, *args, **kwargs) :
|
||||
self._object = apply(textobject.PDFTextObject, (self._parent, ) + args, kwargs)
|
||||
return "beginText"
|
||||
|
||||
class PathObject(PDFObject) :
|
||||
_name = "p"
|
||||
def _postinit(self, *args, **kwargs) :
|
||||
self._object = apply(pathobject.PDFPathObject, args, kwargs)
|
||||
return "beginPath"
|
||||
|
||||
class Action(PDFAction) :
|
||||
"""Class called for every Canvas method call."""
|
||||
def _precomment(self) :
|
||||
"""Outputs comments before the method call."""
|
||||
if self._action == "showPage" :
|
||||
self._parent._PyWrite("\n # Ends page %i" % self._parent._pagenumber)
|
||||
elif self._action == "saveState" :
|
||||
state = {}
|
||||
d = self._parent._object.__dict__
|
||||
for name in self._parent._object.STATE_ATTRIBUTES:
|
||||
state[name] = d[name]
|
||||
self._parent._PyWrite("\n # Saves context level %i %s" % (self._parent._contextlevel, state))
|
||||
self._parent._contextlevel = self._parent._contextlevel + 1
|
||||
elif self._action == "restoreState" :
|
||||
self._parent._contextlevel = self._parent._contextlevel - 1
|
||||
self._parent._PyWrite("\n # Restores context level %i %s" % (self._parent._contextlevel, self._parent._object.state_stack[-1]))
|
||||
elif self._action == "beginForm" :
|
||||
self._parent._formnumber = self._parent._formnumber + 1
|
||||
self._parent._PyWrite("\n # Begins form %i" % self._parent._formnumber)
|
||||
elif self._action == "endForm" :
|
||||
self._parent._PyWrite("\n # Ends form %i" % self._parent._formnumber)
|
||||
elif self._action == "save" :
|
||||
self._parent._PyWrite("\n # Saves the PDF document to disk")
|
||||
|
||||
def _postcomment(self) :
|
||||
"""Outputs comments after the method call."""
|
||||
if self._action == "showPage" :
|
||||
self._parent._pagenumber = self._parent._pagenumber + 1
|
||||
self._parent._PyWrite("\n # Begins page %i" % self._parent._pagenumber)
|
||||
elif self._action in [ "endForm", "drawPath", "clipPath" ] :
|
||||
self._parent._PyWrite("")
|
||||
|
||||
_name = "c"
|
||||
def __init__(self, *args, **kwargs) :
|
||||
"""Initialize and begins source code."""
|
||||
self._parent = self # nice trick, isn't it ?
|
||||
self._in = 0
|
||||
self._contextlevel = 0
|
||||
self._pagenumber = 1
|
||||
self._formnumber = 0
|
||||
self._footerpresent = 0
|
||||
self._object = apply(canvas.Canvas, args, kwargs)
|
||||
self._pyfile = cStringIO.StringIO()
|
||||
self._PyWrite(PyHeader)
|
||||
try :
|
||||
del kwargs["filename"]
|
||||
except KeyError :
|
||||
pass
|
||||
self._PyWrite(" # create the PDF document\n %s = Canvas(file, %s)\n\n # Begins page 1" % (self._name, apply(buildargs, args[1:], kwargs)))
|
||||
|
||||
def __nonzero__(self) :
|
||||
"""This is needed by platypus' tables."""
|
||||
return 1
|
||||
|
||||
def __str__(self) :
|
||||
"""Returns the equivalent Python source code."""
|
||||
if not self._footerpresent :
|
||||
self._PyWrite(PyFooter)
|
||||
self._footerpresent = 1
|
||||
return self._pyfile.getvalue()
|
||||
|
||||
def __getattr__(self, name) :
|
||||
"""Method or attribute access."""
|
||||
if name == "beginPath" :
|
||||
return self.PathObject(self)
|
||||
elif name == "beginText" :
|
||||
return self.TextObject(self)
|
||||
else :
|
||||
return self.Action(self, name)
|
||||
|
||||
def _PyWrite(self, pycode) :
|
||||
"""Outputs the source code with a trailing newline."""
|
||||
self._pyfile.write("%s\n" % pycode)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print 'For test scripts, look in reportlab/test'
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue