Added printing requirements

This commit is contained in:
Tom Price
2014-12-07 17:32:24 +00:00
parent c76d12d877
commit 7ba11a2db9
571 changed files with 143368 additions and 6 deletions

View File

@@ -12,12 +12,6 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Add report lab things
import sys
sys.path.insert(0,'reportlab.zip')
sys.path.insert(0,'z3c.zip')
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/

Binary file not shown.

6
reportlab/MANIFEST.in Normal file
View File

@@ -0,0 +1,6 @@
global-include *.dtd *.txt *.xml *.yml
global-include *.c *.h *.in *.mashed
global-include *.gif *.png *.jpg .a85
global-include *.AFM *.PFB *.ttf
global-include README
include changes

48
reportlab/__init__.py Normal file
View File

@@ -0,0 +1,48 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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 = "3.1.43"
import sys, os, imp
if sys.version_info[0:2]!=(2, 7) and sys.version_info<(3, 3):
raise ImportError("""reportlab requires Python 2.7+ or 3.3+; 3.0-3.2 are not supported.""")
#define these early in reportlab's life
isPy3 = sys.version_info[0]==3
if isPy3:
def cmp(a,b):
return -1 if a<b else (1 if a>b else 0)
import builtins
builtins.cmp = cmp
builtins.xrange = range
del cmp, builtins
else:
from future_builtins import ascii
import __builtin__
__builtin__.ascii = ascii
del ascii, __builtin__
#try to use dynamic modifications from
#reportlab.local_rl_mods.py
#reportlab_mods.py or ~/.reportlab_mods
try:
import reportlab.local_rl_mods
except ImportError:
pass
def _fake_import(fn,name):
if os.path.isfile(fn):
with open(fn,'rb') as f:
imp.load_source(name,fn,f)
try:
import reportlab_mods #application specific modifications can be anywhere on python path
except ImportError:
try:
_fake_import(os.path.expanduser(os.path.join('~','.reportlab_mods')),'reportlab_mods')
except ImportError:
pass

View File

@@ -0,0 +1,8 @@
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.

View File

@@ -0,0 +1,14 @@
2004-05-18 Michal Kosmulski <mkosmul@users.sourceforge.net>
* Version 1.1
* Changed license to GNU GPL with Font Exception
* Added Zacute character
* Added basic hinting
* Added kerning information
2000 Michal Kosmulski <mkosmul@users.sourceforge.net>
* Version 1.0
* Added Polish and German diacritic characters except for Zacute
1999 Michal Kosmulski <mkosmul@users.sourceforge.net>
* Version 0.9
* Includes only the 26 letters of the English alphabet
* Distributed under a freeware license

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
675 Mass Ave, Cambridge, MA 02139, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) 19yy <name of author>
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
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19yy name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

View File

@@ -0,0 +1,26 @@
Copyright (C) 1999, 2000, 2004 Michal Kosmulski
<mkosmul@users.sourceforge.net>
This font 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
(at your option) any later version.
This font is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this font; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
As a special exception, if you create a document which uses
this font, and embed this font or unaltered portions of this font into
the document, this font does not by itself cause the resulting
document to be covered by the GNU General Public License. This
exception does not however invalidate any other reasons why the
document might be covered by the GNU General Public License. If you
modify this font, you may extend this exception to your version of the
font, but you are not obligated to do so. If you do not wish to do so,
delete this exception statement from your version.

View File

@@ -0,0 +1,106 @@
Dark Garden
===========
GENERAL
Dark Garden is a decorative outline font of unusual shape. The typeface
is based on author's original hand drawings. The letterform is complex,
with all characters decorated with spikes resembling thorns or flames,
character spacing is very dense. Such a theme makes it a great font for
titles, banners, logos etc.
As of version 1.1, Dark Garden includes letters of the English alphabet,
Polish and German diacritic characters and some punctuation. Unicode
encoding is used. The font is equipped with hinting information to allow
high quality rendering at small point sizes. Only horizontal and
vertical hints are currently used, future versions might take advantage
of truetype hinting instructions some day. There are also a lot of
kerning pairs defined in the font - they were chosen after looking at a
choice of Polish and English texts and selecting the character pairs
that needed manual kerning.
The font was created with PfaEdit / FontForge
(http://fontforge.sourceforge.net/) - an opensource font editing tool.
Pfaedit uses its own text-based file format which can then be exported
to truetype, postscript, opentype and many other font formats. This
makes it possible to generate font files for Dark Garden in any of the
many supported formats, so it can be used on almost any platform. So
far, the truetype version has been tested on X11 (Linux) and Windows,
the opentype version worked on Windows but not X11. Truetype should also
work on Mac OS.
INSTALLATION
Installation is described for the truetype version of the font (which is
what most users will need).
On Windows:
* Extract .ttf files from the archive into a temporary directory
* In the Control Panel choose Fonts
* From the File menu choose Install New Font
* Browse to the temporary directory you chose
* "Dark Garden" should appear in the font list - click OK (this may
require Administrator privileges)
On Linux / UNIX (using KDE):
* Extract .ttf files from the archive into a temporary directory
* In the Control Center choose Font Installer in the System
Administration tree
* Click on "Administrator Mode" and enter root's password
* Click on "Add Fonts" icon
* Select the .ttf file extracted from the archive and click OK
On Linux / UNIX (using commandline)
unzip darkgarden-*.ttf.zip
su
ginstall -o root -m 644 DarkGardenMK.ttf /usr/X11R6/lib/fonts/TTF
fc-cache -f
COPYRIGHT AND LICENSE
Copyright (C) 1999, 2000, 2004 Michal Kosmulski
<mkosmul@users.sourceforge.net>
This font 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
(at your option) any later version.
This font is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this font; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
As a special exception, if you create a document which uses
this font, and embed this font or unaltered portions of this font into
the document, this font does not by itself cause the resulting
document to be covered by the GNU General Public License. This
exception does not however invalidate any other reasons why the
document might be covered by the GNU General Public License. If you
modify this font, you may extend this exception to your version of the
font, but you are not obligated to do so. If you do not wish to do so,
delete this exception statement from your version.
The text of this license can be found in file COPYING.txt in the
distribution archives. The GNU General Public License text can be found
in file COPYING-GPL.txt.
CONTACTING THE AUTHOR
Send e-mail with questions, bug reports and patches to Michal Kosmulski,
mkosmul@users.sourceforge.net
The homepage of Dark Garden Font is located at
http://darkgarden.sourceforge.net/.

11067
reportlab/fonts/DarkGarden.sfd Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,429 @@
StartFontMetrics 2.0
Comment Generated by FontForge 20071110
Comment Creation Date: Wed Sep 10 17:07:49 2008
FontName DarkGardenMK
FullName Dark Garden
FamilyName Dark Garden
Weight Medium
Notice (Copyright (C) 1999, 2000, 2004 Michal Kosmulski )
Comment <mkosmul@users.sourceforge.net>
Comment
Comment This font is free software; you can redistribute it and/or modify
Comment it under the terms of the GNU General Public License as published by
Comment the Free Software Foundation; either version 2 of the License, or
Comment (at your option) any later version.
Comment
Comment This font is distributed in the hope that it will be useful,
Comment but WITHOUT ANY WARRANTY; without even the implied warranty of
Comment MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Comment GNU General Public License for more details.
Comment
Comment You should have received a copy of the GNU General Public License
Comment along with this font; if not, write to the Free Software
Comment Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Comment
Comment As a special exception, if you create a document which uses
Comment this font, and embed this font or unaltered portions of this font into
Comment the document, this font does not by itself cause the resulting
Comment document to be covered by the GNU General Public License. This
Comment exception does not however invalidate any other reasons why the
Comment document might be covered by the GNU General Public License. If you
Comment modify this font, you may extend this exception to your version of the
Comment font, but you are not obligated to do so. If you do not wish to do so,
Comment delete this exception statement from your version.
Comment
ItalicAngle 0
IsFixedPitch false
UnderlinePosition -420
UnderlineThickness 80
Version 1.1
EncodingScheme ISO10646-1
FontBBox -11 -331 935 1122
CapHeight 931
XHeight 869
Ascender 831
Descender -123
StartCharMetrics 86
C 0 ; WX 1000 ; N .notdef ; B 0 0 531 890 ;
C 1 ; WX 0 ; N glyph1 ; B 0 0 0 0 ;
C 2 ; WX 1000 ; N glyph2 ; B 0 0 0 0 ;
C 32 ; WX 500 ; N space ; B 0 0 0 0 ;
C 44 ; WX 130 ; N comma ; B 0 -76 123 105 ;
C 45 ; WX 611 ; N hyphen ; B 46 364 560 519 ;
C 46 ; WX 131 ; N period ; B 1 0 124 124 ;
C 58 ; WX 187 ; N colon ; B 32 6 159 447 ;
C 59 ; WX 187 ; N semicolon ; B 36 -98 163 470 ;
C 65 ; WX 706 ; N A ; B 3 -162 670 853 ;
C 66 ; WX 624 ; N B ; B -5 -27 602 911 ;
C 67 ; WX 619 ; N C ; B -1 -52 590 846 ;
C 68 ; WX 730 ; N D ; B 4 -133 703 912 ;
C 69 ; WX 613 ; N E ; B 1 -100 613 836 ;
C 70 ; WX 660 ; N F ; B 3 -96 629 833 ;
C 71 ; WX 624 ; N G ; B 5 -61 594 898 ;
C 72 ; WX 660 ; N H ; B 7 -177 629 876 ;
C 73 ; WX 284 ; N I ; B 22 -179 271 932 ;
C 74 ; WX 603 ; N J ; B 3 -93 570 863 ;
C 75 ; WX 639 ; N K ; B 7 -182 625 921 ;
C 76 ; WX 658 ; N L ; B 15 -3 616 903 ;
C 77 ; WX 833 ; N M ; B 9 -236 861 874 ;
C 78 ; WX 614 ; N N ; B 3 -135 579 917 ;
C 79 ; WX 691 ; N O ; B 9 -35 659 848 ;
C 80 ; WX 648 ; N P ; B 4 -203 605 846 ;
C 81 ; WX 612 ; N Q ; B -11 -179 578 894 ;
C 82 ; WX 647 ; N R ; B -1 -331 614 891 ;
C 83 ; WX 666 ; N S ; B 3 -60 654 876 ;
C 84 ; WX 829 ; N T ; B 0 -195 787 817 ;
C 85 ; WX 688 ; N U ; B 6 -33 656 874 ;
C 86 ; WX 618 ; N V ; B 4 -188 589 991 ;
C 87 ; WX 985 ; N W ; B 4 -132 935 974 ;
C 88 ; WX 690 ; N X ; B 6 -125 731 951 ;
C 89 ; WX 645 ; N Y ; B 7 -209 609 953 ;
C 90 ; WX 841 ; N Z ; B 14 -108 853 845 ;
C 97 ; WX 612 ; N a ; B 13 -93 581 769 ;
C 98 ; WX 528 ; N b ; B -4 -23 512 774 ;
C 99 ; WX 571 ; N c ; B 42 15 545 779 ;
C 100 ; WX 641 ; N d ; B 15 -60 609 829 ;
C 101 ; WX 527 ; N e ; B 8 -35 528 760 ;
C 102 ; WX 563 ; N f ; B 21 -25 553 765 ;
C 103 ; WX 549 ; N g ; B 21 10 522 825 ;
C 104 ; WX 581 ; N h ; B 24 -96 553 798 ;
C 105 ; WX 256 ; N i ; B 22 -99 235 846 ;
C 106 ; WX 527 ; N j ; B 17 -27 499 786 ;
C 107 ; WX 543 ; N k ; B 6 -155 531 783 ;
C 108 ; WX 525 ; N l ; B -1 62 510 832 ;
C 109 ; WX 718 ; N m ; B 14 -151 738 792 ;
C 110 ; WX 523 ; N n ; B 13 -63 502 832 ;
C 111 ; WX 612 ; N o ; B 30 28 584 779 ;
C 112 ; WX 561 ; N p ; B 13 -124 524 767 ;
C 113 ; WX 566 ; N q ; B 33 -98 535 814 ;
C 114 ; WX 548 ; N r ; B -3 -245 520 795 ;
C 115 ; WX 567 ; N s ; B 3 9 556 806 ;
C 116 ; WX 718 ; N t ; B 13 -117 682 743 ;
C 117 ; WX 587 ; N u ; B 8 36 560 805 ;
C 118 ; WX 530 ; N v ; B 9 -99 506 902 ;
C 119 ; WX 774 ; N w ; B 1 -49 793 891 ;
C 120 ; WX 604 ; N x ; B 11 -43 628 870 ;
C 121 ; WX 559 ; N y ; B 14 -122 526 866 ;
C 122 ; WX 764 ; N z ; B 7 -36 720 774 ;
C 196 ; WX 765 ; N Adieresis ; B 8 -179 724 1064 ;
C 211 ; WX 704 ; N Oacute ; B 7 -82 665 1012 ;
C 214 ; WX 791 ; N Odieresis ; B 1 -68 755 1084 ;
C 220 ; WX 763 ; N Udieresis ; B -1 -59 728 1122 ;
C 223 ; WX 543 ; N germandbls ; B 2 -304 516 795 ;
C 228 ; WX 650 ; N adieresis ; B 6 -152 615 904 ;
C 243 ; WX 600 ; N oacute ; B 8 -2 567 928 ;
C 246 ; WX 672 ; N odieresis ; B 0 -58 642 921 ;
C 252 ; WX 648 ; N udieresis ; B -1 -50 619 953 ;
C -1 ; WX 699 ; N Aogonek ; B 1 -257 657 838 ;
C -1 ; WX 594 ; N aogonek ; B 1 -219 559 712 ;
C -1 ; WX 596 ; N Cacute ; B 8 -57 561 1046 ;
C -1 ; WX 514 ; N cacute ; B 14 20 484 959 ;
C -1 ; WX 739 ; N Eogonek ; B 2 -240 738 785 ;
C -1 ; WX 637 ; N eogonek ; B 13 -121 640 750 ;
C -1 ; WX 848 ; N Lslash ; B 8 -107 908 871 ;
C -1 ; WX 722 ; N lslash ; B 11 -32 776 799 ;
C -1 ; WX 627 ; N Nacute ; B 8 -46 597 1050 ;
C -1 ; WX 534 ; N nacute ; B 8 32 509 964 ;
C -1 ; WX 558 ; N Sacute ; B 4 -27 566 1094 ;
C -1 ; WX 490 ; N sacute ; B 7 58 485 1010 ;
C -1 ; WX 721 ; N Zacute ; B 7 -90 714 1013 ;
C -1 ; WX 617 ; N zacute ; B 7 -44 607 894 ;
C -1 ; WX 759 ; N Zdotaccent ; B -7 -103 749 925 ;
C -1 ; WX 655 ; N zdotaccent ; B 4 -53 645 822 ;
EndCharMetrics
StartKernData
StartKernPairs 291
KPX hyphen Z -126
KPX hyphen z -101
KPX hyphen X -88
KPX hyphen x -76
KPX hyphen Y -63
KPX hyphen y -50
KPX hyphen W -88
KPX hyphen w -88
KPX hyphen T -139
KPX hyphen t -133
KPX hyphen S -114
KPX hyphen s -76
KPX hyphen A -114
KPX hyphen a -114
KPX A C -25
KPX A c -63
KPX A M -50
KPX A m -38
KPX A h -63
KPX A H -50
KPX A hyphen -82
KPX A s -139
KPX A S -120
KPX A y -145
KPX A Y -125
KPX A t -145
KPX A T -145
KPX A v -31
KPX A w -112
KPX A V -24
KPX A W -94
KPX C S -69
KPX C s -63
KPX C A -31
KPX C a -44
KPX D Z -88
KPX D z -69
KPX D a -101
KPX D A -73
KPX E S -63
KPX E s -63
KPX F t -139
KPX F T -82
KPX F hyphen -101
KPX F period -114
KPX F comma -133
KPX F A -239
KPX F a -229
KPX H A -44
KPX K o -50
KPX K O -25
KPX K s -107
KPX K S -101
KPX K g -88
KPX K G -38
KPX K c -114
KPX K C -69
KPX L t -203
KPX L T -203
KPX L Y -114
KPX L y -114
KPX L i -63
KPX L I -69
KPX L hyphen -107
KPX L S -114
KPX L w -133
KPX L W -139
KPX M A -50
KPX M a -50
KPX M i 38
KPX N A -114
KPX N a -101
KPX O y -38
KPX O Y -44
KPX O a -76
KPX O A -76
KPX O x -76
KPX O X -76
KPX O W -114
KPX O t -139
KPX O T -178
KPX P hyphen -101
KPX P period -126
KPX P comma -139
KPX P t -44
KPX P T -95
KPX P Lslash -177
KPX P lslash -177
KPX P A -166
KPX P a -177
KPX R Y -69
KPX R y -63
KPX R S -101
KPX R G -50
KPX R t -139
KPX R T -152
KPX R V -10
KPX R W -73
KPX S E 25
KPX S e 19
KPX S S -57
KPX S t -88
KPX S T -31
KPX T lslash -190
KPX T Lslash -229
KPX T U -57
KPX T u -69
KPX T h -114
KPX T H -76
KPX T hyphen -146
KPX T period -126
KPX T comma -126
KPX T oacute -146
KPX T Oacute -101
KPX T s -139
KPX T S -126
KPX T o -101
KPX T O -69
KPX T A -250
KPX T a -250
KPX U t -38
KPX U T -31
KPX U X -63
KPX V Odieresis -94
KPX V odieresis -109
KPX V o -95
KPX V O -76
KPX V hyphen -101
KPX V period -120
KPX V comma -120
KPX V I -50
KPX V i -88
KPX V a -156
KPX V A -131
KPX W Odieresis -87
KPX W odieresis -103
KPX W lslash -229
KPX W Lslash -248
KPX W H -126
KPX W h -146
KPX W hyphen -114
KPX W period -159
KPX W comma -177
KPX W s -159
KPX W S -126
KPX W i -88
KPX W I -44
KPX W oacute -152
KPX W Oacute -178
KPX W o -133
KPX W O -107
KPX W Adieresis -152
KPX W adieresis -131
KPX W aogonek -166
KPX W Aogonek -166
KPX W a -219
KPX W A -194
KPX X hyphen -101
KPX Y O -44
KPX Y o -88
KPX Y Lslash -152
KPX Y S -126
KPX Y hyphen -95
KPX Y period -114
KPX Y comma -114
KPX Y a -208
KPX Y A -187
KPX Z y -57
KPX Z Y -50
KPX Z lslash -88
KPX Z Lslash -82
KPX Z i -25
KPX Z I -31
KPX Z e -31
KPX Z b -38
KPX Z B -38
KPX Z hyphen -101
KPX a c -63
KPX a m -38
KPX a h -57
KPX a hyphen -88
KPX a s -101
KPX a y -145
KPX a t -166
KPX a v -20
KPX a w -107
KPX c s -57
KPX c a -50
KPX d z -44
KPX d a -73
KPX e s -38
KPX f t -63
KPX f hyphen -76
KPX f period -120
KPX f comma -126
KPX f a -208
KPX h a -50
KPX k o -31
KPX k s -50
KPX k g -31
KPX k c -76
KPX l t -139
KPX l y -76
KPX l i -38
KPX l hyphen -76
KPX l s -63
KPX l w -95
KPX m a -44
KPX m i 31
KPX n a -101
KPX o y -31
KPX o a -76
KPX o x -69
KPX o w -95
KPX o t -152
KPX p hyphen -50
KPX p period -107
KPX p comma -101
KPX p t -95
KPX p lslash -145
KPX p a -145
KPX r y -76
KPX r s -88
KPX r g -50
KPX r t -165
KPX r v -20
KPX r w -73
KPX s c -25
KPX s s -50
KPX s t -38
KPX t lslash -159
KPX t u -57
KPX t h -82
KPX t hyphen -107
KPX t period -126
KPX t comma -126
KPX t oacute -101
KPX t s -101
KPX t o -88
KPX t a -218
KPX u t -38
KPX u x -50
KPX v odieresis -83
KPX v o -69
KPX v hyphen -88
KPX v period -88
KPX v comma -101
KPX v i -50
KPX v a -125
KPX w odieresis -20
KPX w lslash -146
KPX w h -50
KPX w hyphen -57
KPX w period -95
KPX w comma -101
KPX w s -44
KPX w i 6
KPX w oacute -76
KPX w o -44
KPX w adieresis -111
KPX w aogonek -152
KPX w a -166
KPX x hyphen -101
KPX y o -63
KPX y lslash -126
KPX y s -101
KPX y hyphen -82
KPX y period -95
KPX y comma -101
KPX y a -177
KPX z y -95
KPX z lslash -120
KPX z i -69
KPX z e -50
KPX z b -76
KPX z hyphen -177
KPX Oacute T -139
KPX Oacute W -88
KPX germandbls t -95
KPX germandbls T -152
KPX oacute t -114
KPX oacute w -76
KPX Aogonek W -101
KPX aogonek w -88
KPX Lslash Y -76
KPX Lslash y -38
KPX Lslash T -101
KPX Lslash W -156
KPX lslash y -50
KPX lslash t -101
KPX lslash w -187
EndKernPairs
EndKernData
EndFontMetrics

Binary file not shown.

BIN
reportlab/fonts/Vera.ttf Normal file

Binary file not shown.

BIN
reportlab/fonts/VeraBI.ttf Normal file

Binary file not shown.

BIN
reportlab/fonts/VeraBd.ttf Normal file

Binary file not shown.

BIN
reportlab/fonts/VeraIt.ttf Normal file

Binary file not shown.

View File

@@ -0,0 +1,124 @@
Bitstream Vera Fonts Copyright
The fonts have a generous copyright, allowing derivative works (as
long as "Bitstream" or "Vera" are not in the names), and full
redistribution (so long as they are not *sold* by themselves). They
can be be bundled, redistributed and sold with any software.
The fonts are distributed under the following copyright:
Copyright
=========
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
Vera is a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of the fonts accompanying this license ("Fonts") and associated
documentation files (the "Font Software"), to reproduce and distribute
the Font Software, including without limitation the rights to use,
copy, merge, publish, distribute, and/or sell copies of the Font
Software, and to permit persons to whom the Font Software is furnished
to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice
shall be included in all copies of one or more of the Font Software
typefaces.
The Font Software may be modified, altered, or added to, and in
particular the designs of glyphs or characters in the Fonts may be
modified and additional glyphs or characters may be added to the
Fonts, only if the fonts are renamed to names not containing either
the words "Bitstream" or the word "Vera".
This License becomes null and void to the extent applicable to Fonts
or Font Software that has been modified and is distributed under the
"Bitstream Vera" names.
The Font Software may be sold as part of a larger software package but
no copy of one or more of the Font Software typefaces may be sold by
itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font
Software without prior written authorization from the Gnome Foundation
or Bitstream Inc., respectively. For further information, contact:
fonts at gnome dot org.
Copyright FAQ
=============
1. I don't understand the resale restriction... What gives?
Bitstream is giving away these fonts, but wishes to ensure its
competitors can't just drop the fonts as is into a font sale system
and sell them as is. It seems fair that if Bitstream can't make money
from the Bitstream Vera fonts, their competitors should not be able to
do so either. You can sell the fonts as part of any software package,
however.
2. I want to package these fonts separately for distribution and
sale as part of a larger software package or system. Can I do so?
Yes. A RPM or Debian package is a "larger software package" to begin
with, and you aren't selling them independently by themselves.
See 1. above.
3. Are derivative works allowed?
Yes!
4. Can I change or add to the font(s)?
Yes, but you must change the name(s) of the font(s).
5. Under what terms are derivative works allowed?
You must change the name(s) of the fonts. This is to ensure the
quality of the fonts, both to protect Bitstream and Gnome. We want to
ensure that if an application has opened a font specifically of these
names, it gets what it expects (though of course, using fontconfig,
substitutions could still could have occurred during font
opening). You must include the Bitstream copyright. Additional
copyrights can be added, as per copyright law. Happy Font Hacking!
6. If I have improvements for Bitstream Vera, is it possible they might get
adopted in future versions?
Yes. The contract between the Gnome Foundation and Bitstream has
provisions for working with Bitstream to ensure quality additions to
the Bitstream Vera font family. Please contact us if you have such
additions. Note, that in general, we will want such additions for the
entire family, not just a single font, and that you'll have to keep
both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add
glyphs to the font, they must be stylistically in keeping with Vera's
design. Vera cannot become a "ransom note" font. Jim Lyles will be
providing a document describing the design elements used in Vera, as a
guide and aid for people interested in contributing to Vera.
7. I want to sell a software package that uses these fonts: Can I do so?
Sure. Bundle the fonts with your software and sell your software
with the fonts. That is the intent of the copyright.
8. If applications have built the names "Bitstream Vera" into them,
can I override this somehow to use fonts of my choosing?
This depends on exact details of the software. Most open source
systems and software (e.g., Gnome, KDE, etc.) are now converting to
use fontconfig (see www.fontconfig.org) to handle font configuration,
selection and substitution; it has provisions for overriding font
names and subsituting alternatives. An example is provided by the
supplied local.conf file, which chooses the family Bitstream Vera for
"sans", "serif" and "monospace". Other software (e.g., the XFree86
core server) has other mechanisms for font substitution.

View File

@@ -0,0 +1,6 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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$ '''
__doc__='''Framework for reusable object graphics, in PDF or bitmap form'''

View File

@@ -0,0 +1,59 @@
Symbologies Currently Supported
===============================
The following have, at a minimum, been verified to scan with a WASP
CCD barcode scanner (found one bug in my code, two in the scanner!).
Some have had more extensive testing:
Interleaved 2 of 5
MSI
Codabar
Code 39 (Standard Character Set)
Code 39 (Extended Character Set)
Code 93 (Standard Character Set)
Code 93 (Extended Character Set)
Code 128 (Automatic use of A, B, C, with some optimizations --
more coming)
The following have been tested by sending a fair number of mailpieces
with them:
USPS FIM
USPS POSTNET
The following have not been tested, as none of the scanners I have
access to support them:
Code 11
Future Plans, Consulting
========================
Soon:
I plan to implement the following linear codes soon:
UPC/EAN(/JAN)
The following are in progress, but I lack a way to verify them
(scanners I have access to don't read them), and I don't have complete
specs for the UK style.
Royal Mail 4-State (UK/NL/etc style, and Australian style)
Down the road, I'd like to do some 2D symbologies. Likely first candidate
is PDF417. MaxiCode, Aztec Code, and some of the stacked symbologies are
also good candidates.
I am available to do implementation of additional symbologies for hire.
Because I enjoy hacking barcodes, my rates for work in this particular
area are very low and are mainly to help offset costs associated with
obtaining related documents and/or to buy or gain access to scanning
equipment for symbologies if I don't already have a scanner that
supports them. Loans of equipment are also accepted.
For more information, contact:
Ty Sarna
tsarna@sarna.org

View File

@@ -0,0 +1,24 @@
See also README for some plans and info on consulting.
- Overall framework docs
- Finish Aussie Rules 4-State, for which I have complete docs now (yay
USPS and aupost.com.au for putting specs online. Too bad UKPost doesn't.)
- Investigate USPS PLANET stuff
- Higher-level objects that handle barcoded address blocks with correct
spacings and such (US, AU, UK/etc?)
- Even higher-level objects that represent mailpieces and place the
above-style address block objects, FIM codes, "place stamp here" blocks,
etc, correctly?
- Framework for laying out labels on various styles of n-up label
sheets, like Avery labels, etc?
- Decide if Plessey is worth doing. MSI-like (MSI is actually derived from
it), but specs were never formalized. Probably only useful for legacy
applications. If you need it, mail me.
- Get someone to test Code 11, or find a scanner that handles it

View File

@@ -0,0 +1 @@
0.9

View File

@@ -0,0 +1,136 @@
#
# Copyright (c) 1996-2000 Tyler C. Sarna <tsarna@sarna.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
# must display the following acknowledgement:
# This product includes software developed by Tyler C. Sarna.
# 4. Neither the name of the author nor the names of contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
__version__ = '0.9'
__doc__='''Popular barcodes available as reusable widgets'''
def getCodes():
"""Returns a dict mapping code names to widgets"""
from reportlab.graphics.barcode.widgets import BarcodeI2of5, BarcodeCode128, BarcodeStandard93,\
BarcodeExtended93, BarcodeStandard39, BarcodeExtended39,\
BarcodeMSI, BarcodeCodabar, BarcodeCode11, BarcodeFIM,\
BarcodePOSTNET, BarcodeUSPS_4State
#newer codes will typically get their own module
from reportlab.graphics.barcode.eanbc import Ean13BarcodeWidget, Ean8BarcodeWidget, UPCA
from reportlab.graphics.barcode.qr import QrCodeWidget
#the module exports a dictionary of names to widgets, to make it easy for
#apps and doc tools to display information about them.
codes = {}
for widget in (
BarcodeI2of5,
BarcodeCode128,
BarcodeStandard93,
BarcodeExtended93,
BarcodeStandard39,
BarcodeExtended39,
BarcodeMSI,
BarcodeCodabar,
BarcodeCode11,
BarcodeFIM,
BarcodePOSTNET,
BarcodeUSPS_4State,
Ean13BarcodeWidget,
Ean8BarcodeWidget,
UPCA,
QrCodeWidget,
):
codeName = widget.codeName
codes[codeName] = widget
return codes
def getCodeNames():
"""Returns sorted list of supported bar code names"""
return sorted(getCodes().keys())
def createBarcodeDrawing(codeName, **options):
"""This creates and returns a drawing with a barcode.
"""
from reportlab.graphics.shapes import Drawing, Group
codes = getCodes()
bcc = codes[codeName]
width = options.pop('width',None)
height = options.pop('height',None)
isoScale = options.pop('isoScale',0)
kw = {}
for k,v in options.items():
if k.startswith('_') or k in bcc._attrMap: kw[k] = v
bc = bcc(**kw)
#Robin's new ones validate when setting the value property.
#Ty Sarna's old ones do not. We need to test.
if hasattr(bc, 'validate'):
bc.validate() #raise exception if bad value
if not bc.valid:
raise ValueError("Illegal barcode with value '%s' in code '%s'" % (options.get('value',None), codeName))
#size it after setting the data
x1, y1, x2, y2 = bc.getBounds()
w = float(x2 - x1)
h = float(y2 - y1)
sx = width not in ('auto',None)
sy = height not in ('auto',None)
if sx or sy:
sx = sx and width/w or 1.0
sy = sy and height/h or 1.0
if isoScale:
if sx<1.0 and sy<1.0:
sx = sy = max(sx,sy)
else:
sx = sy = min(sx,sy)
w *= sx
h *= sy
else:
sx = sy = 1
#bc.x = -sx*x1
#bc.y = -sy*y1
d = Drawing(width=w,height=h,transform=[sx,0,0,sy,-sx*x1,-sy*y1])
d.add(bc, "_bc")
return d
def createBarcodeImageInMemory(codeName,**options):
"""This creates and returns barcode as an image in memory.
Takes same arguments as createBarcodeDrawing and also an
optional format keyword which can be anything acceptable
to Drawing.asString eg gif, pdf, tiff, py ......
"""
format = options.pop('format','png')
d = createBarcodeDrawing(codeName, **options)
return d.asString(format)

View File

@@ -0,0 +1,321 @@
#
# Copyright (c) 2000 Tyler C. Sarna <tsarna@sarna.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
# must display the following acknowledgement:
# This product includes software developed by Tyler C. Sarna.
# 4. Neither the name of the author nor the names of contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
from reportlab.lib.units import inch
from reportlab.lib.utils import asNative
from reportlab.graphics.barcode.common import MultiWidthBarcode
from string import digits
_patterns = {
0 : 'BaBbBb', 1 : 'BbBaBb', 2 : 'BbBbBa',
3 : 'AbAbBc', 4 : 'AbAcBb', 5 : 'AcAbBb',
6 : 'AbBbAc', 7 : 'AbBcAb', 8 : 'AcBbAb',
9 : 'BbAbAc', 10 : 'BbAcAb', 11 : 'BcAbAb',
12 : 'AaBbCb', 13 : 'AbBaCb', 14 : 'AbBbCa',
15 : 'AaCbBb', 16 : 'AbCaBb', 17 : 'AbCbBa',
18 : 'BbCbAa', 19 : 'BbAaCb', 20 : 'BbAbCa',
21 : 'BaCbAb', 22 : 'BbCaAb', 23 : 'CaBaCa',
24 : 'CaAbBb', 25 : 'CbAaBb', 26 : 'CbAbBa',
27 : 'CaBbAb', 28 : 'CbBaAb', 29 : 'CbBbAa',
30 : 'BaBaBc', 31 : 'BaBcBa', 32 : 'BcBaBa',
33 : 'AaAcBc', 34 : 'AcAaBc', 35 : 'AcAcBa',
36 : 'AaBcAc', 37 : 'AcBaAc', 38 : 'AcBcAa',
39 : 'BaAcAc', 40 : 'BcAaAc', 41 : 'BcAcAa',
42 : 'AaBaCc', 43 : 'AaBcCa', 44 : 'AcBaCa',
45 : 'AaCaBc', 46 : 'AaCcBa', 47 : 'AcCaBa',
48 : 'CaCaBa', 49 : 'BaAcCa', 50 : 'BcAaCa',
51 : 'BaCaAc', 52 : 'BaCcAa', 53 : 'BaCaCa',
54 : 'CaAaBc', 55 : 'CaAcBa', 56 : 'CcAaBa',
57 : 'CaBaAc', 58 : 'CaBcAa', 59 : 'CcBaAa',
60 : 'CaDaAa', 61 : 'BbAdAa', 62 : 'DcAaAa',
63 : 'AaAbBd', 64 : 'AaAdBb', 65 : 'AbAaBd',
66 : 'AbAdBa', 67 : 'AdAaBb', 68 : 'AdAbBa',
69 : 'AaBbAd', 70 : 'AaBdAb', 71 : 'AbBaAd',
72 : 'AbBdAa', 73 : 'AdBaAb', 74 : 'AdBbAa',
75 : 'BdAbAa', 76 : 'BbAaAd', 77 : 'DaCaAa',
78 : 'BdAaAb', 79 : 'AcDaAa', 80 : 'AaAbDb',
81 : 'AbAaDb', 82 : 'AbAbDa', 83 : 'AaDbAb',
84 : 'AbDaAb', 85 : 'AbDbAa', 86 : 'DaAbAb',
87 : 'DbAaAb', 88 : 'DbAbAa', 89 : 'BaBaDa',
90 : 'BaDaBa', 91 : 'DaBaBa', 92 : 'AaAaDc',
93 : 'AaAcDa', 94 : 'AcAaDa', 95 : 'AaDaAc',
96 : 'AaDcAa', 97 : 'DaAaAc', 98 : 'DaAcAa',
99 : 'AaCaDa', 100 : 'AaDaCa', 101 : 'CaAaDa',
102 : 'DaAaCa', 103 : 'BaAdAb', 104 : 'BaAbAd',
105 : 'BaAbCb', 106 : 'BcCaAaB'
}
starta, startb, startc, stop = 103, 104, 105, 106
seta = {
' ' : 0, '!' : 1, '"' : 2, '#' : 3,
'$' : 4, '%' : 5, '&' : 6, '\'' : 7,
'(' : 8, ')' : 9, '*' : 10, '+' : 11,
',' : 12, '-' : 13, '.' : 14, '/' : 15,
'0' : 16, '1' : 17, '2' : 18, '3' : 19,
'4' : 20, '5' : 21, '6' : 22, '7' : 23,
'8' : 24, '9' : 25, ':' : 26, ';' : 27,
'<' : 28, '=' : 29, '>' : 30, '?' : 31,
'@' : 32, 'A' : 33, 'B' : 34, 'C' : 35,
'D' : 36, 'E' : 37, 'F' : 38, 'G' : 39,
'H' : 40, 'I' : 41, 'J' : 42, 'K' : 43,
'L' : 44, 'M' : 45, 'N' : 46, 'O' : 47,
'P' : 48, 'Q' : 49, 'R' : 50, 'S' : 51,
'T' : 52, 'U' : 53, 'V' : 54, 'W' : 55,
'X' : 56, 'Y' : 57, 'Z' : 58, '[' : 59,
'\\' : 60, ']' : 61, '^' : 62, '_' : 63,
'\x00' : 64, '\x01' : 65, '\x02' : 66, '\x03' : 67,
'\x04' : 68, '\x05' : 69, '\x06' : 70, '\x07' : 71,
'\x08' : 72, '\x09' : 73, '\x0a' : 74, '\x0b' : 75,
'\x0c' : 76, '\x0d' : 77, '\x0e' : 78, '\x0f' : 79,
'\x10' : 80, '\x11' : 81, '\x12' : 82, '\x13' : 83,
'\x14' : 84, '\x15' : 85, '\x16' : 86, '\x17' : 87,
'\x18' : 88, '\x19' : 89, '\x1a' : 90, '\x1b' : 91,
'\x1c' : 92, '\x1d' : 93, '\x1e' : 94, '\x1f' : 95,
'\xf3' : 96, '\xf2' : 97, 'SHIFT' : 98, 'TO_C' : 99,
'TO_B' : 100, '\xf4' : 101, '\xf1' : 102
}
setb = {
' ' : 0, '!' : 1, '"' : 2, '#' : 3,
'$' : 4, '%' : 5, '&' : 6, '\'' : 7,
'(' : 8, ')' : 9, '*' : 10, '+' : 11,
',' : 12, '-' : 13, '.' : 14, '/' : 15,
'0' : 16, '1' : 17, '2' : 18, '3' : 19,
'4' : 20, '5' : 21, '6' : 22, '7' : 23,
'8' : 24, '9' : 25, ':' : 26, ';' : 27,
'<' : 28, '=' : 29, '>' : 30, '?' : 31,
'@' : 32, 'A' : 33, 'B' : 34, 'C' : 35,
'D' : 36, 'E' : 37, 'F' : 38, 'G' : 39,
'H' : 40, 'I' : 41, 'J' : 42, 'K' : 43,
'L' : 44, 'M' : 45, 'N' : 46, 'O' : 47,
'P' : 48, 'Q' : 49, 'R' : 50, 'S' : 51,
'T' : 52, 'U' : 53, 'V' : 54, 'W' : 55,
'X' : 56, 'Y' : 57, 'Z' : 58, '[' : 59,
'\\' : 60, ']' : 61, '^' : 62, '_' : 63,
'`' : 64, 'a' : 65, 'b' : 66, 'c' : 67,
'd' : 68, 'e' : 69, 'f' : 70, 'g' : 71,
'h' : 72, 'i' : 73, 'j' : 74, 'k' : 75,
'l' : 76, 'm' : 77, 'n' : 78, 'o' : 79,
'p' : 80, 'q' : 81, 'r' : 82, 's' : 83,
't' : 84, 'u' : 85, 'v' : 86, 'w' : 87,
'x' : 88, 'y' : 89, 'z' : 90, '{' : 91,
'|' : 92, '}' : 93, '~' : 94, '\x7f' : 95,
'\xf3' : 96, '\xf2' : 97, 'SHIFT' : 98, 'TO_C' : 99,
'\xf4' : 100, 'TO_A' : 101, '\xf1' : 102
}
setc = {
'00': 0, '01': 1, '02': 2, '03': 3, '04': 4,
'05': 5, '06': 6, '07': 7, '08': 8, '09': 9,
'10':10, '11':11, '12':12, '13':13, '14':14,
'15':15, '16':16, '17':17, '18':18, '19':19,
'20':20, '21':21, '22':22, '23':23, '24':24,
'25':25, '26':26, '27':27, '28':28, '29':29,
'30':30, '31':31, '32':32, '33':33, '34':34,
'35':35, '36':36, '37':37, '38':38, '39':39,
'40':40, '41':41, '42':42, '43':43, '44':44,
'45':45, '46':46, '47':47, '48':48, '49':49,
'50':50, '51':51, '52':52, '53':53, '54':54,
'55':55, '56':56, '57':57, '58':58, '59':59,
'60':60, '61':61, '62':62, '63':63, '64':64,
'65':65, '66':66, '67':67, '68':68, '69':69,
'70':70, '71':71, '72':72, '73':73, '74':74,
'75':75, '76':76, '77':77, '78':78, '79':79,
'80':80, '81':81, '82':82, '83':83, '84':84,
'85':85, '86':86, '87':87, '88':88, '89':89,
'90':90, '91':91, '92':92, '93':93, '94':94,
'95':95, '96':96, '97':97, '98':98, '99':99,
'TO_B' : 100, 'TO_A' : 101, '\xf1' : 102
}
setmap = {
'TO_A' : (seta, setb),
'TO_B' : (setb, seta),
'TO_C' : (setc, None),
'START_A' : (starta, seta, setb),
'START_B' : (startb, setb, seta),
'START_C' : (startc, setc, None),
}
tos = list(setmap.keys())
class Code128(MultiWidthBarcode):
"""
Code 128 is a very compact symbology that can encode the entire
128 character ASCII set, plus 4 special control codes,
(FNC1-FNC4, expressed in the input string as \xf1 to \xf4).
Code 128 can also encode digits at double density (2 per byte)
and has a mandatory checksum. Code 128 is well supported and
commonly used -- for example, by UPS for tracking labels.
Because of these qualities, Code 128 is probably the best choice
for a linear symbology today (assuming you have a choice).
Options that may be passed to constructor:
value (int, or numeric string. required.):
The value to encode.
barWidth (float, default .0075):
X-Dimension, or width of the smallest element
Minumum is .0075 inch (7.5 mils).
barHeight (float, see default below):
Height of the symbol. Default is the height of the two
bearer bars (if they exist) plus the greater of .25 inch
or .15 times the symbol's length.
quiet (bool, default 1):
Wether to include quiet zones in the symbol.
lquiet (float, see default below):
Quiet zone size to left of code, if quiet is true.
Default is the greater of .25 inch, or 10 barWidth
rquiet (float, defaults as above):
Quiet zone size to right left of code, if quiet is true.
Sources of Information on Code 128:
http://www.semiconductor.agilent.com/barcode/sg/Misc/code_128.html
http://www.adams1.com/pub/russadam/128code.html
http://www.barcodeman.com/c128.html
Official Spec, "ANSI/AIM BC4-1999, ISS" is available for US$45 from
http://www.aimglobal.org/aimstore/
"""
barWidth = inch * 0.0075
lquiet = None
rquiet = None
quiet = 1
barHeight = None
def __init__(self, value='', **args):
value = str(value) if isinstance(value,int) else asNative(value)
for k, v in args.items():
setattr(self, k, v)
if self.quiet:
if self.lquiet is None:
self.lquiet = max(inch * 0.25, self.barWidth * 10.0)
if self.rquiet is None:
self.rquiet = max(inch * 0.25, self.barWidth * 10.0)
else:
self.lquiet = self.rquiet = 0.0
MultiWidthBarcode.__init__(self, value)
def validate(self):
vval = ""
self.valid = 1
for c in self.value:
if ord(c) > 127 and c not in '\xf1\xf2\xf3\xf4':
self.valid = 0
continue
vval = vval + c
self.validated = vval
return vval
def _trailingDigitsToC(self, l):
# Optimization: trailing digits -> set C double-digits
c = 1
savings = -1 # the TO_C costs one character
rl = ['STOP']
while c < len(l):
i = (-c - 1)
if l[i] == '\xf1':
c += 1
rl.insert(0, '\xf1')
continue
elif len(l[i]) == 1 and l[i] in digits \
and len(l[i-1]) == 1 and l[i-1] in digits:
c += 2
savings += 1
rl.insert(0, l[i-1] + l[i])
continue
else:
break
if savings > 0:
return l[:-c] + ['TO_C'] + rl
else:
return l
def encode(self):
# First, encode using only B
s = self.validated
l = ['START_B']
for c in s:
if c not in setb:
l = l + ['TO_A', c, 'TO_B']
else:
l.append(c)
l.append('STOP')
l = self._trailingDigitsToC(l)
# Finally, replace START_X,TO_Y with START_Y
if l[1] in tos:
l[:2] = ['START_' + l[1][-1]]
# print repr(l)
# encode into numbers
start, set, shset = setmap[l[0]]
e = [start]
l = l[1:-1]
while l:
c = l[0]
if c == 'SHIFT':
e = e + [set[c], shset[l[1]]]
l = l[2:]
elif c in tos:
e.append(set[c])
set, shset = setmap[c]
l = l[1:]
else:
e.append(set[c])
l = l[1:]
c = e[0]
for i in range(1, len(e)):
c = c + i * e[i]
self.encoded = e + [c % 103, stop]
return self.encoded
def decompose(self):
self.decomposed = ''.join([_patterns[c] for c in self.encoded])
return self.decomposed
def _humanText(self):
return self.value

View File

@@ -0,0 +1,245 @@
#
# Copyright (c) 1996-2000 Tyler C. Sarna <tsarna@sarna.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
# must display the following acknowledgement:
# This product includes software developed by Tyler C. Sarna.
# 4. Neither the name of the author nor the names of contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
from reportlab.lib.units import inch
from reportlab.lib.utils import asNative
from reportlab.graphics.barcode.common import Barcode
from string import digits as string_digits
_patterns = {
'0': ("bsbSBsBsb", 0), '1': ("BsbSbsbsB", 1),
'2': ("bsBSbsbsB", 2), '3': ("BsBSbsbsb", 3),
'4': ("bsbSBsbsB", 4), '5': ("BsbSBsbsb", 5),
'6': ("bsBSBsbsb", 6), '7': ("bsbSbsBsB", 7),
'8': ("BsbSbsBsb", 8), '9': ("bsBSbsBsb", 9),
'A': ("BsbsbSbsB", 10), 'B': ("bsBsbSbsB", 11),
'C': ("BsBsbSbsb", 12), 'D': ("bsbsBSbsB", 13),
'E': ("BsbsBSbsb", 14), 'F': ("bsBsBSbsb", 15),
'G': ("bsbsbSBsB", 16), 'H': ("BsbsbSBsb", 17),
'I': ("bsBsbSBsb", 18), 'J': ("bsbsBSBsb", 19),
'K': ("BsbsbsbSB", 20), 'L': ("bsBsbsbSB", 21),
'M': ("BsBsbsbSb", 22), 'N': ("bsbsBsbSB", 23),
'O': ("BsbsBsbSb", 24), 'P': ("bsBsBsbSb", 25),
'Q': ("bsbsbsBSB", 26), 'R': ("BsbsbsBSb", 27),
'S': ("bsBsbsBSb", 28), 'T': ("bsbsBsBSb", 29),
'U': ("BSbsbsbsB", 30), 'V': ("bSBsbsbsB", 31),
'W': ("BSBsbsbsb", 32), 'X': ("bSbsBsbsB", 33),
'Y': ("BSbsBsbsb", 34), 'Z': ("bSBsBsbsb", 35),
'-': ("bSbsbsBsB", 36), '.': ("BSbsbsBsb", 37),
' ': ("bSBsbsBsb", 38), '*': ("bSbsBsBsb", None),
'$': ("bSbSbSbsb", 39), '/': ("bSbSbsbSb", 40),
'+': ("bSbsbSbSb", 41), '%': ("bsbSbSbSb", 42)
}
from reportlab.lib.utils import ascii_uppercase, ascii_lowercase
_stdchrs = string_digits + ascii_uppercase + "-. $/+%"
_extended = {
'\0': "%U", '\01': "$A", '\02': "$B", '\03': "$C",
'\04': "$D", '\05': "$E", '\06': "$F", '\07': "$G",
'\010': "$H", '\011': "$I", '\012': "$J", '\013': "$K",
'\014': "$L", '\015': "$M", '\016': "$N", '\017': "$O",
'\020': "$P", '\021': "$Q", '\022': "$R", '\023': "$S",
'\024': "$T", '\025': "$U", '\026': "$V", '\027': "$W",
'\030': "$X", '\031': "$Y", '\032': "$Z", '\033': "%A",
'\034': "%B", '\035': "%C", '\036': "%D", '\037': "%E",
'!': "/A", '"': "/B", '#': "/C", '$': "/D",
'%': "/E", '&': "/F", '\'': "/G", '(': "/H",
')': "/I", '*': "/J", '+': "/K", ',': "/L",
'/': "/O", ':': "/Z", ';': "%F", '<': "%G",
'=': "%H", '>': "%I", '?': "%J", '@': "%V",
'[': "%K", '\\': "%L", ']': "%M", '^': "%N",
'_': "%O", '`': "%W", 'a': "+A", 'b': "+B",
'c': "+C", 'd': "+D", 'e': "+E", 'f': "+F",
'g': "+G", 'h': "+H", 'i': "+I", 'j': "+J",
'k': "+K", 'l': "+L", 'm': "+M", 'n': "+N",
'o': "+O", 'p': "+P", 'q': "+Q", 'r': "+R",
's': "+S", 't': "+T", 'u': "+U", 'v': "+V",
'w': "+W", 'x': "+X", 'y': "+Y", 'z': "+Z",
'{': "%P", '|': "%Q", '}': "%R", '~': "%S",
'\177': "%T"
}
_extchrs = _stdchrs + ascii_lowercase + \
"\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017" + \
"\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" + \
"*!'#&\"(),:;<=>?@[\\]^_`{|}~\177"
def _encode39(value, cksum, stop):
v = sum([_patterns[c][1] for c in value]) % 43
if cksum:
value += _stdchrs[v]
if stop: value = '*'+value+'*'
return value
class _Code39Base(Barcode):
barWidth = inch * 0.0075
lquiet = None
rquiet = None
quiet = 1
gap = None
barHeight = None
ratio = 2.2
checksum = 1
bearers = 0.0
stop = 1
def __init__(self, value = "", **args):
value = asNative(value)
for k, v in args.items():
setattr(self, k, v)
if self.quiet:
if self.lquiet is None:
self.lquiet = max(inch * 0.25, self.barWidth * 10.0)
self.rquiet = max(inch * 0.25, self.barWidth * 10.0)
else:
self.lquiet = self.rquiet = 0.0
Barcode.__init__(self, value)
def decompose(self):
dval = ""
for c in self.encoded:
dval = dval + _patterns[c][0] + 'i'
self.decomposed = dval[:-1]
return self.decomposed
def _humanText(self):
return self.stop and self.encoded[1:-1] or self.encoded
class Standard39(_Code39Base):
"""
Options that may be passed to constructor:
value (int, or numeric string required.):
The value to encode.
barWidth (float, default .0075):
X-Dimension, or width of the smallest element
Minumum is .0075 inch (7.5 mils).
ratio (float, default 2.2):
The ratio of wide elements to narrow elements.
Must be between 2.0 and 3.0 (or 2.2 and 3.0 if the
barWidth is greater than 20 mils (.02 inch))
gap (float or None, default None):
width of intercharacter gap. None means "use barWidth".
barHeight (float, see default below):
Height of the symbol. Default is the height of the two
bearer bars (if they exist) plus the greater of .25 inch
or .15 times the symbol's length.
checksum (bool, default 1):
Wether to compute and include the check digit
bearers (float, in units of barWidth. default 0):
Height of bearer bars (horizontal bars along the top and
bottom of the barcode). Default is 0 (no bearers).
quiet (bool, default 1):
Wether to include quiet zones in the symbol.
lquiet (float, see default below):
Quiet zone size to left of code, if quiet is true.
Default is the greater of .25 inch, or .15 times the symbol's
length.
rquiet (float, defaults as above):
Quiet zone size to right left of code, if quiet is true.
stop (bool, default 1):
Whether to include start/stop symbols.
Sources of Information on Code 39:
http://www.semiconductor.agilent.com/barcode/sg/Misc/code_39.html
http://www.adams1.com/pub/russadam/39code.html
http://www.barcodeman.com/c39_1.html
Official Spec, "ANSI/AIM BC1-1995, USS" is available for US$45 from
http://www.aimglobal.org/aimstore/
"""
def validate(self):
vval = [].append
self.valid = 1
for c in self.value:
if c in ascii_lowercase:
c = c.upper()
if c not in _stdchrs:
self.valid = 0
continue
vval(c)
self.validated = ''.join(vval.__self__)
return self.validated
def encode(self):
self.encoded = _encode39(self.validated, self.checksum, self.stop)
return self.encoded
class Extended39(_Code39Base):
"""
Extended Code 39 is a convention for encoding additional characters
not present in stanmdard Code 39 by using pairs of characters to
represent the characters missing in Standard Code 39.
See Standard39 for arguments.
Sources of Information on Extended Code 39:
http://www.semiconductor.agilent.com/barcode/sg/Misc/xcode_39.html
http://www.barcodeman.com/c39_ext.html
"""
def validate(self):
vval = ""
self.valid = 1
for c in self.value:
if c not in _extchrs:
self.valid = 0
continue
vval = vval + c
self.validated = vval
return vval
def encode(self):
self.encoded = ""
for c in self.validated:
if c in _extended:
self.encoded = self.encoded + _extended[c]
elif c in _stdchrs:
self.encoded = self.encoded + c
else:
raise ValueError
self.encoded = _encode39(self.encoded, self.checksum,self.stop)
return self.encoded

View File

@@ -0,0 +1,234 @@
#
# Copyright (c) 2000 Tyler C. Sarna <tsarna@sarna.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
# must display the following acknowledgement:
# This product includes software developed by Tyler C. Sarna.
# 4. Neither the name of the author nor the names of contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
from reportlab.lib.units import inch
from reportlab.lib.utils import asNative
from reportlab.graphics.barcode.common import MultiWidthBarcode
_patterns = {
'0' : ('AcAaAb', 0), '1' : ('AaAbAc', 1), '2' : ('AaAcAb', 2),
'3' : ('AaAdAa', 3), '4' : ('AbAaAc', 4), '5' : ('AbAbAb', 5),
'6' : ('AbAcAa', 6), '7' : ('AaAaAd', 7), '8' : ('AcAbAa', 8),
'9' : ('AdAaAa', 9), 'A' : ('BaAaAc', 10), 'B' : ('BaAbAb', 11),
'C' : ('BaAcAa', 12), 'D' : ('BbAaAb', 13), 'E' : ('BbAbAa', 14),
'F' : ('BcAaAa', 15), 'G' : ('AaBaAc', 16), 'H' : ('AaBbAb', 17),
'I' : ('AaBcAa', 18), 'J' : ('AbBaAb', 19), 'K' : ('AcBaAa', 20),
'L' : ('AaAaBc', 21), 'M' : ('AaAbBb', 22), 'N' : ('AaAcBa', 23),
'O' : ('AbAaBb', 24), 'P' : ('AcAaBa', 25), 'Q' : ('BaBaAb', 26),
'R' : ('BaBbAa', 27), 'S' : ('BaAaBb', 28), 'T' : ('BaAbBa', 29),
'U' : ('BbAaBa', 30), 'V' : ('BbBaAa', 31), 'W' : ('AaBaBb', 32),
'X' : ('AaBbBa', 33), 'Y' : ('AbBaBa', 34), 'Z' : ('AbCaAa', 35),
'-' : ('AbAaCa', 36), '.' : ('CaAaAb', 37), ' ' : ('CaAbAa', 38),
'$' : ('CbAaAa', 39), '/' : ('AaBaCa', 40), '+' : ('AaCaBa', 41),
'%' : ('BaAaCa', 42), '#' : ('AbAbBa', 43), '!' : ('CaBaAa', 44),
'=' : ('CaAaBa', 45), '&' : ('AbBbAa', 46),
'start' : ('AaAaDa', -1), 'stop' : ('AaAaDaA', -2)
}
_charsbyval = {}
for k, v in _patterns.items():
_charsbyval[v[1]] = k
_extended = {
'\x00' : '!U', '\x01' : '#A', '\x02' : '#B', '\x03' : '#C',
'\x04' : '#D', '\x05' : '#E', '\x06' : '#F', '\x07' : '#G',
'\x08' : '#H', '\x09' : '#I', '\x0a' : '#J', '\x0b' : '#K',
'\x0c' : '#L', '\x0d' : '#M', '\x0e' : '#N', '\x0f' : '#O',
'\x10' : '#P', '\x11' : '#Q', '\x12' : '#R', '\x13' : '#S',
'\x14' : '#T', '\x15' : '#U', '\x16' : '#V', '\x17' : '#W',
'\x18' : '#X', '\x19' : '#Y', '\x1a' : '#Z', '\x1b' : '!A',
'\x1c' : '!B', '\x1d' : '!C', '\x1e' : '!D', '\x1f' : '!E',
'!' : '=A', '"' : '=B', '#' : '=C', '$' : '=D',
'%' : '=E', '&' : '=F', '\'' : '=G', '(' : '=H',
')' : '=I', '*' : '=J', '+' : '=K', ',' : '=L',
'/' : '=O', ':' : '=Z', ';' : '!F', '<' : '!G',
'=' : '!H', '>' : '!I', '?' : '!J', '@' : '!V',
'[' : '!K', '\\' : '!L', ']' : '!M', '^' : '!N',
'_' : '!O', '`' : '!W', 'a' : '&A', 'b' : '&B',
'c' : '&C', 'd' : '&D', 'e' : '&E', 'f' : '&F',
'g' : '&G', 'h' : '&H', 'i' : '&I', 'j' : '&J',
'k' : '&K', 'l' : '&L', 'm' : '&M', 'n' : '&N',
'o' : '&O', 'p' : '&P', 'q' : '&Q', 'r' : '&R',
's' : '&S', 't' : '&T', 'u' : '&U', 'v' : '&V',
'w' : '&W', 'x' : '&X', 'y' : '&Y', 'z' : '&Z',
'{' : '!P', '|' : '!Q', '}' : '!R', '~' : '!S',
'\x7f' : '!T'
}
def _encode93(str):
s = list(str)
s.reverse()
# compute 'C' checksum
i = 0; v = 1; c = 0
while i < len(s):
c = c + v * _patterns[s[i]][1]
i = i + 1; v = v + 1
if v > 20:
v = 1
s.insert(0, _charsbyval[c % 47])
# compute 'K' checksum
i = 0; v = 1; c = 0
while i < len(s):
c = c + v * _patterns[s[i]][1]
i = i + 1; v = v + 1
if v > 15:
v = 1
s.insert(0, _charsbyval[c % 47])
s.reverse()
return ''.join(s)
class _Code93Base(MultiWidthBarcode):
barWidth = inch * 0.0075
lquiet = None
rquiet = None
quiet = 1
barHeight = None
stop = 1
def __init__(self, value='', **args):
if type(value) is type(1):
value = asNative(value)
for (k, v) in args.items():
setattr(self, k, v)
if self.quiet:
if self.lquiet is None:
self.lquiet = max(inch * 0.25, self.barWidth * 10.0)
self.rquiet = max(inch * 0.25, self.barWidth * 10.0)
else:
self.lquiet = self.rquiet = 0.0
MultiWidthBarcode.__init__(self, value)
def decompose(self):
dval = self.stop and [_patterns['start'][0]] or []
dval += [_patterns[c][0] for c in self.encoded]
if self.stop: dval.append(_patterns['stop'][0])
self.decomposed = ''.join(dval)
return self.decomposed
class Standard93(_Code93Base):
"""
Code 93 is a Uppercase alphanumeric symbology with some punctuation.
See Extended Code 93 for a variant that can represent the entire
128 characrter ASCII set.
Options that may be passed to constructor:
value (int, or numeric string. required.):
The value to encode.
barWidth (float, default .0075):
X-Dimension, or width of the smallest element
Minumum is .0075 inch (7.5 mils).
barHeight (float, see default below):
Height of the symbol. Default is the height of the two
bearer bars (if they exist) plus the greater of .25 inch
or .15 times the symbol's length.
quiet (bool, default 1):
Wether to include quiet zones in the symbol.
lquiet (float, see default below):
Quiet zone size to left of code, if quiet is true.
Default is the greater of .25 inch, or 10 barWidth
rquiet (float, defaults as above):
Quiet zone size to right left of code, if quiet is true.
stop (bool, default 1):
Whether to include start/stop symbols.
Sources of Information on Code 93:
http://www.semiconductor.agilent.com/barcode/sg/Misc/code_93.html
Official Spec, "NSI/AIM BC5-1995, USS" is available for US$45 from
http://www.aimglobal.org/aimstore/
"""
def validate(self):
vval = ""
self.valid = 1
for c in self.value.upper():
if c not in _patterns:
self.valid = 0
continue
vval = vval + c
self.validated = vval
return vval
def encode(self):
self.encoded = _encode93(self.validated)
return self.encoded
class Extended93(_Code93Base):
"""
Extended Code 93 is a convention for encoding the entire 128 character
set using pairs of characters to represent the characters missing in
Standard Code 93. It is very much like Extended Code 39 in that way.
See Standard93 for arguments.
"""
def validate(self):
vval = []
self.valid = 1
a = vval.append
for c in self.value:
if c not in _patterns and c not in _extended:
self.valid = 0
continue
a(c)
self.validated = ''.join(vval)
return self.validated
def encode(self):
self.encoded = ""
for c in self.validated:
if c in _patterns:
self.encoded = self.encoded + c
elif c in _extended:
self.encoded = self.encoded + _extended[c]
else:
raise ValueError
self.encoded = _encode93(self.encoded)
return self.encoded
def _humanText(self):
return self.validated+self.encoded[-2:]

View File

@@ -0,0 +1,749 @@
#
# Copyright (c) 1996-2000 Tyler C. Sarna <tsarna@sarna.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
# must display the following acknowledgement:
# This product includes software developed by Tyler C. Sarna.
# 4. Neither the name of the author nor the names of contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
from reportlab.platypus.flowables import Flowable
from reportlab.lib.units import inch
from reportlab.lib.utils import ascii_uppercase, ascii_lowercase
from string import digits as string_digits
class Barcode(Flowable):
"""Abstract Base for barcodes. Includes implementations of
some methods suitable for the more primitive barcode types"""
fontName = 'Courier'
fontSize = 12
humanReadable = 0
def _humanText(self):
return self.encoded
def __init__(self, value='',**kwd):
self.value = str(value)
self._setKeywords(**kwd)
if not hasattr(self, 'gap'):
self.gap = None
def _calculate(self):
self.validate()
self.encode()
self.decompose()
self.computeSize()
def _setKeywords(self,**kwd):
for (k, v) in kwd.items():
setattr(self, k, v)
def validate(self):
self.valid = 1
self.validated = self.value
def encode(self):
self.encoded = self.validated
def decompose(self):
self.decomposed = self.encoded
def computeSize(self, *args):
barWidth = self.barWidth
wx = barWidth * self.ratio
if self.gap == None:
self.gap = barWidth
w = 0.0
for c in self.decomposed:
if c in 'sb':
w = w + barWidth
elif c in 'SB':
w = w + wx
else: # 'i'
w = w + self.gap
if self.barHeight is None:
self.barHeight = w * 0.15
self.barHeight = max(0.25 * inch, self.barHeight)
if self.bearers:
self.barHeight = self.barHeight + self.bearers * 2.0 * barWidth
if self.quiet:
w += self.lquiet + self.rquiet
self._height = self.barHeight
self._width = w
def width(self):
self._calculate()
return self._width
width = property(width)
def height(self):
self._calculate()
return self._height
height = property(height)
def draw(self):
self._calculate()
barWidth = self.barWidth
wx = barWidth * self.ratio
left = self.quiet and self.lquiet or 0
b = self.bearers * barWidth
bb = b * 0.5
tb = self.barHeight - (b * 1.5)
for c in self.decomposed:
if c == 'i':
left = left + self.gap
elif c == 's':
left = left + barWidth
elif c == 'S':
left = left + wx
elif c == 'b':
self.rect(left, bb, barWidth, tb)
left = left + barWidth
elif c == 'B':
self.rect(left, bb, wx, tb)
left = left + wx
if self.bearers:
self.rect(self.lquiet, 0, \
self._width - (self.lquiet + self.rquiet), b)
self.rect(self.lquiet, self.barHeight - b, \
self._width - (self.lquiet + self.rquiet), b)
self.drawHumanReadable()
def drawHumanReadable(self):
if self.humanReadable:
#we have text
from reportlab.pdfbase.pdfmetrics import getAscent, stringWidth
s = str(self._humanText())
fontSize = self.fontSize
fontName = self.fontName
w = stringWidth(s,fontName,fontSize)
width = self._width
if self.quiet:
width -= self.lquiet+self.rquiet
x = self.lquiet
else:
x = 0
if w>width: fontSize *= width/float(w)
y = 1.07*getAscent(fontName)*fontSize/1000.
self.annotate(x+width/2.,-y,s,fontName,fontSize)
def rect(self, x, y, w, h):
self.canv.rect(x, y, w, h, stroke=0, fill=1)
def annotate(self,x,y,text,fontName,fontSize,anchor='middle'):
canv = self.canv
canv.saveState()
canv.setFont(self.fontName,fontSize)
if anchor=='middle': func = 'drawCentredString'
elif anchor=='end': func = 'drawRightString'
else: func = 'drawString'
getattr(canv,func)(x,y,text)
canv.restoreState()
class MultiWidthBarcode(Barcode):
"""Base for variable-bar-width codes like Code93 and Code128"""
def computeSize(self, *args):
barWidth = self.barWidth
oa, oA = ord('a') - 1, ord('A') - 1
w = 0.0
for c in self.decomposed:
oc = ord(c)
if c in ascii_lowercase:
w = w + barWidth * (oc - oa)
elif c in ascii_uppercase:
w = w + barWidth * (oc - oA)
if self.barHeight is None:
self.barHeight = w * 0.15
self.barHeight = max(0.25 * inch, self.barHeight)
if self.quiet:
w += self.lquiet + self.rquiet
self._height = self.barHeight
self._width = w
def draw(self):
self._calculate()
oa, oA = ord('a') - 1, ord('A') - 1
barWidth = self.barWidth
left = self.quiet and self.lquiet or 0
for c in self.decomposed:
oc = ord(c)
if c in ascii_lowercase:
left = left + (oc - oa) * barWidth
elif c in ascii_uppercase:
w = (oc - oA) * barWidth
self.rect(left, 0, w, self.barHeight)
left += w
self.drawHumanReadable()
class I2of5(Barcode):
"""
Interleaved 2 of 5 is a numeric-only barcode. It encodes an even
number of digits; if an odd number is given, a 0 is prepended.
Options that may be passed to constructor:
value (int, or numeric string required.):
The value to encode.
barWidth (float, default .0075):
X-Dimension, or width of the smallest element
Minumum is .0075 inch (7.5 mils).
ratio (float, default 2.2):
The ratio of wide elements to narrow elements.
Must be between 2.0 and 3.0 (or 2.2 and 3.0 if the
barWidth is greater than 20 mils (.02 inch))
gap (float or None, default None):
width of intercharacter gap. None means "use barWidth".
barHeight (float, see default below):
Height of the symbol. Default is the height of the two
bearer bars (if they exist) plus the greater of .25 inch
or .15 times the symbol's length.
checksum (bool, default 1):
Whether to compute and include the check digit
bearers (float, in units of barWidth. default 3.0):
Height of bearer bars (horizontal bars along the top and
bottom of the barcode). Default is 3 x-dimensions.
Set to zero for no bearer bars. (Bearer bars help detect
misscans, so it is suggested to leave them on).
quiet (bool, default 1):
Whether to include quiet zones in the symbol.
lquiet (float, see default below):
Quiet zone size to left of code, if quiet is true.
Default is the greater of .25 inch, or .15 times the symbol's
length.
rquiet (float, defaults as above):
Quiet zone size to right left of code, if quiet is true.
stop (bool, default 1):
Whether to include start/stop symbols.
Sources of Information on Interleaved 2 of 5:
http://www.semiconductor.agilent.com/barcode/sg/Misc/i_25.html
http://www.adams1.com/pub/russadam/i25code.html
Official Spec, "ANSI/AIM BC2-1995, USS" is available for US$45 from
http://www.aimglobal.org/aimstore/
"""
patterns = {
'start' : 'bsbs',
'stop' : 'Bsb',
'B0' : 'bbBBb', 'S0' : 'ssSSs',
'B1' : 'BbbbB', 'S1' : 'SsssS',
'B2' : 'bBbbB', 'S2' : 'sSssS',
'B3' : 'BBbbb', 'S3' : 'SSsss',
'B4' : 'bbBbB', 'S4' : 'ssSsS',
'B5' : 'BbBbb', 'S5' : 'SsSss',
'B6' : 'bBBbb', 'S6' : 'sSSss',
'B7' : 'bbbBB', 'S7' : 'sssSS',
'B8' : 'BbbBb', 'S8' : 'SssSs',
'B9' : 'bBbBb', 'S9' : 'sSsSs'
}
barHeight = None
barWidth = inch * 0.0075
ratio = 2.2
checksum = 1
bearers = 3.0
quiet = 1
lquiet = None
rquiet = None
stop = 1
def __init__(self, value='', **args):
if type(value) == type(1):
value = str(value)
for k, v in args.items():
setattr(self, k, v)
if self.quiet:
if self.lquiet is None:
self.lquiet = min(inch * 0.25, self.barWidth * 10.0)
self.rquiet = min(inch * 0.25, self.barWidth * 10.0)
else:
self.lquiet = self.rquiet = 0.0
Barcode.__init__(self, value)
def validate(self):
vval = ""
self.valid = 1
for c in self.value.strip():
if c not in string_digits:
self.valid = 0
continue
vval = vval + c
self.validated = vval
return vval
def encode(self):
s = self.validated
cs = self.checksum
c = len(s)
#ensure len(result)%2 == 0, checksum included
if ((c % 2 == 0) and cs) or ((c % 2 == 1) and not cs):
s = '0' + s
c += 1
if cs:
c = 3*sum([int(s[i]) for i in range(0,c,2)])+sum([int(s[i]) for i in range(1,c,2)])
s += str((10 - c) % 10)
self.encoded = s
def decompose(self):
dval = self.stop and [self.patterns['start']] or []
a = dval.append
for i in range(0, len(self.encoded), 2):
b = self.patterns['B' + self.encoded[i]]
s = self.patterns['S' + self.encoded[i+1]]
for i in range(0, len(b)):
a(b[i] + s[i])
if self.stop: a(self.patterns['stop'])
self.decomposed = ''.join(dval)
return self.decomposed
class MSI(Barcode):
"""
MSI is a numeric-only barcode.
Options that may be passed to constructor:
value (int, or numeric string required.):
The value to encode.
barWidth (float, default .0075):
X-Dimension, or width of the smallest element
ratio (float, default 2.2):
The ratio of wide elements to narrow elements.
gap (float or None, default None):
width of intercharacter gap. None means "use barWidth".
barHeight (float, see default below):
Height of the symbol. Default is the height of the two
bearer bars (if they exist) plus the greater of .25 inch
or .15 times the symbol's length.
checksum (bool, default 1):
Wether to compute and include the check digit
bearers (float, in units of barWidth. default 0):
Height of bearer bars (horizontal bars along the top and
bottom of the barcode). Default is 0 (no bearers).
lquiet (float, see default below):
Quiet zone size to left of code, if quiet is true.
Default is the greater of .25 inch, or 10 barWidths.
rquiet (float, defaults as above):
Quiet zone size to right left of code, if quiet is true.
stop (bool, default 1):
Whether to include start/stop symbols.
Sources of Information on MSI Bar Code:
http://www.semiconductor.agilent.com/barcode/sg/Misc/msi_code.html
http://www.adams1.com/pub/russadam/plessy.html
"""
patterns = {
'start' : 'Bs', 'stop' : 'bSb',
'0' : 'bSbSbSbS', '1' : 'bSbSbSBs',
'2' : 'bSbSBsbS', '3' : 'bSbSBsBs',
'4' : 'bSBsbSbS', '5' : 'bSBsbSBs',
'6' : 'bSBsBsbS', '7' : 'bSBsBsBs',
'8' : 'BsbSbSbS', '9' : 'BsbSbSBs'
}
stop = 1
barHeight = None
barWidth = inch * 0.0075
ratio = 2.2
checksum = 1
bearers = 0.0
quiet = 1
lquiet = None
rquiet = None
def __init__(self, value="", **args):
if type(value) == type(1):
value = str(value)
for k, v in args.items():
setattr(self, k, v)
if self.quiet:
if self.lquiet is None:
self.lquiet = max(inch * 0.25, self.barWidth * 10.0)
self.rquiet = max(inch * 0.25, self.barWidth * 10.0)
else:
self.lquiet = self.rquiet = 0.0
Barcode.__init__(self, value)
def validate(self):
vval = ""
self.valid = 1
for c in self.value.strip():
if c not in string_digits:
self.valid = 0
continue
vval = vval + c
self.validated = vval
return vval
def encode(self):
s = self.validated
if self.checksum:
c = ''
for i in range(1, len(s), 2):
c = c + s[i]
d = str(int(c) * 2)
t = 0
for c in d:
t = t + int(c)
for i in range(0, len(s), 2):
t = t + int(s[i])
c = 10 - (t % 10)
s = s + str(c)
self.encoded = s
def decompose(self):
dval = self.stop and [self.patterns['start']] or []
dval += [self.patterns[c] for c in self.encoded]
if self.stop: dval.append(self.patterns['stop'])
self.decomposed = ''.join(dval)
return self.decomposed
class Codabar(Barcode):
"""
Codabar is a numeric plus some puntuation ("-$:/.+") barcode
with four start/stop characters (A, B, C, and D).
Options that may be passed to constructor:
value (string required.):
The value to encode.
barWidth (float, default .0065):
X-Dimension, or width of the smallest element
minimum is 6.5 mils (.0065 inch)
ratio (float, default 2.0):
The ratio of wide elements to narrow elements.
gap (float or None, default None):
width of intercharacter gap. None means "use barWidth".
barHeight (float, see default below):
Height of the symbol. Default is the height of the two
bearer bars (if they exist) plus the greater of .25 inch
or .15 times the symbol's length.
checksum (bool, default 0):
Whether to compute and include the check digit
bearers (float, in units of barWidth. default 0):
Height of bearer bars (horizontal bars along the top and
bottom of the barcode). Default is 0 (no bearers).
quiet (bool, default 1):
Whether to include quiet zones in the symbol.
stop (bool, default 1):
Whether to include start/stop symbols.
lquiet (float, see default below):
Quiet zone size to left of code, if quiet is true.
Default is the greater of .25 inch, or 10 barWidth
rquiet (float, defaults as above):
Quiet zone size to right left of code, if quiet is true.
Sources of Information on Codabar
http://www.semiconductor.agilent.com/barcode/sg/Misc/codabar.html
http://www.barcodeman.com/codabar.html
Official Spec, "ANSI/AIM BC3-1995, USS" is available for US$45 from
http://www.aimglobal.org/aimstore/
"""
patterns = {
'0': 'bsbsbSB', '1': 'bsbsBSb', '2': 'bsbSbsB',
'3': 'BSbsbsb', '4': 'bsBsbSb', '5': 'BsbsbSb',
'6': 'bSbsbsB', '7': 'bSbsBsb', '8': 'bSBsbsb',
'9': 'BsbSbsb', '-': 'bsbSBsb', '$': 'bsBSbsb',
':': 'BsbsBsB', '/': 'BsBsbsB', '.': 'BsBsBsb',
'+': 'bsBsBsB', 'A': 'bsBSbSb', 'B': 'bSbSbsB',
'C': 'bsbSbSB', 'D': 'bsbSBSb'
}
values = {
'0' : 0, '1' : 1, '2' : 2, '3' : 3, '4' : 4,
'5' : 5, '6' : 6, '7' : 7, '8' : 8, '9' : 9,
'-' : 10, '$' : 11, ':' : 12, '/' : 13, '.' : 14,
'+' : 15, 'A' : 16, 'B' : 17, 'C' : 18, 'D' : 19
}
chars = string_digits + "-$:/.+"
stop = 1
barHeight = None
barWidth = inch * 0.0065
ratio = 2.0 # XXX ?
checksum = 0
bearers = 0.0
quiet = 1
lquiet = None
rquiet = None
def __init__(self, value='', **args):
if type(value) == type(1):
value = str(value)
for k, v in args.items():
setattr(self, k, v)
if self.quiet:
if self.lquiet is None:
self.lquiet = min(inch * 0.25, self.barWidth * 10.0)
self.rquiet = min(inch * 0.25, self.barWidth * 10.0)
else:
self.lquiet = self.rquiet = 0.0
Barcode.__init__(self, value)
def validate(self):
vval = ""
self.valid = 1
s = self.value.strip()
for i in range(0, len(s)):
c = s[i]
if c not in self.chars:
if ((i != 0) and (i != len(s) - 1)) or (c not in 'ABCD'):
self.Valid = 0
continue
vval = vval + c
if self.stop:
if vval[0] not in 'ABCD':
vval = 'A' + vval
if vval[-1] not in 'ABCD':
vval = vval + vval[0]
self.validated = vval
return vval
def encode(self):
s = self.validated
if self.checksum:
v = sum([self.values[c] for c in s])
s += self.chars[v % 16]
self.encoded = s
def decompose(self):
dval = ''.join([self.patterns[c]+'i' for c in self.encoded])
self.decomposed = dval[:-1]
return self.decomposed
class Code11(Barcode):
"""
Code 11 is an almost-numeric barcode. It encodes the digits 0-9 plus
dash ("-"). 11 characters total, hence the name.
value (int or string required.):
The value to encode.
barWidth (float, default .0075):
X-Dimension, or width of the smallest element
ratio (float, default 2.2):
The ratio of wide elements to narrow elements.
gap (float or None, default None):
width of intercharacter gap. None means "use barWidth".
barHeight (float, see default below):
Height of the symbol. Default is the height of the two
bearer bars (if they exist) plus the greater of .25 inch
or .15 times the symbol's length.
checksum (0 none, 1 1-digit, 2 2-digit, -1 auto, default -1):
How many checksum digits to include. -1 ("auto") means
1 if the number of digits is 10 or less, else 2.
bearers (float, in units of barWidth. default 0):
Height of bearer bars (horizontal bars along the top and
bottom of the barcode). Default is 0 (no bearers).
quiet (bool, default 1):
Wether to include quiet zones in the symbol.
lquiet (float, see default below):
Quiet zone size to left of code, if quiet is true.
Default is the greater of .25 inch, or 10 barWidth
rquiet (float, defaults as above):
Quiet zone size to right left of code, if quiet is true.
Sources of Information on Code 11:
http://www.cwi.nl/people/dik/english/codes/barcodes.html
"""
chars = '0123456789-'
patterns = {
'0' : 'bsbsB', '1' : 'BsbsB', '2' : 'bSbsB',
'3' : 'BSbsb', '4' : 'bsBsB', '5' : 'BsBsb',
'6' : 'bSBsb', '7' : 'bsbSB', '8' : 'BsbSb',
'9' : 'Bsbsb', '-' : 'bsBsb', 'S' : 'bsBSb' # Start/Stop
}
values = {
'0' : 0, '1' : 1, '2' : 2, '3' : 3, '4' : 4,
'5' : 5, '6' : 6, '7' : 7, '8' : 8, '9' : 9,
'-' : 10,
}
stop = 1
barHeight = None
barWidth = inch * 0.0075
ratio = 2.2 # XXX ?
checksum = -1 # Auto
bearers = 0.0
quiet = 1
lquiet = None
rquiet = None
def __init__(self, value='', **args):
if type(value) == type(1):
value = str(value)
for k, v in args.items():
setattr(self, k, v)
if self.quiet:
if self.lquiet is None:
self.lquiet = min(inch * 0.25, self.barWidth * 10.0)
self.rquiet = min(inch * 0.25, self.barWidth * 10.0)
else:
self.lquiet = self.rquiet = 0.0
Barcode.__init__(self, value)
def validate(self):
vval = ""
self.valid = 1
s = self.value.strip()
for i in range(0, len(s)):
c = s[i]
if c not in self.chars:
self.Valid = 0
continue
vval = vval + c
self.validated = vval
return vval
def _addCSD(self,s,m):
# compute first checksum
i = c = 0
v = 1
V = self.values
while i < len(s):
c += v * V[s[-(i+1)]]
i += 1
v += 1
if v==m:
v = 1
return s+self.chars[c % 11]
def encode(self):
s = self.validated
tcs = self.checksum
if tcs<0:
self.checksum = tcs = 1+int(len(s)>10)
if tcs > 0: s = self._addCSD(s,11)
if tcs > 1: s = self._addCSD(s,10)
self.encoded = self.stop and ('S' + s + 'S') or s
def decompose(self):
self.decomposed = ''.join([(self.patterns[c]+'i') for c in self.encoded])[:-1]
return self.decomposed
def _humanText(self):
return self.stop and self.encoded[1:-1] or self.encoded

View File

@@ -0,0 +1,350 @@
__all__=(
'Ean13BarcodeWidget','isEanString',
)
from reportlab.graphics.shapes import Group, String, Rect
from reportlab.lib import colors
from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.lib.validators import isNumber, isColor, isString, Validator, isBoolean
from reportlab.lib.attrmap import *
from reportlab.graphics.charts.areas import PlotArea
from reportlab.lib.units import mm
from reportlab.lib.utils import asNative
#work out a list of manufacturer codes....
_eanNumberSystems = [
('00-13', 'USA & Canada'),
('20-29', 'In-Store Functions'),
('30-37', 'France'),
('40-44', 'Germany'),
('45', 'Japan (also 49)'),
('46', 'Russian Federation'),
('471', 'Taiwan'),
('474', 'Estonia'),
('475', 'Latvia'),
('477', 'Lithuania'),
('479', 'Sri Lanka'),
('480', 'Philippines'),
('482', 'Ukraine'),
('484', 'Moldova'),
('485', 'Armenia'),
('486', 'Georgia'),
('487', 'Kazakhstan'),
('489', 'Hong Kong'),
('49', 'Japan (JAN-13)'),
('50', 'United Kingdom'),
('520', 'Greece'),
('528', 'Lebanon'),
('529', 'Cyprus'),
('531', 'Macedonia'),
('535', 'Malta'),
('539', 'Ireland'),
('54', 'Belgium & Luxembourg'),
('560', 'Portugal'),
('569', 'Iceland'),
('57', 'Denmark'),
('590', 'Poland'),
('594', 'Romania'),
('599', 'Hungary'),
('600-601', 'South Africa'),
('609', 'Mauritius'),
('611', 'Morocco'),
('613', 'Algeria'),
('619', 'Tunisia'),
('622', 'Egypt'),
('625', 'Jordan'),
('626', 'Iran'),
('64', 'Finland'),
('690-692', 'China'),
('70', 'Norway'),
('729', 'Israel'),
('73', 'Sweden'),
('740', 'Guatemala'),
('741', 'El Salvador'),
('742', 'Honduras'),
('743', 'Nicaragua'),
('744', 'Costa Rica'),
('746', 'Dominican Republic'),
('750', 'Mexico'),
('759', 'Venezuela'),
('76', 'Switzerland'),
('770', 'Colombia'),
('773', 'Uruguay'),
('775', 'Peru'),
('777', 'Bolivia'),
('779', 'Argentina'),
('780', 'Chile'),
('784', 'Paraguay'),
('785', 'Peru'),
('786', 'Ecuador'),
('789', 'Brazil'),
('80-83', 'Italy'),
('84', 'Spain'),
('850', 'Cuba'),
('858', 'Slovakia'),
('859', 'Czech Republic'),
('860', 'Yugloslavia'),
('869', 'Turkey'),
('87', 'Netherlands'),
('880', 'South Korea'),
('885', 'Thailand'),
('888', 'Singapore'),
('890', 'India'),
('893', 'Vietnam'),
('899', 'Indonesia'),
('90-91', 'Austria'),
('93', 'Australia'),
('94', 'New Zealand'),
('955', 'Malaysia'),
('977', 'International Standard Serial Number for Periodicals (ISSN)'),
('978', 'International Standard Book Numbering (ISBN)'),
('979', 'International Standard Music Number (ISMN)'),
('980', 'Refund receipts'),
('981-982', 'Common Currency Coupons'),
('99', 'Coupons')
]
manufacturerCodes = {}
for (k, v) in _eanNumberSystems:
words = k.split('-')
if len(words)==2:
fromCode = int(words[0])
toCode = int(words[1])
for code in range(fromCode, toCode+1):
manufacturerCodes[code] = v
else:
manufacturerCodes[int(k)] = v
def nDigits(n):
class _ndigits(Validator):
def test(self,x):
return type(x) is str and len(x)<=n and len([c for c in x if c in "0123456789"])==n
return _ndigits()
class Ean13BarcodeWidget(PlotArea):
codeName = "EAN13"
_attrMap = AttrMap(BASE=PlotArea,
value = AttrMapValue(nDigits(12), desc='the number'),
fontName = AttrMapValue(isString, desc='fontName'),
fontSize = AttrMapValue(isNumber, desc='font size'),
x = AttrMapValue(isNumber, desc='x-coord'),
y = AttrMapValue(isNumber, desc='y-coord'),
barFillColor = AttrMapValue(isColor, desc='bar color'),
barHeight = AttrMapValue(isNumber, desc='Height of bars.'),
barWidth = AttrMapValue(isNumber, desc='Width of bars.'),
barStrokeWidth = AttrMapValue(isNumber, desc='Width of bar borders.'),
barStrokeColor = AttrMapValue(isColor, desc='Color of bar borders.'),
textColor = AttrMapValue(isColor, desc='human readable text color'),
humanReadable = AttrMapValue(isBoolean, desc='if human readable'),
quiet = AttrMapValue(isBoolean, desc='if quiet zone to be used'),
lquiet = AttrMapValue(isBoolean, desc='left quiet zone length'),
rquiet = AttrMapValue(isBoolean, desc='right quiet zone length'),
)
_digits=12
_start_right = 7 #for ean-13 left = [0:7] right=[7:13]
_nbars = 113
barHeight = 25.93*mm #millimeters
barWidth = (37.29/_nbars)*mm
humanReadable = 1
_0csw = 1
_1csw = 3
#Left Hand Digits.
_left = ( ("0001101", "0011001", "0010011", "0111101",
"0100011", "0110001", "0101111", "0111011",
"0110111", "0001011",
), #odd left hand digits
("0100111", "0110011", "0011011", "0100001",
"0011101", "0111001", "0000101", "0010001",
"0001001", "0010111"), #even left hand digits
)
_right = ("1110010", "1100110", "1101100", "1000010",
"1011100", "1001110", "1010000", "1000100",
"1001000", "1110100")
quiet = 1
rquiet = lquiet = None
_tail = "101"
_sep = "01010"
_lhconvert={
"0": (0,0,0,0,0,0),
"1": (0,0,1,0,1,1),
"2": (0,0,1,1,0,1),
"3": (0,0,1,1,1,0),
"4": (0,1,0,0,1,1),
"5": (0,1,1,0,0,1),
"6": (0,1,1,1,0,0),
"7": (0,1,0,1,0,1),
"8": (0,1,0,1,1,0),
"9": (0,1,1,0,1,0)
}
fontSize = 8 #millimeters
fontName = 'Helvetica'
textColor = barFillColor = colors.black
barStrokeColor = None
barStrokeWidth = 0
x = 0
y = 0
def __init__(self,value='123456789012',**kw):
value = str(value) if isinstance(value,int) else asNative(value)
self.value=max(self._digits-len(value),0)*'0'+value[:self._digits]
for k, v in kw.items():
setattr(self, k, v)
width = property(lambda self: self.barWidth*(self._nbars-18+self._calc_quiet(self.lquiet)+self._calc_quiet(self.rquiet)))
def wrap(self,aW,aH):
return self.width,self.barHeight
def _encode_left(self,s,a):
cp = self._lhconvert[s[0]] #convert the left hand numbers
_left = self._left
z = ord('0')
for i,c in enumerate(s[1:self._start_right]):
a(_left[cp[i]][ord(c)-z])
def _short_bar(self,i):
i += 9 - self._lquiet
return self.humanReadable and ((12<i<55) or (57<i<101))
def _calc_quiet(self,v):
if self.quiet:
if v is None:
v = 9
else:
x = float(max(v,0))/self.barWidth
v = int(x)
if v-x>0: v += 1
else:
v = 0
return v
def draw(self):
g = Group()
gAdd = g.add
barWidth = self.barWidth
width = self.width
barHeight = self.barHeight
x = self.x
y = self.y
gAdd(Rect(x,y,width,barHeight,fillColor=None,strokeColor=None,strokeWidth=0))
s = self.value+self._checkdigit(self.value)
self._lquiet = lquiet = self._calc_quiet(self.lquiet)
rquiet = self._calc_quiet(self.rquiet)
b = [lquiet*'0',self._tail] #the signal string
a = b.append
self._encode_left(s,a)
a(self._sep)
z = ord('0')
_right = self._right
for c in s[self._start_right:]:
a(_right[ord(c)-z])
a(self._tail)
a(rquiet*'0')
fontSize = self.fontSize
barFillColor = self.barFillColor
barStrokeWidth = self.barStrokeWidth
barStrokeColor = self.barStrokeColor
fth = fontSize*1.2
b = ''.join(b)
lrect = None
for i,c in enumerate(b):
if c=="1":
dh = self._short_bar(i) and fth or 0
yh = y+dh
if lrect and lrect.y==yh:
lrect.width += barWidth
else:
lrect = Rect(x,yh,barWidth,barHeight-dh,fillColor=barFillColor,strokeWidth=barStrokeWidth,strokeColor=barStrokeColor)
gAdd(lrect)
else:
lrect = None
x += barWidth
if self.humanReadable: self._add_human_readable(s,gAdd)
return g
def _add_human_readable(self,s,gAdd):
barWidth = self.barWidth
fontSize = self.fontSize
textColor = self.textColor
fontName = self.fontName
fth = fontSize*1.2
# draw the num below the line.
c = s[0]
w = stringWidth(c,fontName,fontSize)
x = self.x+barWidth*(self._lquiet-8)
y = self.y + 0.2*fth
gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor))
x = self.x + (33-9+self._lquiet)*barWidth
c = s[1:7]
gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle'))
x += 47*barWidth
c = s[7:]
gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle'))
def _checkdigit(cls,num):
z = ord('0')
iSum = cls._0csw*sum([(ord(x)-z) for x in num[::2]]) \
+ cls._1csw*sum([(ord(x)-z) for x in num[1::2]])
return chr(z+((10-(iSum%10))%10))
_checkdigit=classmethod(_checkdigit)
class Ean8BarcodeWidget(Ean13BarcodeWidget):
codeName = "EAN8"
_attrMap = AttrMap(BASE=Ean13BarcodeWidget,
value = AttrMapValue(nDigits(7), desc='the number'),
)
_start_right = 4 #for ean-13 left = [0:7] right=[7:13]
_nbars = 85
_digits=7
_0csw = 3
_1csw = 1
def _encode_left(self,s,a):
cp = self._lhconvert[s[0]] #convert the left hand numbers
_left = self._left[0]
z = ord('0')
for i,c in enumerate(s[0:self._start_right]):
a(_left[ord(c)-z])
def _short_bar(self,i):
i += 9 - self._lquiet
return self.humanReadable and ((12<i<41) or (43<i<73))
def _add_human_readable(self,s,gAdd):
barWidth = self.barWidth
fontSize = self.fontSize
textColor = self.textColor
fontName = self.fontName
fth = fontSize*1.2
# draw the num below the line.
y = self.y + 0.2*fth
x = (26.5-9+self._lquiet)*barWidth
c = s[0:4]
gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle'))
x = (59.5-9+self._lquiet)*barWidth
c = s[4:]
gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle'))
class UPCA(Ean13BarcodeWidget):
codeName = "UPCA"
_attrMap = AttrMap(BASE=Ean13BarcodeWidget,
value = AttrMapValue(nDigits(11), desc='the number'),
)
_start_right = 6
_digits = 11
_0csw = 3
_1csw = 1
_nbars = 1+7*11+2*3+5

View File

@@ -0,0 +1,81 @@
#
# Copyright (c) 2000 Tyler C. Sarna <tsarna@sarna.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
# must display the following acknowledgement:
# This product includes software developed by Tyler C. Sarna.
# 4. Neither the name of the author nor the names of contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
from reportlab.lib.units import inch
from reportlab.graphics.barcode.common import Barcode
import string
# . 3 T Tracker
# , 2 D Descender
# ' 1 A Ascender
# | 0 H Ascender/Descender
_rm_patterns = {
"0" : "--||", "1" : "-',|", "2" : "-'|,", "3" : "'-,|",
"4" : "'-|,", "5" : "'',,", "6" : "-,'|", "7" : "-|-|",
"8" : "-|',", "9" : "',-|", "A" : "',',", "B" : "'|-,",
"C" : "-,|'", "D" : "-|,'", "E" : "-||-", "F" : "',,'",
"G" : "',|-", "H" : "'|,-", "I" : ",-'|", "J" : ",'-|",
"K" : ",'',", "L" : "|--|", "M" : "|-',", "N" : "|'-,",
"O" : ",-|'", "P" : ",','", "Q" : ",'|-", "R" : "|-,'",
"S" : "|-|-", "T" : "|',-", "U" : ",,''", "V" : ",|-'",
"W" : ",|'-", "X" : "|,-'", "Y" : "|,'-", "Z" : "||--",
# start, stop
"(" : "'-,'", ")" : "'|,|"
}
_ozN_patterns = {
"0" : "||", "1" : "|'", "2" : "|,", "3" : "'|", "4" : "''",
"5" : "',", "6" : ",|", "7" : ",'", "8" : ",,", "9" : ".|"
}
_ozC_patterns = {
"A" : "|||", "B" : "||'", "C" : "||,", "D" : "|'|",
"E" : "|''", "F" : "|',", "G" : "|,|", "H" : "|,'",
"I" : "|,,", "J" : "'||", "K" : "'|'", "L" : "'|,",
"M" : "''|", "N" : "'''", "O" : "'',", "P" : "',|",
"Q" : "','", "R" : "',,", "S" : ",||", "T" : ",|'",
"U" : ",|,", "V" : ",'|", "W" : ",''", "X" : ",',",
"Y" : ",,|", "Z" : ",,'", "a" : "|,.", "b" : "|.|",
"c" : "|.'", "d" : "|.,", "e" : "|..", "f" : "'|.",
"g" : "''.", "h" : "',.", "i" : "'.|", "j" : "'.'",
"k" : "'.,", "l" : "'..", "m" : ",|.", "n" : ",'.",
"o" : ",,.", "p" : ",.|", "q" : ",.'", "r" : ",.,",
"s" : ",..", "t" : ".|.", "u" : ".'.", "v" : ".,.",
"w" : "..|", "x" : "..'", "y" : "..,", "z" : "...",
"0" : ",,,", "1" : ".||", "2" : ".|'", "3" : ".|,",
"4" : ".'|", "5" : ".''", "6" : ".',", "7" : ".,|",
"8" : ".,'", "9" : ".,,", " " : "||.", "#" : "|'.",
}
#http://www.auspost.com.au/futurepost/

View File

@@ -0,0 +1,196 @@
# (c) 2008 Jerome Alet - <alet@librelogiciel.com>
# Licensing terms : ReportLab's license.
from reportlab.graphics.barcode.code39 import Standard39
from reportlab.lib import colors
from reportlab.lib.units import cm
from string import digits as string_digits
from reportlab.lib.utils import ascii_uppercase
class BaseLTOLabel(Standard39) :
"""
Base class for LTO labels.
Specification taken from "IBM LTO Ultrium Cartridge Label Specification, Revision 3"
available on May 14th 2008 from :
http://www-1.ibm.com/support/docview.wss?rs=543&context=STCVQ6R&q1=ssg1*&uid=ssg1S7000429&loc=en_US&cs=utf-8&lang=en+en
"""
LABELWIDTH = 7.9 * cm
LABELHEIGHT = 1.7 * cm
LABELROUND = 0.15 * cm
CODERATIO = 2.75
CODENOMINALWIDTH = 7.4088 * cm
CODEBARHEIGHT = 1.11 * cm
CODEBARWIDTH = 0.0432 * cm
CODEGAP = CODEBARWIDTH
CODELQUIET = 10 * CODEBARWIDTH
CODERQUIET = 10 * CODEBARWIDTH
def __init__(self, prefix="",
number=None,
subtype="1",
border=None,
checksum=False,
availheight=None) :
"""
Initializes an LTO label.
prefix : Up to six characters from [A-Z][0-9]. Defaults to "".
number : Label's number or None. Defaults to None.
subtype : LTO subtype string , e.g. "1" for LTO1. Defaults to "1".
border : None, or the width of the label's border. Defaults to None.
checksum : Boolean indicates if checksum char has to be printed. Defaults to False.
availheight : Available height on the label, or None for automatic. Defaults to None.
"""
self.height = max(availheight, self.CODEBARHEIGHT)
self.border = border
if (len(subtype) != 1) \
or (subtype not in ascii_uppercase + string_digits) :
raise ValueError("Invalid subtype '%s'" % subtype)
if ((not number) and (len(prefix) > 6)) \
or not prefix.isalnum() :
raise ValueError("Invalid prefix '%s'" % prefix)
label = "%sL%s" % ((prefix + str(number or 0).zfill(6 - len(prefix)))[:6],
subtype)
if len(label) != 8 :
raise ValueError("Invalid set of parameters (%s, %s, %s)" \
% (prefix, number, subtype))
self.label = label
Standard39.__init__(self,
label,
ratio=self.CODERATIO,
barHeight=self.height,
barWidth=self.CODEBARWIDTH,
gap=self.CODEGAP,
lquiet=self.CODELQUIET,
rquiet=self.CODERQUIET,
quiet=True,
checksum=checksum)
def drawOn(self, canvas, x, y) :
"""Draws the LTO label onto the canvas."""
canvas.saveState()
canvas.translate(x, y)
if self.border :
canvas.setLineWidth(self.border)
canvas.roundRect(0, 0,
self.LABELWIDTH,
self.LABELHEIGHT,
self.LABELROUND)
Standard39.drawOn(self,
canvas,
(self.LABELWIDTH-self.CODENOMINALWIDTH)/2.0,
self.LABELHEIGHT-self.height)
canvas.restoreState()
class VerticalLTOLabel(BaseLTOLabel) :
"""
A class for LTO labels with rectangular blocks around the tape identifier.
"""
LABELFONT = ("Helvetica-Bold", 14)
BLOCKWIDTH = 1*cm
BLOCKHEIGHT = 0.45*cm
LINEWIDTH = 0.0125
NBBLOCKS = 7
COLORSCHEME = ("red",
"yellow",
"lightgreen",
"lightblue",
"grey",
"orangered",
"pink",
"darkgreen",
"orange",
"purple")
def __init__(self, *args, **kwargs) :
"""
Initializes the label.
colored : boolean to determine if blocks have to be colorized.
"""
if "colored" in kwargs:
self.colored = kwargs["colored"]
del kwargs["colored"]
else :
self.colored = False
kwargs["availheight"] = self.LABELHEIGHT-self.BLOCKHEIGHT
BaseLTOLabel.__init__(self, *args, **kwargs)
def drawOn(self, canvas, x, y) :
"""Draws some blocks around the identifier's characters."""
BaseLTOLabel.drawOn(self,
canvas,
x,
y)
canvas.saveState()
canvas.setLineWidth(self.LINEWIDTH)
canvas.setStrokeColorRGB(0, 0, 0)
canvas.translate(x, y)
xblocks = (self.LABELWIDTH-(self.NBBLOCKS*self.BLOCKWIDTH))/2.0
for i in range(self.NBBLOCKS) :
(font, size) = self.LABELFONT
newfont = self.LABELFONT
if i == (self.NBBLOCKS - 1) :
part = self.label[i:]
(font, size) = newfont
size /= 2.0
newfont = (font, size)
else :
part = self.label[i]
canvas.saveState()
canvas.translate(xblocks+(i*self.BLOCKWIDTH), 0)
if self.colored and part.isdigit() :
canvas.setFillColorRGB(*getattr(colors,
self.COLORSCHEME[int(part)],
colors.Color(1, 1, 1)).rgb())
else:
canvas.setFillColorRGB(1, 1, 1)
canvas.rect(0, 0, self.BLOCKWIDTH, self.BLOCKHEIGHT, fill=True)
canvas.translate((self.BLOCKWIDTH+canvas.stringWidth(part, *newfont))/2.0,
(self.BLOCKHEIGHT/2.0))
canvas.rotate(90.0)
canvas.setFont(*newfont)
canvas.setFillColorRGB(0, 0, 0)
canvas.drawCentredString(0, 0, part)
canvas.restoreState()
canvas.restoreState()
def test() :
"""Test this."""
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib import pagesizes
canvas = Canvas("labels.pdf", pagesize=pagesizes.A4)
canvas.setFont("Helvetica", 30)
(width, height) = pagesizes.A4
canvas.drawCentredString(width/2.0, height-4*cm, "Sample LTO labels")
xpos = xorig = 2 * cm
ypos = yorig = 2 * cm
colwidth = 10 * cm
lineheight = 3.9 * cm
count = 1234
BaseLTOLabel("RL", count, "3").drawOn(canvas, xpos, ypos)
ypos += lineheight
count += 1
BaseLTOLabel("RL", count, "3",
border=0.0125).drawOn(canvas, xpos, ypos)
ypos += lineheight
count += 1
VerticalLTOLabel("RL", count, "3").drawOn(canvas, xpos, ypos)
ypos += lineheight
count += 1
VerticalLTOLabel("RL", count, "3",
border=0.0125).drawOn(canvas, xpos, ypos)
ypos += lineheight
count += 1
VerticalLTOLabel("RL", count, "3",
colored=True).drawOn(canvas, xpos, ypos)
ypos += lineheight
count += 1
VerticalLTOLabel("RL", count, "3",
border=0.0125, colored=True).drawOn(canvas, xpos, ypos)
canvas.showPage()
canvas.save()
if __name__ == "__main__" :
test()

View File

@@ -0,0 +1,209 @@
#
# ReportLab QRCode widget
#
# Ported from the Javascript library QRCode for Javascript by Sam Curren
#
# URL: http://www.d-project.com/
# http://d-project.googlecode.com/svn/trunk/misc/qrcode/js/qrcode.js
# qrcode.js is copyright (c) 2009 Kazuhiko Arase
#
# Original ReportLab module by German M. Bravo
#
# modified and improved by Anders Hammarquist <iko@openend.se>
# and used with permission under the ReportLab License
#
# The word "QR Code" is registered trademark of
# DENSO WAVE INCORPORATED
# http://www.denso-wave.com/qrcode/faqpatent-e.html
__all__ = ('QrCodeWidget')
import itertools
from reportlab.platypus.flowables import Flowable
from reportlab.graphics.shapes import Group, Rect
from reportlab.lib import colors
from reportlab.lib.validators import isNumber, isNumberOrNone, isColor, isString, Validator
from reportlab.lib.attrmap import AttrMap, AttrMapValue
from reportlab.graphics.widgetbase import Widget
from reportlab.lib.units import mm
try:
from reportlab.lib.utils import asUnicodeEx, isUnicode
except ImportError:
# ReportLab 2.x compatibility
def asUnicodeEx(v, enc='utf8'):
if isinstance(v, unicode):
return v
if isinstance(v, str):
return v.decode(enc)
return str(v).decode(enc)
def isUnicode(v):
return isinstance(v, unicode)
from reportlab.graphics.barcode import qrencoder
class isLevel(Validator):
def test(self, x):
return x in ['L', 'M', 'Q', 'H']
isLevel = isLevel()
class isUnicodeOrQRList(Validator):
def _test(self, x):
if isUnicode(x):
return True
if all(isinstance(v, qrencoder.QR) for v in x):
return True
return False
def test(self, x):
return self._test(x) or self.normalizeTest(x)
def normalize(self, x):
if self._test(x):
return x
try:
return asUnicodeEx(x)
except UnicodeError:
raise ValueError("Can't convert to unicode: %r" % x)
isUnicodeOrQRList = isUnicodeOrQRList()
class SRect(Rect):
def __init__(self, x, y, width, height, fillColor=colors.black):
Rect.__init__(self, x, y, width, height, fillColor=fillColor,
strokeColor=None, strokeWidth=0)
class QrCodeWidget(Widget):
codeName = "QR"
_attrMap = AttrMap(
BASE = Widget,
value = AttrMapValue(isUnicodeOrQRList, desc='QRCode data'),
x = AttrMapValue(isNumber, desc='x-coord'),
y = AttrMapValue(isNumber, desc='y-coord'),
barFillColor = AttrMapValue(isColor, desc='bar color'),
barWidth = AttrMapValue(isNumber, desc='Width of bars.'), # maybe should be named just width?
barHeight = AttrMapValue(isNumber, desc='Height of bars.'), # maybe should be named just height?
barBorder = AttrMapValue(isNumber, desc='Width of QR border.'), # maybe should be named qrBorder?
barLevel = AttrMapValue(isLevel, desc='QR Code level.'), # maybe should be named qrLevel
qrVersion = AttrMapValue(isNumberOrNone, desc='QR Code version. None for auto'),
# Below are ignored, they make no sense
barStrokeWidth = AttrMapValue(isNumber, desc='Width of bar borders.'),
barStrokeColor = AttrMapValue(isColor, desc='Color of bar borders.'),
)
x = 0
y = 0
barFillColor = colors.black
barStrokeColor = None
barStrokeWidth = 0
barHeight = 32*mm
barWidth = 32*mm
barBorder = 4
barLevel = 'L'
qrVersion = None
value = None
def __init__(self, value='Hello World', **kw):
self.value = isUnicodeOrQRList.normalize(value)
for k, v in kw.items():
setattr(self, k, v)
ec_level = getattr(qrencoder.QRErrorCorrectLevel, self.barLevel)
self.__dict__['qr'] = qrencoder.QRCode(self.qrVersion, ec_level)
if isUnicode(self.value):
self.addData(self.value)
elif self.value:
for v in self.value:
self.addData(v)
def addData(self, value):
self.qr.addData(value)
def draw(self):
self.qr.make()
g = Group()
color = self.barFillColor
border = self.barBorder
width = self.barWidth
height = self.barHeight
x = self.x
y = self.y
g.add(SRect(x, y, width, height, fillColor=None))
moduleCount = self.qr.getModuleCount()
minwh = float(min(width, height))
boxsize = minwh / (moduleCount + border * 2.0)
offsetX = (width - minwh) / 2.0
offsetY = (minwh - height) / 2.0
for r, row in enumerate(self.qr.modules):
row = map(bool, row)
c = 0
for t, tt in itertools.groupby(row):
isDark = t
count = len(list(tt))
if isDark:
x = (c + border) * boxsize
y = (r + border + 1) * boxsize
s = SRect(offsetX + x, offsetY + height - y, count * boxsize, boxsize)
g.add(s)
c += count
return g
# Flowable version
class QrCode(Flowable):
height = 32*mm
width = 32*mm
qrBorder = 4
qrLevel = 'L'
qrVersion = None
value = None
def __init__(self, value=None, **kw):
self.value = isUnicodeOrQRList.normalize(value)
for k, v in kw.items():
setattr(self, k, v)
ec_level = getattr(qrencoder.QRErrorCorrectLevel, self.qrLevel)
self.qr = qrencoder.QRCode(self.qrVersion, ec_level)
if isUnicode(self.value):
self.addData(self.value)
elif self.value:
for v in self.value:
self.addData(v)
def addData(self, value):
self.qr.addData(value)
def draw(self):
self.qr.make()
moduleCount = self.qr.getModuleCount()
border = self.qrBorder
xsize = self.width / (moduleCount + border * 2.0)
ysize = self.height / (moduleCount + border * 2.0)
for r, row in enumerate(self.qr.modules):
row = map(bool, row)
c = 0
for t, tt in itertools.groupby(row):
isDark = t
count = len(list(tt))
if isDark:
x = (c + border) * xsize
y = self.height - (r + border + 1) * ysize
self.rect(x, y, count * xsize, ysize * 1.05)
c += count
def rect(self, x, y, w, h):
self.canv.rect(x, y, w, h, stroke=0, fill=1)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,200 @@
#!/usr/pkg/bin/python
import os, sys, time
from reportlab.graphics.barcode.common import *
from reportlab.graphics.barcode.code39 import *
from reportlab.graphics.barcode.code93 import *
from reportlab.graphics.barcode.code128 import *
from reportlab.graphics.barcode.usps import *
from reportlab.graphics.barcode.usps4s import USPS_4State
from reportlab.platypus import Spacer, SimpleDocTemplate, Table, TableStyle, Preformatted, PageBreak
from reportlab.lib.units import inch, cm
from reportlab.lib import colors
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus.paragraph import Paragraph
from reportlab.platypus.frames import Frame
from reportlab.platypus.flowables import XBox, KeepTogether
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.barcode import getCodes, getCodeNames, createBarcodeDrawing, createBarcodeImageInMemory
def run():
styles = getSampleStyleSheet()
styleN = styles['Normal']
styleH = styles['Heading1']
story = []
#for codeNames in code
story.append(Paragraph('I2of5', styleN))
story.append(I2of5(1234, barWidth = inch*0.02, checksum=0))
story.append(Paragraph('MSI', styleN))
story.append(MSI(1234))
story.append(Paragraph('Codabar', styleN))
story.append(Codabar("A012345B", barWidth = inch*0.02))
story.append(Paragraph('Code 11', styleN))
story.append(Code11("01234545634563"))
story.append(Paragraph('Code 39', styleN))
story.append(Standard39("A012345B%R"))
story.append(Paragraph('Extended Code 39', styleN))
story.append(Extended39("A012345B}"))
story.append(Paragraph('Code93', styleN))
story.append(Standard93("CODE 93"))
story.append(Paragraph('Extended Code93', styleN))
story.append(Extended93("L@@K! Code 93 :-)")) #, barWidth=0.005 * inch))
story.append(Paragraph('Code 128', styleN))
c=Code128("AB-12345678") #, barWidth=0.005 * inch)
#print 'WIDTH =', (c.width / inch), 'barWidth =', (c.barWidth / inch)
#print 'LQ =', (c.lquiet / inch), 'RQ =', (c.rquiet / inch)
story.append(c)
story.append(Paragraph('USPS FIM', styleN))
story.append(FIM("A"))
story.append(Paragraph('USPS POSTNET', styleN))
story.append(POSTNET('78247-1043'))
story.append(Paragraph('USPS 4 State', styleN))
story.append(USPS_4State('01234567094987654321','01234567891'))
from reportlab.graphics.barcode import createBarcodeDrawing
story.append(Paragraph('EAN13', styleN))
bcd = createBarcodeDrawing('EAN13', value='123456789012')
story.append(bcd)
story.append(Paragraph('EAN8', styleN))
bcd = createBarcodeDrawing('EAN8', value='1234567')
story.append(bcd)
story.append(Paragraph('UPCA', styleN))
bcd = createBarcodeDrawing('UPCA', value='03600029145')
story.append(bcd)
story.append(Paragraph('USPS_4State', styleN))
bcd = createBarcodeDrawing('USPS_4State', value='01234567094987654321',routing='01234567891')
story.append(bcd)
story.append(Paragraph('Label Size', styleN))
story.append(XBox((2.0 + 5.0/8.0)*inch, 1 * inch, '1x2-5/8"'))
story.append(Paragraph('Label Size', styleN))
story.append(XBox((1.75)*inch, .5 * inch, '1/2x1-3/4"'))
c = Canvas('out.pdf')
f = Frame(inch, inch, 6*inch, 9*inch, showBoundary=1)
f.addFromList(story, c)
c.save()
print('saved out.pdf')
def fullTest(fileName="test_full.pdf"):
"""Creates large-ish test document with a variety of parameters"""
story = []
styles = getSampleStyleSheet()
styleN = styles['Normal']
styleH = styles['Heading1']
styleH2 = styles['Heading2']
story = []
story.append(Paragraph('ReportLab Barcode Test Suite - full output', styleH))
story.append(Paragraph('Generated on %s' % time.ctime(time.time()), styleN))
story.append(Paragraph('', styleN))
story.append(Paragraph('Repository information for this build:', styleN))
#see if we can figure out where it was built, if we're running in source
if os.path.split(os.getcwd())[-1] == 'barcode' and os.path.isdir('.svn'):
#runnning in a filesystem svn copy
infoLines = os.popen('svn info').read()
story.append(Preformatted(infoLines, styles["Code"]))
story.append(Paragraph('About this document', styleH2))
story.append(Paragraph('History and Status', styleH2))
story.append(Paragraph("""
This is the test suite and docoumentation for the ReportLab open source barcode API,
being re-released as part of the forthcoming ReportLab 2.0 release.
""", styleN))
story.append(Paragraph("""
Several years ago Ty Sarna contributed a barcode module to the ReportLab community.
Several of the codes were used by him in hiw work and to the best of our knowledge
this was correct. These were written as flowable objects and were available in PDFs,
but not in our graphics framework. However, we had no knowledge of barcodes ourselves
and did not advertise or extend the package.
""", styleN))
story.append(Paragraph("""
We "wrapped" the barcodes to be usable within our graphics framework; they are now available
as Drawing objects which can be rendered to EPS files or bitmaps. For the last 2 years this
has been available in our Diagra and Report Markup Language products. However, we did not
charge separately and use was on an "as is" basis.
""", styleN))
story.append(Paragraph("""
A major licensee of our technology has kindly agreed to part-fund proper productisation
of this code on an open source basis in Q1 2006. This has involved addition of EAN codes
as well as a proper testing program. Henceforth we intend to publicise the code more widely,
gather feedback, accept contributions of code and treat it as "supported".
""", styleN))
story.append(Paragraph("""
This involved making available both downloads and testing resources. This PDF document
is the output of the current test suite. It contains codes you can scan (if you use a nice sharp
laser printer!), and will be extended over coming weeks to include usage examples and notes on
each barcode and how widely tested they are. This is being done through documentation strings in
the barcode objects themselves so should always be up to date.
""", styleN))
story.append(Paragraph('Usage examples', styleH2))
story.append(Paragraph("""
To be completed
""", styleN))
story.append(Paragraph('The codes', styleH2))
story.append(Paragraph("""
Below we show a scannable code from each barcode, with and without human-readable text.
These are magnified about 2x from the natural size done by the original author to aid
inspection. This will be expanded to include several test cases per code, and to add
explanations of checksums. Be aware that (a) if you enter numeric codes which are too
short they may be prefixed for you (e.g. "123" for an 8-digit code becomes "00000123"),
and that the scanned results and readable text will generally include extra checksums
at the end.
""", styleN))
codeNames = getCodeNames()
from reportlab.lib.utils import flatten
width = [float(x[8:]) for x in sys.argv if x.startswith('--width=')]
height = [float(x[9:]) for x in sys.argv if x.startswith('--height=')]
isoScale = [int(x[11:]) for x in sys.argv if x.startswith('--isoscale=')]
options = {}
if width: options['width'] = width[0]
if height: options['height'] = height[0]
if isoScale: options['isoScale'] = isoScale[0]
scales = [x[8:].split(',') for x in sys.argv if x.startswith('--scale=')]
scales = list(map(float,scales and flatten(scales) or [1]))
scales = list(map(float,scales and flatten(scales) or [1]))
for scale in scales:
story.append(PageBreak())
story.append(Paragraph('Scale = %.1f'%scale, styleH2))
story.append(Spacer(36, 12))
for codeName in codeNames:
s = [Paragraph('Code: ' + codeName, styleH2)]
for hr in (0,1):
s.append(Spacer(36, 12))
dr = createBarcodeDrawing(codeName, humanReadable=hr,**options)
dr.renderScale = scale
s.append(dr)
s.append(Spacer(36, 12))
s.append(Paragraph('Barcode should say: ' + dr._bc.value, styleN))
story.append(KeepTogether(s))
SimpleDocTemplate(fileName).build(story)
print('created', fileName)
if __name__=='__main__':
run()
fullTest()
def createSample(name,memory):
f = open(name,'wb')
f.write(memory)
f.close()
createSample('test_cbcim.png',createBarcodeImageInMemory('EAN13', value='123456789012'))
createSample('test_cbcim.gif',createBarcodeImageInMemory('EAN8', value='1234567', format='gif'))
createSample('test_cbcim.pdf',createBarcodeImageInMemory('UPCA', value='03600029145',format='pdf'))
createSample('test_cbcim.tiff',createBarcodeImageInMemory('USPS_4State', value='01234567094987654321',routing='01234567891',format='tiff'))

View File

@@ -0,0 +1,232 @@
#
# Copyright (c) 1996-2000 Tyler C. Sarna <tsarna@sarna.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
# must display the following acknowledgement:
# This product includes software developed by Tyler C. Sarna.
# 4. Neither the name of the author nor the names of contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
from reportlab.lib.units import inch
from reportlab.graphics.barcode.common import Barcode
from string import digits as string_digits, whitespace as string_whitespace
from reportlab.lib.utils import asNative
_fim_patterns = {
'A' : "|| | ||",
'B' : "| || || |",
'C' : "|| | | ||",
'D' : "||| | |||",
# XXX There is an E.
# The below has been seen, but dunno if it is E or not:
# 'E' : '|||| ||||'
}
_postnet_patterns = {
'1' : "...||", '2' : "..|.|", '3' : "..||.", '4' : ".|..|",
'5' : ".|.|.", '6' : ".||..", '7' : "|...|", '8' : "|..|.",
'9' : "|.|..", '0' : "||...", 'S' : "|",
}
class FIM(Barcode):
"""
FIM (Facing ID Marks) encode only one letter.
There are currently four defined:
A Courtesy reply mail with pre-printed POSTNET
B Business reply mail without pre-printed POSTNET
C Business reply mail with pre-printed POSTNET
D OCR Readable mail without pre-printed POSTNET
Options that may be passed to constructor:
value (single character string from the set A - D. required.):
The value to encode.
quiet (bool, default 0):
Whether to include quiet zones in the symbol.
The following may also be passed, but doing so will generate nonstandard
symbols which should not be used. This is mainly documented here to
show the defaults:
barHeight (float, default 5/8 inch):
Height of the code. This might legitimately be overriden to make
a taller symbol that will 'bleed' off the edge of the paper,
leaving 5/8 inch remaining.
lquiet (float, default 1/4 inch):
Quiet zone size to left of code, if quiet is true.
Default is the greater of .25 inch, or .15 times the symbol's
length.
rquiet (float, default 15/32 inch):
Quiet zone size to right left of code, if quiet is true.
Sources of information on FIM:
USPS Publication 25, A Guide to Business Mail Preparation
http://new.usps.com/cpim/ftp/pubs/pub25.pdf
"""
barWidth = inch * (1.0/32.0)
spaceWidth = inch * (1.0/16.0)
barHeight = inch * (5.0/8.0)
rquiet = inch * (0.25)
lquiet = inch * (15.0/32.0)
quiet = 0
def __init__(self, value='', **args):
value = str(value) if isinstance(value,int) else asNative(value)
for k, v in args.items():
setattr(self, k, v)
Barcode.__init__(self, value)
def validate(self):
self.valid = 1
self.validated = ''
for c in self.value:
if c in string_whitespace:
continue
elif c in "abcdABCD":
self.validated = self.validated + c.upper()
else:
self.valid = 0
if len(self.validated) != 1:
raise ValueError("Input must be exactly one character")
return self.validated
def decompose(self):
self.decomposed = ''
for c in self.encoded:
self.decomposed = self.decomposed + _fim_patterns[c]
return self.decomposed
def computeSize(self):
self._width = (len(self.decomposed) - 1) * self.spaceWidth + self.barWidth
if self.quiet:
self._width += self.lquiet + self.rquiet
self._height = self.barHeight
def draw(self):
self._calculate()
left = self.quiet and self.lquiet or 0
for c in self.decomposed:
if c == '|':
self.rect(left, 0.0, self.barWidth, self.barHeight)
left += self.spaceWidth
self.drawHumanReadable()
def _humanText(self):
return self.value
class POSTNET(Barcode):
"""
POSTNET is used in the US to encode "zip codes" (postal codes) on
mail. It can encode 5, 9, or 11 digit codes. I've read that it's
pointless to do 5 digits, since USPS will just have to re-print
them with 9 or 11 digits.
Sources of information on POSTNET:
USPS Publication 25, A Guide to Business Mail Preparation
http://new.usps.com/cpim/ftp/pubs/pub25.pdf
"""
quiet = 0
shortHeight = inch * 0.050
barHeight = inch * 0.125
barWidth = inch * 0.018
spaceWidth = inch * 0.0275
def __init__(self, value='', **args):
value = str(value) if isinstance(value,int) else asNative(value)
for k, v in args.items():
setattr(self, k, v)
Barcode.__init__(self, value)
def validate(self):
self.validated = ''
self.valid = 1
count = 0
for c in self.value:
if c in (string_whitespace + '-'):
pass
elif c in string_digits:
count = count + 1
if count == 6:
self.validated = self.validated + '-'
self.validated = self.validated + c
else:
self.valid = 0
if len(self.validated) not in [5, 10, 12]:
self.valid = 0
return self.validated
def encode(self):
self.encoded = "S"
check = 0
for c in self.validated:
if c in string_digits:
self.encoded = self.encoded + c
check = check + int(c)
elif c == '-':
pass
else:
raise ValueError("Invalid character in input")
check = (10 - check) % 10
self.encoded = self.encoded + repr(check) + 'S'
return self.encoded
def decompose(self):
self.decomposed = ''
for c in self.encoded:
self.decomposed = self.decomposed + _postnet_patterns[c]
return self.decomposed
def computeSize(self):
self._width = len(self.decomposed) * self.barWidth + (len(self.decomposed) - 1) * self.spaceWidth
self._height = self.barHeight
def draw(self):
self._calculate()
sdown = self.barHeight - self.shortHeight
left = 0
for c in self.decomposed:
if c == '.':
h = self.shortHeight
else:
h = self.barHeight
self.rect(left, 0.0, self.barWidth, h)
left = left + self.barWidth + self.spaceWidth
self.drawHumanReadable()
def _humanText(self):
return self.encoded[1:-1]

View File

@@ -0,0 +1,386 @@
#copyright ReportLab Inc. 2000-2012
#see license.txt for license details
__version__=''' $Id$ '''
__all__ = ('USPS_4State',)
from reportlab.lib.colors import black
from reportlab.graphics.barcode.common import Barcode
from reportlab.lib.utils import asNative
class USPS_4State(Barcode):
''' USPS 4-State OneView (TM) barcode. All info from USPS-B-3200A
'''
_widthSize = 1
_heightSize = 1
_fontSize = 11
_humanReadable = 0
tops = dict(
F = (0.067,0.115),
T = (0.021,0.040),
A = (0.067,0.115),
D = (0.021,0.040),
)
bottoms = dict(
F = (-0.067,-0.115),
D = (-0.067,-0.115),
T = (-0.021,-0.040),
A = (-0.021,-0.040),
)
dimensions = dict(
width = (0.015, 0.025),
pitch = (0.0416,0.050),
hcz = (0.125,0.125),
vcz = (0.040,0.040),
)
def __init__(self,value='01234567094987654321',routing='',**kwd):
self._init()
value = str(value) if isinstance(value,int) else asNative(value)
self._tracking = value
self._routing = routing
self._setKeywords(**kwd)
def _init(self):
self._bvalue = None
self._codewords = None
self._characters = None
self._barcodes = None
def scale(kind,D,s):
V = D[kind]
return 72*(V[0]*(1-s)+s*V[1])
scale = staticmethod(scale)
def tracking(self,tracking):
self._init()
self._tracking = tracking
tracking = property(lambda self: self._tracking,tracking)
def routing(self,routing):
self._init()
self._routing = routing
routing = property(lambda self: self._routing,routing)
def widthSize(self,value):
self._sized = None
self._widthSize = value
widthSize = property(lambda self: self._widthSize,widthSize)
def heightSize(self,value):
self._sized = None
self._heightSize = value
heightSize = property(lambda self: self._heightSize,heightSize)
def fontSize(self,value):
self._sized = None
self._fontSize = value
fontSize = property(lambda self: self._fontSize,fontSize)
def humanReadable(self,value):
self._sized = None
self._humanReadable = value
humanReadable = property(lambda self: self._humanReadable,humanReadable)
def binary(self):
'''convert the 4 state string values to binary
>>> print hex(USPS_4State('01234567094987654321','').binary)
0x1122103B5C2004B1L
>>> print hex(USPS_4State('01234567094987654321','01234').binary)
0xD138A87BAB5CF3804B1L
>>> print hex(USPS_4State('01234567094987654321','012345678').binary)
0x202BDC097711204D21804B1L
>>> print hex(USPS_4State('01234567094987654321','01234567891').binary)
0x16907B2A24ABC16A2E5C004B1L
'''
value = self._bvalue
if not value:
routing = self.routing
n = len(routing)
try:
if n==0:
value = 0
elif n==5:
value = int(routing)+1
elif n==9:
value = int(routing)+100001
elif n==11:
value = int(routing)+1000100001
else:
raise ValueError
except:
raise ValueError('Problem converting %s, routing code must be 0, 5, 9 or 11 digits' % routing)
tracking = self.tracking
svalue = tracking[0:2]
try:
value *= 10
value += int(svalue[0])
value *= 5
value += int(svalue[1])
except:
raise ValueError('Problem converting %s, barcode identifier must be 2 digits' % svalue)
i = 2
for name,nd in (('special services',3), ('customer identifier',6), ('sequence number',9)):
j = i
i += nd
svalue = tracking[j:i]
try:
if len(svalue)!=nd: raise ValueError
for j in range(nd):
value *= 10
value += int(svalue[j])
except:
raise ValueError('Problem converting %s, %s must be %d digits' % (svalue,name,nd))
self._bvalue = value
return value
binary = property(binary)
def codewords(self):
'''convert binary value into codewords
>>> print USPS_4State('01234567094987654321','01234567891').codewords)
(673, 787, 607, 1022, 861, 19, 816, 1294, 35, 602)
'''
if not self._codewords:
value = self.binary
A, J = divmod(value,636)
A, I = divmod(A,1365)
A, H = divmod(A,1365)
A, G = divmod(A,1365)
A, F = divmod(A,1365)
A, E = divmod(A,1365)
A, D = divmod(A,1365)
A, C = divmod(A,1365)
A, B = divmod(A,1365)
assert 0<=A<=658, 'improper value %s passed to _2codewords A-->%s' % (hex(int(value)),A)
self._fcs = _crc11(value)
if self._fcs&1024: A += 659
J *= 2
self._codewords = tuple(map(int,(A,B,C,D,E,F,G,H,I,J)))
return self._codewords
codewords = property(codewords)
def table1(self):
self.__class__.table1 = _initNof13Table(5,1287)
return self.__class__.table1
table1 = property(table1)
def table2(self):
self.__class__.table2 = _initNof13Table(2,78)
return self.__class__.table2
table2 = property(table2)
def characters(self):
''' convert own codewords to characters
>>> print ' '.join(hex(c)[2:] for c in USPS_4State('01234567094987654321','01234567891').characters)
dcb 85c 8e4 b06 6dd 1740 17c6 1200 123f 1b2b
'''
if not self._characters:
codewords = self.codewords
fcs = self._fcs
C = []
aC = C.append
table1 = self.table1
table2 = self.table2
for i in range(10):
cw = codewords[i]
if cw<=1286:
c = table1[cw]
else:
c = table2[cw-1287]
if (fcs>>i)&1:
c = ~c & 0x1fff
aC(c)
self._characters = tuple(C)
return self._characters
characters = property(characters)
def barcodes(self):
'''Get 4 state bar codes for current routing and tracking
>>> print USPS_4State('01234567094987654321','01234567891').barcodes
AADTFFDFTDADTAADAATFDTDDAAADDTDTTDAFADADDDTFFFDDTTTADFAAADFTDAADA
'''
if not self._barcodes:
C = self.characters
B = []
aB = B.append
bits2bars = self._bits2bars
for dc,db,ac,ab in self.table4:
aB(bits2bars[((C[dc]>>db)&1)+2*((C[ac]>>ab)&1)])
self._barcodes = ''.join(B)
return self._barcodes
barcodes = property(barcodes)
table4 = ((7, 2, 4, 3), (1, 10, 0, 0), (9, 12, 2, 8), (5, 5, 6, 11),
(8, 9, 3, 1), (0, 1, 5, 12), (2, 5, 1, 8), (4, 4, 9, 11),
(6, 3, 8, 10), (3, 9, 7, 6), (5, 11, 1, 4), (8, 5, 2, 12),
(9, 10, 0, 2), (7, 1, 6, 7), (3, 6, 4, 9), (0, 3, 8, 6),
(6, 4, 2, 7), (1, 1, 9, 9), (7, 10, 5, 2), (4, 0, 3, 8),
(6, 2, 0, 4), (8, 11, 1, 0), (9, 8, 3, 12), (2, 6, 7, 7),
(5, 1, 4, 10), (1, 12, 6, 9), (7, 3, 8, 0), (5, 8, 9, 7),
(4, 6, 2, 10), (3, 4, 0, 5), (8, 4, 5, 7), (7, 11, 1, 9),
(6, 0, 9, 6), (0, 6, 4, 8), (2, 1, 3, 2), (5, 9, 8, 12),
(4, 11, 6, 1), (9, 5, 7, 4), (3, 3, 1, 2), (0, 7, 2, 0),
(1, 3, 4, 1), (6, 10, 3, 5), (8, 7, 9, 4), (2, 11, 5, 6),
(0, 8, 7, 12), (4, 2, 8, 1), (5, 10, 3, 0), (9, 3, 0, 9),
(6, 5, 2, 4), (7, 8, 1, 7), (5, 0, 4, 5), (2, 3, 0, 10),
(6, 12, 9, 2), (3, 11, 1, 6), (8, 8, 7, 9), (5, 4, 0, 11),
(1, 5, 2, 2), (9, 1, 4, 12), (8, 3, 6, 6), (7, 0, 3, 7),
(4, 7, 7, 5), (0, 12, 1, 11), (2, 9, 9, 0), (6, 8, 5, 3),
(3, 10, 8, 2))
_bits2bars = 'T','D','A','F'
horizontalClearZone = property(lambda self: self.scale('hcz',self.dimensions,self.widthScale))
verticalClearZone = property(lambda self: self.scale('vcz',self.dimensions,self.heightScale))
pitch = property(lambda self: self.scale('pitch',self.dimensions,self.widthScale))
barWidth = property(lambda self: self.scale('width',self.dimensions,self.widthScale))
barHeight = property(lambda self: self.scale('F',self.tops,self.heightScale) - self.scale('F',self.bottoms,self.heightScale))
widthScale = property(lambda self: min(1,max(0,self.widthSize)))
heightScale = property(lambda self: min(1,max(0,self.heightSize)))
def width(self):
self.computeSize()
return self._width
width = property(width)
def height(self):
self.computeSize()
return self._height
height = property(height)
def computeSize(self):
if not getattr(self,'_sized',None):
ws = self.widthScale
hs = self.heightScale
barHeight = self.barHeight
barWidth = self.barWidth
pitch = self.pitch
hcz = self.horizontalClearZone
vcz = self.verticalClearZone
self._width = 2*hcz + barWidth + 64*pitch
self._height = 2*vcz+barHeight
if self.humanReadable:
self._height += self.fontSize*1.2+vcz
self._sized = True
def wrap(self,aW,aH):
self.computeSize()
return self.width, self.height
def _getBarVInfo(self,y0=0):
vInfo = {}
hs = self.heightScale
for b in ('T','D','A','F'):
y = self.scale(b,self.bottoms,hs)+y0
vInfo[b] = y,self.scale(b,self.tops,hs)+y0 - y
return vInfo
def draw(self):
self.computeSize()
hcz = self.horizontalClearZone
vcz = self.verticalClearZone
bw = self.barWidth
x = hcz
y0 = vcz+self.barHeight*0.5
dw = self.pitch
vInfo = self._getBarVInfo(y0)
for b in self.barcodes:
yb, hb = vInfo[b]
self.rect(x,yb,bw,hb)
x += dw
self.drawHumanReadable()
def value(self):
tracking = self.tracking
routing = self.routing
routing = routing and (routing,) or ()
return ' '.join((tracking[0:2],tracking[2:5],tracking[5:11],tracking[11:])+routing)
value = property(value,lambda self,value: self.__dict__.__setitem__('tracking',value))
def drawHumanReadable(self):
if self.humanReadable:
hcz = self.horizontalClearZone
vcz = self.verticalClearZone
fontName = self.fontName
fontSize = self.fontSize
y = self.barHeight+2*vcz+0.2*fontSize
self.annotate(hcz,y,self.value,fontName,fontSize)
def annotate(self,x,y,text,fontName,fontSize,anchor='middle'):
Barcode.annotate(self,x,y,text,fontName,fontSize,anchor='start')
def _crc11(value):
'''
>>> print ' '.join(hex(_crc11(USPS_4State('01234567094987654321',x).binary)) for x in ('','01234','012345678','01234567891'))
0x51 0x65 0x606 0x751
'''
bytes = hex(int(value))[2:-1]
bytes = '0'*(26-len(bytes))+bytes
gp = 0x0F35
fcs = 0x07FF
data = int(bytes[:2],16)<<5
for b in range(2,8):
if (fcs ^ data)&0x400:
fcs = (fcs<<1)^gp
else:
fcs = fcs<<1
fcs &= 0x7ff
data <<= 1
for x in range(2,2*13,2):
data = int(bytes[x:x+2],16)<<3
for b in range(8):
if (fcs ^ data)&0x400:
fcs = (fcs<<1)^gp
else:
fcs = fcs<<1
fcs &= 0x7ff
data <<= 1
return fcs
def _ru13(i):
'''reverse unsigned 13 bit number
>>> print _ru13(7936), _ru13(31), _ru13(47), _ru13(7808)
31 7936 7808 47
'''
r = 0
for x in range(13):
r <<= 1
r |= i & 1
i >>= 1
return r
def _initNof13Table(N,lenT):
'''create and return table of 13 bit values with N bits on
>>> T = _initNof13Table(5,1287)
>>> print ' '.join('T[%d]=%d' % (i, T[i]) for i in (0,1,2,3,4,1271,1272,1284,1285,1286))
T[0]=31 T[1]=7936 T[2]=47 T[3]=7808 T[4]=55 T[1271]=6275 T[1272]=6211 T[1284]=856 T[1285]=744 T[1286]=496
'''
T = lenT*[None]
l = 0
u = lenT-1
for c in range(8192):
bc = 0
for b in range(13):
bc += (c&(1<<b))!=0
if bc!=N: continue
r = _ru13(c)
if r<c: continue #we already looked at this pair
if r==c:
T[u] = c
u -= 1
else:
T[l] = c
l += 1
T[l] = r
l += 1
assert l==(u+1), 'u+1(%d)!=l(%d) for %d of 13 table' % (u+1,l,N)
return T
def _test():
import doctest
return doctest.testmod()
if __name__ == "__main__":
_test()

View File

@@ -0,0 +1,307 @@
#copyright ReportLab Europe Limited. 2000-2012
#see license.txt for license details
__version__=''' $Id$ '''
__all__= (
'BarcodeI2of5',
'BarcodeCode128',
'BarcodeStandard93',
'BarcodeExtended93',
'BarcodeStandard39',
'BarcodeExtended39',
'BarcodeMSI',
'BarcodeCodabar',
'BarcodeCode11',
'BarcodeFIM',
'BarcodePOSTNET',
'BarcodeUSPS_4State',
)
from reportlab.lib.validators import isInt, isNumber, isColor, isString, isColorOrNone, OneOf, isBoolean, EitherOr, isNumberOrNone
from reportlab.lib.attrmap import AttrMap, AttrMapValue
from reportlab.lib.colors import black
from reportlab.lib.utils import rl_exec
from reportlab.graphics.shapes import Line, Rect, Group, NotImplementedError, String
from reportlab.graphics.charts.areas import PlotArea
'''
#snippet
#first make your Drawing
from reportlab.graphics.shapes import Drawing
d= Drawing(100,50)
#create and set up the widget
from reportlab.graphics.barcode.widgets import BarcodeStandard93
bc = BarcodeStandard93()
bc.value = 'RGB-123456'
#add to the drawing and save
d.add(bc)
# d.save(formats=['gif','pict'],fnRoot='bc_sample')
'''
class _BarcodeWidget(PlotArea):
_attrMap = AttrMap(BASE=PlotArea,
barStrokeColor = AttrMapValue(isColorOrNone, desc='Color of bar borders.'),
barFillColor = AttrMapValue(isColorOrNone, desc='Color of bar interior areas.'),
barStrokeWidth = AttrMapValue(isNumber, desc='Width of bar borders.'),
value = AttrMapValue(EitherOr((isString,isNumber)), desc='Value.'),
textColor = AttrMapValue(isColorOrNone, desc='Color of human readable text.'),
valid = AttrMapValue(isBoolean),
validated = AttrMapValue(isString,desc="validated form of input"),
encoded = AttrMapValue(None,desc="encoded form of input"),
decomposed = AttrMapValue(isString,desc="decomposed form of input"),
canv = AttrMapValue(None,desc="temporarily used for internal methods"),
gap = AttrMapValue(isNumberOrNone, desc='Width of inter character gaps.'),
)
textColor = barFillColor = black
barStrokeColor = None
barStrokeWidth = 0
_BCC = None
def __init__(self,_value='',**kw):
PlotArea.__init__(self)
del self.__dict__['width']
del self.__dict__['height']
self.x = self.y = 0
kw.setdefault('value',_value)
self._BCC.__init__(self,**kw)
def rect(self,x,y,w,h,**kw):
self._Gadd(Rect(self.x+x,self.y+y,w,h,
strokeColor=self.barStrokeColor,strokeWidth=self.barStrokeWidth, fillColor=self.barFillColor))
def draw(self):
if not self._BCC: raise NotImplementedError("Abstract class %s cannot be drawn" % self.__class__.__name__)
self.canv = self
G = Group()
self._Gadd = G.add
self._Gadd(Rect(self.x,self.y,self.width,self.height,fillColor=None,strokeColor=None,strokeWidth=0.0001))
self._BCC.draw(self)
del self.canv, self._Gadd
return G
def annotate(self,x,y,text,fontName,fontSize,anchor='middle'):
self._Gadd(String(self.x+x,self.y+y,text,fontName=fontName,fontSize=fontSize,
textAnchor=anchor,fillColor=self.textColor))
def _BCW(doc,codeName,attrMap,mod,value,**kwds):
"""factory for Barcode Widgets"""
_pre_init = kwds.pop('_pre_init','')
_methods = kwds.pop('_methods','')
name = 'Barcode'+codeName
ns = vars().copy()
code = 'from %s import %s' % (mod,codeName)
rl_exec(code,ns)
ns['_BarcodeWidget'] = _BarcodeWidget
code = '''class %(name)s(_BarcodeWidget,%(codeName)s):
\t_BCC = %(codeName)s
\tcodeName = %(codeName)r
\tdef __init__(self,**kw):%(_pre_init)s
\t\t_BarcodeWidget.__init__(self,%(value)r,**kw)%(_methods)s''' % ns
rl_exec(code,ns)
Klass = ns[name]
if attrMap: Klass._attrMap = attrMap
if doc: Klass.__doc__ = doc
for k, v in kwds.items():
setattr(Klass,k,v)
return Klass
BarcodeI2of5 = _BCW(
"""Interleaved 2 of 5 is used in distribution and warehouse industries.
It encodes an even-numbered sequence of numeric digits. There is an optional
module 10 check digit; if including this, the total length must be odd so that
it becomes even after including the check digit. Otherwise the length must be
even. Since the check digit is optional, our library does not check it.
""",
"I2of5",
AttrMap(BASE=_BarcodeWidget,
barWidth = AttrMapValue(isNumber,'''(float, default .0075):
X-Dimension, or width of the smallest element
Minumum is .0075 inch (7.5 mils).'''),
ratio = AttrMapValue(isNumber,'''(float, default 2.2):
The ratio of wide elements to narrow elements.
Must be between 2.0 and 3.0 (or 2.2 and 3.0 if the
barWidth is greater than 20 mils (.02 inch))'''),
gap = AttrMapValue(isNumberOrNone,'''(float or None, default None):
width of intercharacter gap. None means "use barWidth".'''),
barHeight = AttrMapValue(isNumber,'''(float, see default below):
Height of the symbol. Default is the height of the two
bearer bars (if they exist) plus the greater of .25 inch
or .15 times the symbol's length.'''),
checksum = AttrMapValue(isBoolean,'''(bool, default 1):
Whether to compute and include the check digit'''),
bearers = AttrMapValue(isNumber,'''(float, in units of barWidth. default 3.0):
Height of bearer bars (horizontal bars along the top and
bottom of the barcode). Default is 3 x-dimensions.
Set to zero for no bearer bars. (Bearer bars help detect
misscans, so it is suggested to leave them on).'''),
quiet = AttrMapValue(isBoolean,'''(bool, default 1):
Whether to include quiet zones in the symbol.'''),
lquiet = AttrMapValue(isNumber,'''(float, see default below):
Quiet zone size to left of code, if quiet is true.
Default is the greater of .25 inch, or .15 times the symbol's
length.'''),
rquiet = AttrMapValue(isNumber,'''(float, defaults as above):
Quiet zone size to right left of code, if quiet is true.'''),
fontName = AttrMapValue(isString, desc='human readable font'),
fontSize = AttrMapValue(isNumber, desc='human readable font size'),
humanReadable = AttrMapValue(isBoolean, desc='if human readable'),
stop = AttrMapValue(isBoolean, desc='if we use start/stop symbols (default 1)'),
),
'reportlab.graphics.barcode.common',
1234,
_tests = [
'12',
'1234',
'123456',
'12345678',
'1234567890'
],
)
BarcodeCode128 = _BCW("""Code 128 encodes any number of characters in the ASCII character set.""",
"Code128",
AttrMap(BASE=BarcodeI2of5,UNWANTED=('bearers','checksum','ratio','checksum','stop')),
'reportlab.graphics.barcode.code128',
"AB-12345678",
_tests = ['ReportLab Rocks!', 'PFWZF'],
)
BarcodeStandard93=_BCW("""This is a compressed form of Code 39""",
"Standard93",
AttrMap(BASE=BarcodeCode128,
stop = AttrMapValue(isBoolean, desc='if we use start/stop symbols (default 1)'),
),
'reportlab.graphics.barcode.code93',
"CODE 93",
)
BarcodeExtended93=_BCW("""This is a compressed form of Code 39, allowing the full ASCII charset""",
"Extended93",
AttrMap(BASE=BarcodeCode128,
stop = AttrMapValue(isBoolean, desc='if we use start/stop symbols (default 1)'),
),
'reportlab.graphics.barcode.code93',
"L@@K! Code 93 ;-)",
)
BarcodeStandard39=_BCW("""Code39 is widely used in non-retail, especially US defence and health.
Allowed characters are 0-9, A-Z (caps only), space, and -.$/+%*.""",
"Standard39",
AttrMap(BASE=BarcodeI2of5),
'reportlab.graphics.barcode.code39',
"A012345B%R",
)
BarcodeExtended39=_BCW("""Extended 39 encodes the full ASCII character set by encoding
characters as pairs of Code 39 characters; $, /, % and + are used as
shift characters.""",
"Extended39",
AttrMap(BASE=BarcodeI2of5),
'reportlab.graphics.barcode.code39',
"A012345B}",
)
BarcodeMSI=_BCW("""MSI is used for inventory control in retail applications.
There are several methods for calculating check digits so we
do not implement one.
""",
"MSI",
AttrMap(BASE=BarcodeI2of5),
'reportlab.graphics.barcode.common',
1234,
)
BarcodeCodabar=_BCW("""Used in blood banks, photo labs and FedEx labels.
Encodes 0-9, -$:/.+, and four start/stop characters A-D.""",
"Codabar",
AttrMap(BASE=BarcodeI2of5),
'reportlab.graphics.barcode.common',
"A012345B",
)
BarcodeCode11=_BCW("""Used mostly for labelling telecommunications equipment.
It encodes numeric digits.""",
'Code11',
AttrMap(BASE=BarcodeI2of5,
checksum = AttrMapValue(isInt,'''(integer, default 2):
Whether to compute and include the check digit(s).
(0 none, 1 1-digit, 2 2-digit, -1 auto, default -1):
How many checksum digits to include. -1 ("auto") means
1 if the number of digits is 10 or less, else 2.'''),
),
'reportlab.graphics.barcode.common',
"01234545634563",
)
BarcodeFIM=_BCW("""
FIM was developed as part of the POSTNET barcoding system.
FIM (Face Identification Marking) is used by the cancelling machines
to sort mail according to whether or not they have bar code
and their postage requirements. There are four types of FIM
called FIM A, FIM B, FIM C, and FIM D.
The four FIM types have the following meanings:
FIM A- Postage required pre-barcoded
FIM B - Postage pre-paid, no bar code exists
FIM C- Postage prepaid prebarcoded
FIM D- Postage required, no bar code exists""",
"FIM",
AttrMap(BASE=_BarcodeWidget,
barWidth = AttrMapValue(isNumber,'''(float, default 1/32in): the bar width.'''),
spaceWidth = AttrMapValue(isNumber,'''(float or None, default 1/16in):
width of intercharacter gap. None means "use barWidth".'''),
barHeight = AttrMapValue(isNumber,'''(float, default 5/8in): The bar height.'''),
quiet = AttrMapValue(isBoolean,'''(bool, default 0):
Whether to include quiet zones in the symbol.'''),
lquiet = AttrMapValue(isNumber,'''(float, default: 15/32in):
Quiet zone size to left of code, if quiet is true.'''),
rquiet = AttrMapValue(isNumber,'''(float, default 1/4in):
Quiet zone size to right left of code, if quiet is true.'''),
fontName = AttrMapValue(isString, desc='human readable font'),
fontSize = AttrMapValue(isNumber, desc='human readable font size'),
humanReadable = AttrMapValue(isBoolean, desc='if human readable'),
),
'reportlab.graphics.barcode.usps',
"A",
)
BarcodePOSTNET=_BCW('',
"POSTNET",
AttrMap(BASE=_BarcodeWidget,
barWidth = AttrMapValue(isNumber,'''(float, default 0.018*in): the bar width.'''),
spaceWidth = AttrMapValue(isNumber,'''(float or None, default 0.0275in): width of intercharacter gap.'''),
shortHeight = AttrMapValue(isNumber,'''(float, default 0.05in): The short bar height.'''),
barHeight = AttrMapValue(isNumber,'''(float, default 0.125in): The full bar height.'''),
fontName = AttrMapValue(isString, desc='human readable font'),
fontSize = AttrMapValue(isNumber, desc='human readable font size'),
humanReadable = AttrMapValue(isBoolean, desc='if human readable'),
),
'reportlab.graphics.barcode.usps',
"78247-1043",
)
BarcodeUSPS_4State=_BCW('',
"USPS_4State",
AttrMap(BASE=_BarcodeWidget,
widthSize = AttrMapValue(isNumber,'''(float, default 1): the bar width size adjustment between 0 and 1.'''),
heightSize = AttrMapValue(isNumber,'''(float, default 1): the bar height size adjustment between 0 and 1.'''),
fontName = AttrMapValue(isString, desc='human readable font'),
fontSize = AttrMapValue(isNumber, desc='human readable font size'),
tracking = AttrMapValue(isString, desc='tracking data'),
routing = AttrMapValue(isString, desc='routing data'),
humanReadable = AttrMapValue(isBoolean, desc='if human readable'),
),
'reportlab.graphics.barcode.usps4s',
'01234567094987654321',
_pre_init="\n\t\tkw.setdefault('routing','01234567891')\n",
_methods = "\n\tdef annotate(self,x,y,text,fontName,fontSize,anchor='middle'):\n\t\t_BarcodeWidget.annotate(self,x,y,text,fontName,fontSize,anchor='start')\n"
)
if __name__=='__main__':
raise ValueError('widgets.py has no script function')

View File

@@ -0,0 +1,5 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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$ '''
__doc__='''Business charts'''

View File

@@ -0,0 +1,94 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/areas.py
__version__=''' $Id$ '''
__doc__='''This module defines a Area mixin classes'''
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
from reportlab.lib.colors import grey
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 e.g. Rect(0,0,width,height).'),
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

View File

@@ -0,0 +1,165 @@
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")

View File

@@ -0,0 +1,400 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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
__version__=''' $Id$ '''
__doc__="""Doughnut chart
Produces a circular chart like the doughnut charts produced by Excel.
Can handle multiple series (which produce concentric 'rings' in the chart).
"""
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,\
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.piecharts import AbstractPieChart, WedgeProperties, _addWedgeLabel, fixLabelOverlaps
from reportlab.graphics.charts.textlabels import Label
from reportlab.graphics.widgets.markers import Marker
from functools import reduce
class SectorProperties(WedgeProperties):
"""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(BASE=WedgeProperties,
)
class Doughnut(AbstractPieChart):
_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"),
simpleLabels = AttrMapValue(isBoolean, desc="If true(default) use String not super duper WedgeLabel"),
# advanced usage
checkLabelOverlap = AttrMapValue(isBoolean, desc="If true check and attempt to fix\n standard label overlaps(default off)",advancedUsage=1),
sideLabels = AttrMapValue(isBoolean, desc="If true attempt to make chart with labels along side and pointers", advancedUsage=1)
)
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.simpleLabels = 1
self.checkLabelOverlap = 0
self.sideLabels = 0
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
self.slices[4].fillColor = colors.pink
self.slices[5].fillColor = colors.magenta
self.slices[6].fillColor = colors.yellow
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 list(map(lambda x,f=360./sum: f*x, data)) or len(data)*[0]
def makeSectors(self):
# normalize slice data
if isinstance(self.data,(list,tuple)) and isinstance(self.data[0],(list,tuple)):
#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))
self._seriesCount = max(n)
else:
normData = self.normalizeData(self.data)
n = len(normData)
self._seriesCount = n
#labels
checkLabelOverlap = self.checkLabelOverlap
L = []
L_add = L.append
if self.labels is None:
labels = []
if not isinstance(n,(list,tuple)):
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 labels if
#we silently create all for the extreme case of no labels.
if not isinstance(n,(list,tuple)):
i = n-len(labels)
if i>0:
labels = list(labels) + [''] * i
else:
tlab = 0
for m in n:
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()
startAngle = self.startAngle #% 360
styleCount = len(self.slices)
if isinstance(self.data[0],(list,tuple)):
#multi-series doughnut
ndata = len(self.data)
yir = (yradius/2.5)/ndata
xir = (xradius/2.5)/ndata
ydr = (yradius-yir)/ndata
xdr = (xradius-xir)/ndata
for sn,series in enumerate(normData):
for i,angle in enumerate(series):
endAngle = (startAngle + (angle * whichWay)) #% 360
if abs(startAngle-endAngle)<1e-5:
startAngle = endAngle
continue
if startAngle < endAngle:
a1 = startAngle
a2 = endAngle
else:
a1 = endAngle
a2 = startAngle
startAngle = endAngle
#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)
yr1 = yir+sn*ydr
yr = yr1 + ydr
xr1 = xir+sn*xdr
xr = xr1 + xdr
if isinstance(n,(list,tuple)):
theSector = Wedge(cx, cy, xr, a1, a2, yradius=yr, radius1=xr1, yradius1=yr1)
else:
theSector = Wedge(cx, cy, xr, a1, a2, yradius=yr, radius1=xr1, yradius1=yr1, annular=True)
theSector.fillColor = sectorStyle.fillColor
theSector.strokeColor = sectorStyle.strokeColor
theSector.strokeWidth = sectorStyle.strokeWidth
theSector.strokeDashArray = sectorStyle.strokeDashArray
g.add(theSector)
if sn == 0:
text = self.getSeriesName(i,'')
if text:
averageAngle = (a1+a2)/2.0
aveAngleRadians = averageAngle*pi/180.0
labelRadius = sectorStyle.labelRadius
rx = xradius*labelRadius
ry = yradius*labelRadius
labelX = centerx + (0.5 * self.width * cos(aveAngleRadians) * labelRadius)
labelY = centery + (0.5 * self.height * sin(aveAngleRadians) * labelRadius)
l = _addWedgeLabel(self,text,averageAngle,labelX,labelY,sectorStyle)
if checkLabelOverlap:
l._origdata = { 'x': labelX, 'y':labelY, 'angle': averageAngle,
'rx': rx, 'ry':ry, 'cx':cx, 'cy':cy,
'bounds': l.getBounds(),
}
L_add(l)
else:
#single series doughnut
yir = yradius/2.5
xir = xradius/2.5
for i,angle in enumerate(normData):
endAngle = (startAngle + (angle * whichWay)) #% 360
if abs(startAngle-endAngle)<1e-5:
startAngle = endAngle
continue
if startAngle < endAngle:
a1 = startAngle
a2 = endAngle
else:
a1 = endAngle
a2 = startAngle
startAngle = endAngle
#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=xir, yradius1=yir)
elif n==1:
theSector = Wedge(cx, cy, xradius, a1, a2, yradius=yradius, radius1=xir, yradius1=yir, annular=True)
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)
rx = xradius*labelRadius
ry = yradius*labelRadius
l = _addWedgeLabel(self,labels[i],averageAngle,labelX,labelY,sectorStyle)
if checkLabelOverlap:
l._origdata = { 'x': labelX, 'y':labelY, 'angle': averageAngle,
'rx': rx, 'ry':ry, 'cx':cx, 'cy':cy,
'bounds': l.getBounds(),
}
L_add(l)
if checkLabelOverlap and L:
fixLabelOverlaps(L)
for l in L: g.add(l)
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
def sample4():
"Make a more complex demo with Label Overlap fixing"
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']
dn.checkLabelOverlap = True
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')

View File

@@ -0,0 +1,716 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/legends.py
__version__=''' $Id$ '''
__doc__="""This will be a collection of legends to be used with charts."""
import copy, operator
from reportlab.lib import colors
from reportlab.lib.validators import isNumber, OneOf, isString, isColorOrNone,\
isNumberOrNone, isListOfNumbersOrNone, isStringOrNone, isBoolean,\
EitherOr, NoneOr, AutoOr, isAuto, Auto, isBoxAnchor, SequenceOf, isInstanceOf
from reportlab.lib.attrmap import *
from reportlab.pdfbase.pdfmetrics import stringWidth, getFont
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
from reportlab.graphics.shapes import Drawing, Group, String, Rect, Line, STATE_DEFAULTS
from reportlab.graphics.charts.areas import PlotArea
from reportlab.graphics.widgets.markers import uSymbol2Symbol, isSymbol
from reportlab.lib.utils import isSeq, find_locals
from reportlab.graphics.shapes import _baseGFontName
from functools import reduce
def _transMax(n,A):
X = n*[0]
m = 0
for a in A:
m = max(m,len(a))
for i,x in enumerate(a):
X[i] = max(X[i],x)
X = [0] + X[:m]
for i in range(m):
X[i+1] += X[i]
return X
def _objStr(s):
if isinstance(s,str):
return s
else:
return str(s)
def _getStr(s):
if isSeq(s):
return list(map(_getStr,s))
else:
return _objStr(s)
def _getLines(s):
if isSeq(s):
return tuple([(x or '').split('\n') for x in s])
else:
return (s or '').split('\n')
def _getLineCount(s):
T = _getLines(s)
if isSeq(s):
return max([len(x) for x in T])
else:
return len(T)
def _getWidths(i,s, fontName, fontSize, subCols):
S = []
aS = S.append
if isSeq(s):
for j,t in enumerate(s):
sc = subCols[j,i]
fN = getattr(sc,'fontName',fontName)
fS = getattr(sc,'fontSize',fontSize)
m = [stringWidth(x, fN, fS) for x in t.split('\n')]
m = max(sc.minWidth,m and max(m) or 0)
aS(m)
aS(sc.rpad)
del S[-1]
else:
sc = subCols[0,i]
fN = getattr(sc,'fontName',fontName)
fS = getattr(sc,'fontSize',fontSize)
m = [stringWidth(x, fN, fS) for x in s.split('\n')]
aS(max(sc.minWidth,m and max(m) or 0))
return S
class SubColProperty(PropHolder):
dividerLines = 0
_attrMap = AttrMap(
minWidth = AttrMapValue(isNumber,desc="minimum width for this subcol"),
rpad = AttrMapValue(isNumber,desc="right padding for this subcol"),
align = AttrMapValue(OneOf('left','right','center','centre','numeric'),desc='alignment in subCol'),
fontName = AttrMapValue(isString, desc="Font name of the strings"),
fontSize = AttrMapValue(isNumber, desc="Font size of the strings"),
leading = AttrMapValue(isNumber, desc="leading for the strings"),
fillColor = AttrMapValue(isColorOrNone, desc="fontColor"),
underlines = AttrMapValue(EitherOr((NoneOr(isInstanceOf(Line)),SequenceOf(isInstanceOf(Line),emptyOK=0,lo=0,hi=0x7fffffff))), desc="underline definitions"),
overlines = AttrMapValue(EitherOr((NoneOr(isInstanceOf(Line)),SequenceOf(isInstanceOf(Line),emptyOK=0,lo=0,hi=0x7fffffff))), desc="overline definitions"),
dx = AttrMapValue(isNumber, desc="x offset from default position"),
dy = AttrMapValue(isNumber, desc="y offset from default position"),
)
class LegendCallout:
def _legendValues(legend,*args):
'''return a tuple of values from the first function up the stack with isinstance(self,legend)'''
L = find_locals(lambda L: L.get('self',None) is legend and L or None)
return tuple([L[a] for a in args])
_legendValues = staticmethod(_legendValues)
def _selfOrLegendValues(self,legend,*args):
L = find_locals(lambda L: L.get('self',None) is legend and L or None)
return tuple([getattr(self,a,L[a]) for a in args])
def __call__(self,legend,g,thisx,y,colName):
col, name = colName
class LegendSwatchCallout(LegendCallout):
def __call__(self,legend,g,thisx,y,i,colName,swatch):
col, name = colName
class LegendColEndCallout(LegendCallout):
def __call__(self,legend, g, x, xt, y, width, lWidth):
pass
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",advancedUsage=1),
autoYPadding = AttrMapValue(isNumber, desc="y Padding between rows if deltay=None",advancedUsage=1),
yGap = AttrMapValue(isNumber, desc="Additional gap between rows",advancedUsage=1),
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="swatches filling color"),
strokeColor = AttrMapValue(isColorOrNone, desc="Border color of the swatches"),
strokeWidth = AttrMapValue(isNumber, desc="Width of the border color of the swatches"),
swatchMarker = AttrMapValue(NoneOr(AutoOr(isSymbol)), desc="None, Auto() or makeMarker('Diamond') ...",advancedUsage=1),
callout = AttrMapValue(None, desc="a user callout(self,g,x,y,(color,text))",advancedUsage=1),
boxAnchor = AttrMapValue(isBoxAnchor,'Anchor point for the legend area'),
variColumn = AttrMapValue(isBoolean,'If true column widths may vary (default is false)',advancedUsage=1),
dividerLines = AttrMapValue(OneOf(0,1,2,3,4,5,6,7),'If 1 we have dividers between the rows | 2 for extra top | 4 for bottom',advancedUsage=1),
dividerWidth = AttrMapValue(isNumber, desc="dividerLines width",advancedUsage=1),
dividerColor = AttrMapValue(isColorOrNone, desc="dividerLines color",advancedUsage=1),
dividerDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array for dividerLines.',advancedUsage=1),
dividerOffsX = AttrMapValue(SequenceOf(isNumber,emptyOK=0,lo=2,hi=2), desc='divider lines X offsets',advancedUsage=1),
dividerOffsY = AttrMapValue(isNumber, desc="dividerLines Y offset",advancedUsage=1),
colEndCallout = AttrMapValue(None, desc="a user callout(self,g, x, xt, y,width, lWidth)",advancedUsage=1),
subCols = AttrMapValue(None,desc="subColumn properties"),
swatchCallout = AttrMapValue(None, desc="a user swatch callout(self,g,x,y,i,(col,name),swatch)",advancedUsage=1),
swdx = AttrMapValue(isNumber, desc="x position adjustment for the swatch"),
swdy = AttrMapValue(isNumber, desc="y position adjustment for the swatch"),
)
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
self.swdx = 0
self.swdy = 0
# 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']
self.swatchMarker = None
self.boxAnchor = 'nw'
self.yGap = 0
self.variColumn = 0
self.dividerLines = 0
self.dividerWidth = 0.5
self.dividerDashArray = None
self.dividerColor = colors.black
self.dividerOffsX = (0,0)
self.dividerOffsY = 0
self.colEndCallout = None
self._init_subCols()
def _init_subCols(self):
sc = self.subCols = TypedPropertyCollection(SubColProperty)
sc.rpad = 1
sc.dx = sc.dy = sc.minWidth = 0
sc.align = 'right'
sc[0].align = 'left'
def _getChartStyleName(self,chart):
for a in 'lines', 'bars', 'slices', 'strands':
if hasattr(chart,a): return a
return None
def _getChartStyle(self,chart):
return getattr(chart,self._getChartStyleName(chart),None)
def _getTexts(self,colorNamePairs):
if not isAuto(colorNamePairs):
texts = [_getStr(p[1]) for p in colorNamePairs]
else:
chart = getattr(colorNamePairs,'chart',getattr(colorNamePairs,'obj',None))
texts = [chart.getSeriesName(i,'series %d' % i) for i in range(chart._seriesCount)]
return texts
def _calculateMaxBoundaries(self, colorNamePairs):
"Calculate the maximum width of some given strings."
fontName = self.fontName
fontSize = self.fontSize
subCols = self.subCols
M = [_getWidths(i, m, fontName, fontSize, subCols) for i,m in enumerate(self._getTexts(colorNamePairs))]
if not M:
return [0,0]
n = max([len(m) for m in M])
if self.variColumn:
columnMaximum = self.columnMaximum
return [_transMax(n,M[r:r+columnMaximum]) for r in range(0,len(M),self.columnMaximum)]
else:
return _transMax(n,M)
def _calcHeight(self):
dy = self.dy
yGap = self.yGap
thisy = upperlefty = self.y - dy
fontSize = self.fontSize
ascent=getFont(self.fontName).face.ascent/1000.
if ascent==0: ascent=0.718 # default (from helvetica)
ascent *= fontSize
leading = fontSize*1.2
deltay = self.deltay
if not deltay: deltay = max(dy,leading)+self.autoYPadding
columnCount = 0
count = 0
lowy = upperlefty
lim = self.columnMaximum - 1
for name in self._getTexts(self.colorNamePairs):
y0 = thisy+(dy-ascent)*0.5
y = y0 - _getLineCount(name)*leading
leadingMove = 2*y0-y-thisy
newy = thisy-max(deltay,leadingMove)-yGap
lowy = min(y,newy,lowy)
if count==lim:
count = 0
thisy = upperlefty
columnCount = columnCount + 1
else:
thisy = newy
count = count+1
return upperlefty - lowy
def _defaultSwatch(self,x,thisy,dx,dy,fillColor,strokeWidth,strokeColor):
return Rect(x, thisy, dx, dy,
fillColor = fillColor,
strokeColor = strokeColor,
strokeWidth = strokeWidth,
)
def draw(self):
colorNamePairs = self.colorNamePairs
autoCP = isAuto(colorNamePairs)
if autoCP:
chart = getattr(colorNamePairs,'chart',getattr(colorNamePairs,'obj',None))
swatchMarker = None
autoCP = Auto(obj=chart)
n = chart._seriesCount
chartTexts = self._getTexts(colorNamePairs)
else:
swatchMarker = getattr(self,'swatchMarker',None)
if isAuto(swatchMarker):
chart = getattr(swatchMarker,'chart',getattr(swatchMarker,'obj',None))
swatchMarker = Auto(obj=chart)
n = len(colorNamePairs)
dx = self.dx
dy = self.dy
alignment = self.alignment
columnMaximum = self.columnMaximum
deltax = self.deltax
deltay = self.deltay
dxTextSpace = self.dxTextSpace
fontName = self.fontName
fontSize = self.fontSize
fillColor = self.fillColor
strokeWidth = self.strokeWidth
strokeColor = self.strokeColor
subCols = self.subCols
leading = fontSize*1.2
yGap = self.yGap
if not deltay:
deltay = max(dy,leading)+self.autoYPadding
ba = self.boxAnchor
maxWidth = self._calculateMaxBoundaries(colorNamePairs)
nCols = int((n+columnMaximum-1)/(columnMaximum*1.0))
xW = dx+dxTextSpace+self.autoXPadding
variColumn = self.variColumn
if variColumn:
width = reduce(operator.add,[m[-1] for m in maxWidth],0)+xW*nCols
else:
deltax = max(maxWidth[-1]+xW,deltax)
width = maxWidth[-1]+nCols*deltax
maxWidth = nCols*[maxWidth]
thisx = self.x
thisy = self.y - self.dy
if ba not in ('ne','n','nw','autoy'):
height = self._calcHeight()
if ba in ('e','c','w'):
thisy += height/2.
else:
thisy += height
if ba not in ('nw','w','sw','autox'):
if ba in ('n','c','s'):
thisx -= width/2
else:
thisx -= width
upperlefty = thisy
g = Group()
ascent=getFont(fontName).face.ascent/1000.
if ascent==0: ascent=0.718 # default (from helvetica)
ascent *= fontSize # normalize
lim = columnMaximum - 1
callout = getattr(self,'callout',None)
scallout = getattr(self,'swatchCallout',None)
dividerLines = self.dividerLines
if dividerLines:
dividerWidth = self.dividerWidth
dividerColor = self.dividerColor
dividerDashArray = self.dividerDashArray
dividerOffsX = self.dividerOffsX
dividerOffsY = self.dividerOffsY
for i in range(n):
if autoCP:
col = autoCP
col.index = i
name = chartTexts[i]
else:
col, name = colorNamePairs[i]
if isAuto(swatchMarker):
col = swatchMarker
col.index = i
if isAuto(name):
name = getattr(swatchMarker,'chart',getattr(swatchMarker,'obj',None)).getSeriesName(i,'series %d' % i)
T = _getLines(name)
S = []
aS = S.append
j = int(i/(columnMaximum*1.0))
jOffs = maxWidth[j]
# thisy+dy/2 = y+leading/2
y = y0 = thisy+(dy-ascent)*0.5
if callout: callout(self,g,thisx,y,(col,name))
if alignment == "left":
x = thisx
xn = thisx+jOffs[-1]+dxTextSpace
elif alignment == "right":
x = thisx+dx+dxTextSpace
xn = thisx
else:
raise ValueError("bad alignment")
if not isSeq(name):
T = [T]
yd = y
for k,lines in enumerate(T):
y = y0
kk = k*2
x1 = x+jOffs[kk]
x2 = x+jOffs[kk+1]
sc = subCols[k,i]
anchor = sc.align
scdx = sc.dx
scdy = sc.dy
fN = getattr(sc,'fontName',fontName)
fS = getattr(sc,'fontSize',fontSize)
fC = getattr(sc,'fillColor',fillColor)
fL = getattr(sc,'leading',1.2*fontSize)
if fN==fontName:
fA = (ascent*fS)/fontSize
else:
fA = getFont(fontName).face.ascent/1000.
if fA==0: fA=0.718
fA *= fS
if anchor=='left':
anchor = 'start'
xoffs = x1
elif anchor=='right':
anchor = 'end'
xoffs = x2
elif anchor=='numeric':
xoffs = x2
else:
anchor = 'middle'
xoffs = 0.5*(x1+x2)
for t in lines:
aS(String(xoffs+scdx,y+scdy,t,fontName=fN,fontSize=fS,fillColor=fC, textAnchor = anchor))
y -= fL
yd = min(yd,y)
y += fL
for iy, a in ((y-max(fL-fA,0),'underlines'),(y+fA,'overlines')):
il = getattr(sc,a,None)
if il:
if not isinstance(il,(tuple,list)): il = (il,)
for l in il:
l = copy.copy(l)
l.y1 += iy
l.y2 += iy
l.x1 += x1
l.x2 += x2
aS(l)
x = xn
y = yd
leadingMove = 2*y0-y-thisy
if dividerLines:
xd = thisx+dx+dxTextSpace+jOffs[-1]+dividerOffsX[1]
yd = thisy+dy*0.5+dividerOffsY
if ((dividerLines&1) and i%columnMaximum) or ((dividerLines&2) and not i%columnMaximum):
g.add(Line(thisx+dividerOffsX[0],yd,xd,yd,
strokeColor=dividerColor, strokeWidth=dividerWidth, strokeDashArray=dividerDashArray))
if (dividerLines&4) and (i%columnMaximum==lim or i==(n-1)):
yd -= max(deltay,leadingMove)+yGap
g.add(Line(thisx+dividerOffsX[0],yd,xd,yd,
strokeColor=dividerColor, strokeWidth=dividerWidth, strokeDashArray=dividerDashArray))
# Make a 'normal' color swatch...
swatchX = x + getattr(self,'swdx',0)
swatchY = thisy + getattr(self,'swdy',0)
if isAuto(col):
chart = getattr(col,'chart',getattr(col,'obj',None))
c = chart.makeSwatchSample(getattr(col,'index',i),swatchX,swatchY,dx,dy)
elif isinstance(col, colors.Color):
if isSymbol(swatchMarker):
c = uSymbol2Symbol(swatchMarker,swatchX+dx/2.,swatchY+dy/2.,col)
else:
c = self._defaultSwatch(swatchX,swatchY,dx,dy,fillColor=col,strokeWidth=strokeWidth,strokeColor=strokeColor)
elif col is not None:
try:
c = copy.deepcopy(col)
c.x = swatchX
c.y = swatchY
c.width = dx
c.height = dy
except:
c = None
else:
c = None
if c:
g.add(c)
if scallout: scallout(self,g,thisx,y0,i,(col,name),c)
for s in S: g.add(s)
if self.colEndCallout and (i%columnMaximum==lim or i==(n-1)):
if alignment == "left":
xt = thisx
else:
xt = thisx+dx+dxTextSpace
yd = thisy+dy*0.5+dividerOffsY - (max(deltay,leadingMove)+yGap)
self.colEndCallout(self, g, thisx, xt, yd, jOffs[-1], jOffs[-1]+dx+dxTextSpace)
if i%columnMaximum==lim:
if variColumn:
thisx += jOffs[-1]+xW
else:
thisx = thisx+deltax
thisy = upperlefty
else:
thisy = thisy-max(deltay,leadingMove)-yGap
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 = 'red green blue yellow pink black white'.split()
items = [(getattr(colors, i), i) for i in items]
legend.colorNamePairs = items
d.add(legend, 'legend')
return d
class TotalAnnotator(LegendColEndCallout):
def __init__(self, lText='Total', rText='0.0', fontName=_baseGFontName, fontSize=10,
fillColor=colors.black, strokeWidth=0.5, strokeColor=colors.black, strokeDashArray=None,
dx=0, dy=0, dly=0, dlx=(0,0)):
self.lText = lText
self.rText = rText
self.fontName = fontName
self.fontSize = fontSize
self.fillColor = fillColor
self.dy = dy
self.dx = dx
self.dly = dly
self.dlx = dlx
self.strokeWidth = strokeWidth
self.strokeColor = strokeColor
self.strokeDashArray = strokeDashArray
def __call__(self,legend, g, x, xt, y, width, lWidth):
from reportlab.graphics.shapes import String, Line
fontSize = self.fontSize
fontName = self.fontName
fillColor = self.fillColor
strokeColor = self.strokeColor
strokeWidth = self.strokeWidth
ascent=getFont(fontName).face.ascent/1000.
if ascent==0: ascent=0.718 # default (from helvetica)
ascent *= fontSize
leading = fontSize*1.2
yt = y+self.dy-ascent*1.3
if self.lText and fillColor:
g.add(String(xt,yt,self.lText,
fontName=fontName,
fontSize=fontSize,
fillColor=fillColor,
textAnchor = "start"))
if self.rText:
g.add(String(xt+width,yt,self.rText,
fontName=fontName,
fontSize=fontSize,
fillColor=fillColor,
textAnchor = "end"))
if strokeWidth and strokeColor:
yL = y+self.dly-leading
g.add(Line(x+self.dlx[0],yL,x+self.dlx[1]+lWidth,yL,
strokeColor=strokeColor, strokeWidth=strokeWidth,
strokeDashArray=self.strokeDashArray))
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.
"""
def __init__(self):
Legend.__init__(self)
# Size of swatch rectangle.
self.dx = 10
self.dy = 2
def _defaultSwatch(self,x,thisy,dx,dy,fillColor,strokeWidth,strokeColor):
l = LineSwatch()
l.x = x
l.y = thisy
l.width = dx
l.height = dy
l.strokeColor = fillColor
return l
def sample1c():
"Make sample legend."
d = Drawing(200, 100)
legend = Legend()
legend.alignment = 'right'
legend.x = 0
legend.y = 100
legend.dxTextSpace = 5
items = 'red green blue yellow pink black white'.split()
items = [(getattr(colors, i), i) for i in 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 = 'red green blue yellow pink black white'.split()
items = [(getattr(colors, i), i) for i in 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 = 'red green blue yellow pink black white'.split()
items = [(getattr(colors, i), i) for i in 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 = 'red green blue yellow pink black white'.split()
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

View File

@@ -0,0 +1,715 @@
#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
__version__=''' $Id$ '''
__doc__="""This modules defines a very preliminary Line Chart example."""
from reportlab.lib import colors
from reportlab.lib.validators import isNumber, isNumberOrNone, isColor, isColorOrNone, isListOfStrings, \
isListOfStringsOrNone, SequenceOf, isBoolean, NoneOr, \
isListOfNumbersOrNone, isStringOrNone, OneOf, Percentage
from reportlab.lib.attrmap import *
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
from reportlab.graphics.charts.legends import _objStr
class LineChartProperties(PropHolder):
_attrMap = AttrMap(
strokeWidth = AttrMapValue(isNumber, desc='Width of a line.'),
strokeColor = AttrMapValue(isColorOrNone, desc='Color of a line or border.'),
fillColor = AttrMapValue(isColorOrNone, desc='fill color of a bar.'),
strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array of a line.'),
symbol = AttrMapValue(NoneOr(isSymbol), desc='Widget placed at data points.',advancedUsage=1),
shader = AttrMapValue(None, desc='Shader Class.',advancedUsage=1),
filler = AttrMapValue(None, desc='Filler Class.',advancedUsage=1),
name = AttrMapValue(isStringOrNone, desc='Name of the line.'),
lineStyle = AttrMapValue(NoneOr(OneOf('line','joinedLine','bar')), desc="What kind of plot this line is",advancedUsage=1),
barWidth = AttrMapValue(isNumberOrNone,desc="Percentage of available width to be used for a bar",advancedUsage=1),
)
class AbstractLineChart(PlotArea):
def makeSwatchSample(self,rowNo, x, y, width, height):
baseStyle = self.lines
styleIdx = rowNo % len(baseStyle)
style = baseStyle[styleIdx]
color = style.strokeColor
yh2 = y+height/2.
lineStyle = getattr(style,'lineStyle',None)
if lineStyle=='bar':
dash = getattr(style, 'strokeDashArray', getattr(baseStyle,'strokeDashArray',None))
strokeWidth= getattr(style, 'strokeWidth', getattr(style, 'strokeWidth',None))
L = Rect(x,y,width,height,strokeWidth=strokeWidth,strokeColor=color,strokeLineCap=0,strokeDashArray=dash,fillColor=getattr(style,'fillColor',color))
elif self.joinedLines or lineStyle=='joinedLine':
dash = getattr(style, 'strokeDashArray', getattr(baseStyle,'strokeDashArray',None))
strokeWidth= getattr(style, 'strokeWidth', getattr(style, 'strokeWidth',None))
L = Line(x,yh2,x+width,yh2,strokeColor=color,strokeLineCap=0)
if strokeWidth: L.strokeWidth = strokeWidth
if dash: L.strokeDashArray = dash
else:
L = None
if hasattr(style, 'symbol'):
S = style.symbol
elif hasattr(baseStyle, 'symbol'):
S = baseStyle.symbol
else:
S = None
if S: S = uSymbol2Symbol(S,x+width/2.,yh2,color)
if S and L:
g = Group()
g.add(L)
g.add(S)
return g
return S or L
def getSeriesName(self,i,default=None):
'''return series name i or default'''
return _objStr(getattr(self.lines[i],'name',default))
class LineChart(AbstractLineChart):
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.',advancedUsage=1),
lineLabelNudge = AttrMapValue(isNumber, desc='Distance between a data point and its label.',advancedUsage=1),
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.',advancedUsage=1),
reversePlotOrder = AttrMapValue(isBoolean, desc='If true reverse plot order.',advancedUsage=1),
annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.',advancedUsage=1),
)
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(list(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._normFactor = normFactor
self._yzero = yzero = self.valueAxis.scale(0)
self._hngs = hngs = 0.5 * self.groupSpacing * normFactor
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 + hngs
y = yzero
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 str:
if labelFmt == 'values':
try:
labelText = self.lineLabelArray[rowNo][colNo]
except:
labelText = None
else:
labelText = labelFmt % labelValue
elif hasattr(labelFmt,'__call__'):
labelText = labelFmt(labelValue)
else:
raise ValueError("Unknown formatter type %s, expected string or function"%labelFmt)
if labelText:
label = self.lineLabels[(rowNo, colNo)]
if not label.visible: return
# 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 = list(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)
yzero = self._yzero
# 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)
lineStyle = getattr(rowStyle,'lineStyle',None)
if hasattr(rowStyle, 'strokeWidth'):
strokeWidth = rowStyle.strokeWidth
elif hasattr(self.lines, 'strokeWidth'):
strokeWidth = self.lines.strokeWidth
else:
strokeWidth = None
# Iterate over data columns.
if lineStyle=='bar':
barWidth = getattr(rowStyle,'barWidth',Percentage(50))
fillColor = getattr(rowStyle,'fillColor',rowColor)
if isinstance(barWidth,Percentage):
hbw = self._hngs*barWidth*0.01
else:
hbw = barWidth*0.5
for colNo in range(len(row)):
x,y = row[colNo]
g.add(Rect(x-hbw,min(y,yzero),2*hbw,abs(y-yzero),strokeWidth=strokeWidth,strokeColor=rowColor,fillColor=fillColor))
elif self.joinedLines or lineStyle=='joinedLine':
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(rowStyle, 'symbol'):
uSymbol = rowStyle.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)
cAdgl = getattr(cA,'drawGridLast',False)
vAdgl = getattr(vA,'drawGridLast',False)
if not cAdgl: cA.makeGrid(g,parent=self,dim=vA.getGridDims)
if not vAdgl: vA.makeGrid(g,parent=self,dim=cA.getGridDims)
g.add(self.makeLines())
if cAdgl: cA.makeGrid(g,parent=self,dim=vA.getGridDims)
if vAdgl: vA.makeGrid(g,parent=self,dim=cA.getGridDims)
for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale))
return g
def _fakeItemKey(a):
'''t, z0, z1, x, y = a[:5]'''
return (-a[1],a[3],a[0],-a[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(key=_fakeItemKey)
#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 = list(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 reportlab.graphics.charts.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 range(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 range(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 range(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()
for v in F.value(): g.add(v[-1])
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 = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
lc.categoryAxis.categoryNames = catNames
lc.categoryAxis.labels.boxAnchor = 'n'
lc.valueAxis.valueMin = 0
lc.valueAxis.valueMax = 60
lc.valueAxis.valueStep = 15
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 = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
lc.categoryAxis.categoryNames = catNames
lc.categoryAxis.labels.boxAnchor = 'n'
lc.valueAxis.valueMin = 0
lc.valueAxis.valueMax = 60
lc.valueAxis.valueStep = 15
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 = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
lc.categoryAxis.categoryNames = catNames
lc.categoryAxis.labels.boxAnchor = 'n'
lc.valueAxis.valueMin = 0
lc.valueAxis.valueMax = 60
lc.valueAxis.valueStep = 15
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 = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
lc.categoryAxis.categoryNames = catNames
lc.categoryAxis.labels.boxAnchor = 'n'
lc.valueAxis.valueMin = 0
lc.valueAxis.valueMax = 60
lc.valueAxis.valueStep = 15
drawing.add(lc)
return drawing

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/markers.py
__version__=''' $Id$ '''
__doc__="""This modules defines a collection of markers used in charts.
The make* functions return a simple shape or a widget as for
the smiley.
"""
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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,186 @@
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")

View File

@@ -0,0 +1,408 @@
#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
__version__=''' $Id$ '''
__doc__="""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.
"""
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, isStringOrNone, EitherOr,\
isCallable
from reportlab.lib.attrmap import *
from reportlab.pdfgen.canvas import Canvas
from reportlab.graphics.shapes import Group, Drawing, Line, Rect, Polygon, PolyLine, Ellipse, \
Wedge, String, STATE_DEFAULTS
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
from reportlab.graphics.charts.areas import PlotArea
from reportlab.graphics.charts.legends import _objStr
from reportlab.graphics.charts.piecharts import WedgeLabel
from reportlab.graphics.widgets.markers import makeMarker, uSymbol2Symbol, isSymbol
class StrandProperty(PropHolder):
_attrMap = AttrMap(
strokeWidth = AttrMapValue(isNumber,desc='width'),
fillColor = AttrMapValue(isColorOrNone,desc='filling color'),
strokeColor = AttrMapValue(isColorOrNone,desc='stroke color'),
strokeDashArray = AttrMapValue(isListOfNumbersOrNone,desc='dashing pattern, e.g. (3,2)'),
symbol = AttrMapValue(EitherOr((isStringOrNone,isSymbol)), desc='Widget placed at data points.',advancedUsage=1),
symbolSize= AttrMapValue(isNumber, desc='Symbol size.',advancedUsage=1),
name = AttrMapValue(isStringOrNone, desc='Name of the strand.'),
)
def __init__(self):
self.strokeWidth = 1
self.fillColor = None
self.strokeColor = STATE_DEFAULTS["strokeColor"]
self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"]
self.symbol = None
self.symbolSize = 5
self.name = None
class SpokeProperty(PropHolder):
_attrMap = AttrMap(
strokeWidth = AttrMapValue(isNumber,desc='width'),
fillColor = AttrMapValue(isColorOrNone,desc='filling color'),
strokeColor = AttrMapValue(isColorOrNone,desc='stroke color'),
strokeDashArray = AttrMapValue(isListOfNumbersOrNone,desc='dashing pattern, e.g. (2,1)'),
labelRadius = AttrMapValue(isNumber,desc='label radius',advancedUsage=1),
visible = AttrMapValue(isBoolean,desc="True if the spoke line is to be drawn"),
)
def __init__(self,**kw):
self.strokeWidth = 0.5
self.fillColor = None
self.strokeColor = STATE_DEFAULTS["strokeColor"]
self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"]
self.visible = 1
self.labelRadius = 1.05
class SpokeLabel(WedgeLabel):
def __init__(self,**kw):
WedgeLabel.__init__(self,**kw)
if '_text' not in list(kw.keys()): self._text = ''
class StrandLabel(SpokeLabel):
_attrMap = AttrMap(BASE=SpokeLabel,
format = AttrMapValue(EitherOr((isStringOrNone,isCallable)),desc="Format for the label"),
dR = AttrMapValue(isNumberOrNone,desc="radial shift for label"),
)
def __init__(self,**kw):
self.format = ''
self.dR = 0
SpokeLabel.__init__(self,**kw)
def _setupLabel(labelClass, text, radius, cx, cy, angle, car, sar, sty):
L = labelClass()
L._text = text
L.x = cx + radius*car
L.y = cy + radius*sar
L._pmv = angle*180/pi
L.boxAnchor = sty.boxAnchor
L.dx = sty.dx
L.dy = sty.dy
L.angle = sty.angle
L.boxAnchor = sty.boxAnchor
L.boxStrokeColor = sty.boxStrokeColor
L.boxStrokeWidth = sty.boxStrokeWidth
L.boxFillColor = sty.boxFillColor
L.strokeColor = sty.strokeColor
L.strokeWidth = sty.strokeWidth
L.leading = sty.leading
L.width = sty.width
L.maxWidth = sty.maxWidth
L.height = sty.height
L.textAnchor = sty.textAnchor
L.visible = sty.visible
L.topPadding = sty.topPadding
L.leftPadding = sty.leftPadding
L.rightPadding = sty.rightPadding
L.bottomPadding = sty.bottomPadding
L.fontName = sty.fontName
L.fontSize = sty.fontSize
L.fillColor = sty.fillColor
return L
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"),
spokes = AttrMapValue(None, desc="collection of spoke descriptor objects"),
strandLabels = AttrMapValue(None, desc="collection of strand label descriptor objects"),
spokeLabels = AttrMapValue(None, desc="collection of spoke label descriptor objects"),
)
def makeSwatchSample(self, rowNo, x, y, width, height):
baseStyle = self.strands
styleIdx = rowNo % len(baseStyle)
style = baseStyle[styleIdx]
strokeColor = getattr(style, 'strokeColor', getattr(baseStyle,'strokeColor',None))
fillColor = getattr(style, 'fillColor', getattr(baseStyle,'fillColor',None))
strokeDashArray = getattr(style, 'strokeDashArray', getattr(baseStyle,'strokeDashArray',None))
strokeWidth = getattr(style, 'strokeWidth', getattr(baseStyle, 'strokeWidth',0))
symbol = getattr(style, 'symbol', getattr(baseStyle, 'symbol',None))
ym = y+height/2.0
if fillColor is None and strokeColor is not None and strokeWidth>0:
bg = Line(x,ym,x+width,ym,strokeWidth=strokeWidth,strokeColor=strokeColor,
strokeDashArray=strokeDashArray)
elif fillColor is not None:
bg = Rect(x,y,width,height,strokeWidth=strokeWidth,strokeColor=strokeColor,
strokeDashArray=strokeDashArray,fillColor=fillColor)
else:
bg = None
if symbol:
symbol = uSymbol2Symbol(symbol,x+width/2.,ym,color)
if bg:
g = Group()
g.add(bg)
g.add(symbol)
return g
return symbol or bg
def getSeriesName(self,i,default=None):
'''return series name i or default'''
return _objStr(getattr(self.strands[i],'name',default))
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.labels = ['a','b','c','d','e','f']
self.startAngle = 90
self.direction = "clockwise"
self.strands = TypedPropertyCollection(StrandProperty)
self.spokes = TypedPropertyCollection(SpokeProperty)
self.spokeLabels = TypedPropertyCollection(SpokeLabel)
self.spokeLabels._text = None
self.strandLabels = TypedPropertyCollection(StrandLabel)
self.x = 10
self.y = 10
self.width = 180
self.height = 180
def demo(self):
d = Drawing(200, 200)
d.add(SpiderChart())
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
assert min(list(map(min,data))) >=0, "Cannot do spider plots of negative numbers!"
norm = max(list(map(max,data)))
norm *= (1.0+outer)
if norm<1e-9: norm = 1.0
self._norm = norm
return [[e/norm for e in row] for row in data]
def _innerDrawLabel(self, sty, radius, cx, cy, angle, car, sar, labelClass=StrandLabel):
"Draw a label for a given item in the list."
fmt = sty.format
value = radius*self._norm
if not fmt:
text = None
elif isinstance(fmt,str):
if fmt == 'values':
text = sty._text
else:
text = fmt % value
elif hasattr(fmt,'__call__'):
text = fmt(value)
else:
raise ValueError("Unknown formatter type %s, expected string or function" % fmt)
if text:
dR = sty.dR
if dR:
radius += dR/self._radius
L = _setupLabel(labelClass, text, radius, cx, cy, angle, car, sar, sty)
if dR<0: L._anti = 1
else:
L = None
return L
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)
cx = self.x + xradius
cy = self.y + yradius
data = self.normalizeData()
self._seriesCount = len(data)
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
S = []
STRANDS = []
STRANDAREAS = []
syms = []
labs = []
csa = []
angle = self.startAngle*pi/180
direction = self.direction == "clockwise" and -1 or 1
angleBetween = direction*(2 * pi)/float(n)
spokes = self.spokes
spokeLabels = self.spokeLabels
for i in range(n):
car = cos(angle)*radius
sar = sin(angle)*radius
csa.append((car,sar,angle))
si = self.spokes[i]
if si.visible:
spoke = Line(cx, cy, cx + car, cy + sar, strokeWidth = si.strokeWidth, strokeColor=si.strokeColor, strokeDashArray=si.strokeDashArray)
S.append(spoke)
sli = spokeLabels[i]
text = sli._text
if not text: text = labels[i]
if text:
S.append(_setupLabel(WedgeLabel, text, si.labelRadius, cx, cy, angle, car, sar, sli))
angle += angleBetween
# now plot the polygons
rowIdx = 0
strands = self.strands
strandLabels = self.strandLabels
for row in data:
# series plot
rsty = strands[rowIdx]
points = []
car, sar = csa[-1][:2]
r = row[-1]
points.append(cx+car*r)
points.append(cy+sar*r)
for i in range(n):
car, sar, angle = csa[i]
r = row[i]
points.append(cx+car*r)
points.append(cy+sar*r)
L = self._innerDrawLabel(strandLabels[(rowIdx,i)], r, cx, cy, angle, car, sar, labelClass=StrandLabel)
if L: labs.append(L)
sty = strands[(rowIdx,i)]
uSymbol = sty.symbol
# put in a marker, if it needs one
if uSymbol:
s_x = cx+car*r
s_y = cy+sar*r
s_fillColor = sty.fillColor
s_strokeColor = sty.strokeColor
s_strokeWidth = sty.strokeWidth
s_angle = 0
s_size = sty.symbolSize
if type(uSymbol) is type(''):
symbol = makeMarker(uSymbol,
size = s_size,
x = s_x,
y = s_y,
fillColor = s_fillColor,
strokeColor = s_strokeColor,
strokeWidth = s_strokeWidth,
angle = s_angle,
)
else:
symbol = uSymbol2Symbol(uSymbol,s_x,s_y,s_fillColor)
for k,v in (('size', s_size), ('fillColor', s_fillColor),
('x', s_x), ('y', s_y),
('strokeColor',s_strokeColor), ('strokeWidth',s_strokeWidth),
('angle',s_angle),):
if getattr(symbol,k,None) is None:
try:
setattr(symbol,k,v)
except:
pass
syms.append(symbol)
# make up the 'strand'
if rsty.fillColor:
strand = Polygon(points)
strand.fillColor = rsty.fillColor
strand.strokeColor = None
strand.strokeWidth = 0
STRANDAREAS.append(strand)
if rsty.strokeColor and rsty.strokeWidth:
strand = PolyLine(points)
strand.strokeColor = rsty.strokeColor
strand.strokeWidth = rsty.strokeWidth
strand.strokeDashArray = rsty.strokeDashArray
STRANDS.append(strand)
rowIdx += 1
for s in (STRANDAREAS+STRANDS+syms+S+labs): g.add(s)
return g
def sample1():
"Make a simple spider chart"
d = Drawing(400, 400)
sp = SpiderChart()
sp.x = 50
sp.y = 50
sp.width = 300
sp.height = 300
sp.data = [[10,12,14,16,14,12], [6,8,10,12,9,15],[7,8,17,4,12,8]]
sp.labels = ['a','b','c','d','e','f']
sp.strands[0].strokeColor = colors.cornsilk
sp.strands[1].strokeColor = colors.cyan
sp.strands[2].strokeColor = colors.palegreen
sp.strands[0].fillColor = colors.cornsilk
sp.strands[1].fillColor = colors.cyan
sp.strands[2].fillColor = colors.palegreen
sp.spokes.strokeDashArray = (2,2)
d.add(sp)
return d
def sample2():
"Make a spider chart with markers, but no fill"
d = Drawing(400, 400)
sp = SpiderChart()
sp.x = 50
sp.y = 50
sp.width = 300
sp.height = 300
sp.data = [[10,12,14,16,14,12], [6,8,10,12,9,15],[7,8,17,4,12,8]]
sp.labels = ['U','V','W','X','Y','Z']
sp.strands.strokeWidth = 1
sp.strands[0].fillColor = colors.pink
sp.strands[1].fillColor = colors.lightblue
sp.strands[2].fillColor = colors.palegreen
sp.strands[0].strokeColor = colors.red
sp.strands[1].strokeColor = colors.blue
sp.strands[2].strokeColor = colors.green
sp.strands.symbol = "FilledDiamond"
sp.strands[1].symbol = makeMarker("Circle")
sp.strands[1].symbol.strokeWidth = 0.5
sp.strands[1].symbol.fillColor = colors.yellow
sp.strands.symbolSize = 6
sp.strandLabels[0,3]._text = 'special'
sp.strandLabels[0,1]._text = 'one'
sp.strandLabels[0,0]._text = 'zero'
sp.strandLabels[1,0]._text = 'Earth'
sp.strandLabels[2,2]._text = 'Mars'
sp.strandLabels.format = 'values'
sp.strandLabels.dR = -5
d.add(sp)
return d
if __name__=='__main__':
d = sample1()
from reportlab.graphics.renderPDF import drawToFile
drawToFile(d, 'spider.pdf')
d = sample2()
drawToFile(d, 'spider2.pdf')

View File

@@ -0,0 +1,466 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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.utils import simpleSplit, _simpleSplit
from reportlab.lib.validators import isNumber, isNumberOrNone, OneOf, isColorOrNone, isString, \
isTextAnchor, isBoxAnchor, isBoolean, NoneOr, isInstanceOf, isNoneOrString, isNoneOrCallable
from reportlab.lib.attrmap import *
from reportlab.pdfbase.pdfmetrics import stringWidth, getAscentDescent
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
from reportlab.graphics.shapes import _baseGFontName
_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 _pathNumTrunc(n):
if int(n)==n: return int(n)
return round(n,5)
def _processGlyph(G, truncate=1, pathReverse=0):
O = []
P = []
R = []
if G and len(G)==1 and G[0][0]=='lineTo':
G = (('moveToClosed',)+G[0][1:],)+G #hack fix for some errors
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 range(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 = list(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=_baseGFontName, fontSize=1000,
anchor='start', truncate=1, pathReverse=0):
from reportlab.graphics import renderPM, _renderPM
_gs = _renderPM.gstate(1,1)
renderPM._setFont(_gs,fontName,fontSize)
P = []
if not anchor=='start':
textLen = stringWidth(text, fontName,fontSize)
if anchor=='end':
x = x-textLen
elif 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=_baseGFontName, fontSize=1000,
anchor='start', truncate=1, pathReverse=0,**kwds):
return definePath(_text2PathDescription(text,x=x,y=y,fontName=fontName,
fontSize=fontSize,anchor=anchor,truncate=truncate,pathReverse=pathReverse),**kwds)
_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,desc=''),
y = AttrMapValue(isNumber,desc=''),
dx = AttrMapValue(isNumber,desc='delta x - offset'),
dy = AttrMapValue(isNumber,desc='delta y - offset'),
angle = AttrMapValue(isNumber,desc='angle of label: default (0), 90 is vertical, 180 is upside down, etc'),
boxAnchor = AttrMapValue(isBoxAnchor,desc='anchoring point of the label'),
boxStrokeColor = AttrMapValue(isColorOrNone,desc='border color of the box'),
boxStrokeWidth = AttrMapValue(isNumber,desc='border width'),
boxFillColor = AttrMapValue(isColorOrNone,desc='the filling color of the box'),
boxTarget = AttrMapValue(OneOf('normal','anti','lo','hi'),desc="one of ('normal','anti','lo','hi')"),
fillColor = AttrMapValue(isColorOrNone,desc='label text color'),
strokeColor = AttrMapValue(isColorOrNone,desc='label text border color'),
strokeWidth = AttrMapValue(isNumber,desc='label text border width'),
text = AttrMapValue(isString,desc='the actual text to display'),
fontName = AttrMapValue(isString,desc='the name of the font used'),
fontSize = AttrMapValue(isNumber,desc='the size of the font'),
leading = AttrMapValue(isNumberOrNone,desc=''),
width = AttrMapValue(isNumberOrNone,desc='the width of the label'),
maxWidth = AttrMapValue(isNumberOrNone,desc='maximum width the label can grow to'),
height = AttrMapValue(isNumberOrNone,desc='the height of the text'),
textAnchor = AttrMapValue(isTextAnchor,desc='the anchoring point of the text inside the label'),
visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
topPadding = AttrMapValue(isNumber,desc='padding at top of box'),
leftPadding = AttrMapValue(isNumber,desc='padding at left of box'),
rightPadding = AttrMapValue(isNumber,desc='padding at right of box'),
bottomPadding = AttrMapValue(isNumber,desc='padding at bottom of box'),
useAscentDescent = AttrMapValue(isBoolean,desc="If True then the font's Ascent & Descent will be used to compute default heights and baseline."),
customDrawChanger = AttrMapValue(isNoneOrCallable,desc="An instance of CustomDrawChanger to modify the behavior at draw time", _advancedUsage=1),
)
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,
boxTarget = 'normal',
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,
useAscentDescent = False,
)
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._lineWidths = []
topPadding = self.topPadding
leftPadding = self.leftPadding
rightPadding = self.rightPadding
bottomPadding = self.bottomPadding
self._lines = simpleSplit(self._text,self.fontName,self.fontSize,self.maxWidth)
if not self.width:
self._width = leftPadding+rightPadding
if self._lines:
self._lineWidths = [stringWidth(line,self.fontName,self.fontSize) for line in self._lines]
self._width += max(self._lineWidths)
else:
self._width = self.width
if self.useAscentDescent:
self._ascent, self._descent = getAscentDescent(self.fontName,self.fontSize)
self._baselineRatio = self._ascent/(self._ascent-self._descent)
else:
self._baselineRatio = 1/1.2
if self.leading:
self._leading = self.leading
elif self.useAscentDescent:
self._leading = self._ascent - self._descent
else:
self._leading = self.fontSize*1.2
self._height = self.height or (self._leading*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 _rawDraw(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._leading*self._baselineRatio
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
svgAttrs=getattr(self,'_svgAttrs',{})
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 -= leading
else:
for line in self._lines:
s = String(x, y, line, _svgAttrs=svgAttrs)
s.textAnchor = textAnchor
s.fontName = fontName
s.fontSize = fontSize
s.fillColor = fillColor
g.add(s)
y -= leading
return g
def draw(self):
customDrawChanger = getattr(self,'customDrawChanger',None)
if customDrawChanger:
customDrawChanger(True,self)
try:
return self._rawDraw()
finally:
customDrawChanger(False,self)
else:
return self._rawDraw()
class LabelDecorator:
_attrMap = AttrMap(
x = AttrMapValue(isNumberOrNone,desc=''),
y = AttrMapValue(isNumberOrNone,desc=''),
dx = AttrMapValue(isNumberOrNone,desc=''),
dy = AttrMapValue(isNumberOrNone,desc=''),
angle = AttrMapValue(isNumberOrNone,desc=''),
boxAnchor = AttrMapValue(isBoxAnchor,desc=''),
boxStrokeColor = AttrMapValue(isColorOrNone,desc=''),
boxStrokeWidth = AttrMapValue(isNumberOrNone,desc=''),
boxFillColor = AttrMapValue(isColorOrNone,desc=''),
fillColor = AttrMapValue(isColorOrNone,desc=''),
strokeColor = AttrMapValue(isColorOrNone,desc=''),
strokeWidth = AttrMapValue(isNumberOrNone),desc='',
fontName = AttrMapValue(isNoneOrString,desc=''),
fontSize = AttrMapValue(isNumberOrNone,desc=''),
leading = AttrMapValue(isNumberOrNone,desc=''),
width = AttrMapValue(isNumberOrNone,desc=''),
maxWidth = AttrMapValue(isNumberOrNone,desc=''),
height = AttrMapValue(isNumberOrNone,desc=''),
textAnchor = AttrMapValue(isTextAnchor,desc=''),
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 PMVLabel(Label):
_attrMap = AttrMap(
BASE=Label,
)
def __init__(self):
Label.__init__(self)
self._pmv = 0
def _getBoxAnchor(self):
a = Label._getBoxAnchor(self)
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 = Label._getTextAnchor(self)
if self._pmv<0: a = {'start':'end', 'middle':'middle', 'end':'start'}[a]
return a
class BarChartLabel(PMVLabel):
"""
An extended Label allowing for nudging, lines visibility etc
"""
_attrMap = AttrMap(
BASE=PMVLabel,
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"),
boxTarget = AttrMapValue(OneOf('normal','anti','lo','hi','mid'),desc="one of ('normal','anti','lo','hi','mid')"),
)
def __init__(self):
PMVLabel.__init__(self)
self.lineStrokeWidth = 0
self.lineStrokeColor = None
self.fixedStart = self.fixedEnd = None
self.nudge = 0
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))
from reportlab.graphics.charts.utils import CustomDrawChanger
class RedNegativeChanger(CustomDrawChanger):
def __init__(self,fillColor=colors.red):
CustomDrawChanger.__init__(self)
self.fillColor = fillColor
def _changer(self,obj):
R = {}
if obj._text.startswith('-'):
R['fillColor'] = obj.fillColor
obj.fillColor = self.fillColor
return R

View File

@@ -0,0 +1,390 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/utils.py
__version__=''' $Id$ '''
__doc__="Utilities used here and there."
from time import mktime, gmtime, strftime
from math import log10, pi, floor, sin, cos, sqrt, hypot
import weakref
from reportlab.graphics.shapes import transformPoint, transformPoints, inverse, Ellipse, Group, String, Path, numericXShift
from reportlab.lib.utils import flatten
from reportlab.pdfbase.pdfmetrics import stringWidth
### 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 = list(map(int, timeString.split('/')))
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.
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
_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 = range(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, labelVOffset=0):
'''
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 = '%+'+repr(w+7)+'.0e'
else:
if power >= 0:
digits = int(power)+w
format = '%' + repr(digits)+'.0f'
else:
digits = w-int(power)
format = '%'+repr(digits+2)+'.'+repr(digits)+'f'
if percent: format=format+'%%'
T = []
n = int(float(hi-t)/grid+0.1)+1
if split:
labels = []
for i in range(n):
v = t+grid*i
T.append(v)
labels.append(format % (v+labelVOffset))
return T, labels
else:
for i in range(n):
v = t+grid*i
T.append((v, format % (v+labelVOffset)))
return T
def findNones(data):
m = len(data)
if None in data:
b = 0
while b<m and data[b] is None:
b += 1
if b==m: return data
l = m-1
while data[l] is None:
l -= 1
l+=1
if b or l: data = data[b:l]
I = [i for i in range(len(data)) if data[i] is None]
for i in I:
data[i] = 0.5*(data[i-1]+data[i+1])
return b, l, data
return 0,m,data
def pairFixNones(pairs):
Y = [x[1] for x in pairs]
b,l,nY = findNones(Y)
m = len(Y)
if b or l<m or nY!=Y:
if b or l<m: pairs = pairs[b:l]
pairs = [(x[0],y) for x,y in zip(pairs,nY)]
return pairs
def maverage(data,n=6):
data = (n-1)*[data[0]]+data
data = [float(sum(data[i-n:i]))/n for i in range(n,len(data)+1)]
return data
def pairMaverage(data,n=6):
return [(x[0],s) for x,s in zip(data, maverage([x[1] for x in data],n))]
class DrawTimeCollector(object):
'''
generic mechanism for collecting information about nodes at the time they are about to be drawn
'''
def __init__(self,formats=['gif']):
self._nodes = weakref.WeakKeyDictionary()
self.clear()
self._pmcanv = None
self.formats = formats
self.disabled = False
def clear(self):
self._info = []
self._info_append = self._info.append
def record(self,func,node,*args,**kwds):
self._nodes[node] = (func,args,kwds)
node.__dict__['_drawTimeCallback'] = self
def __call__(self,node,canvas,renderer):
func = self._nodes.get(node,None)
if func:
func, args, kwds = func
i = func(node,canvas,renderer, *args, **kwds)
if i is not None: self._info_append(i)
@staticmethod
def rectDrawTimeCallback(node,canvas,renderer,**kwds):
A = getattr(canvas,'ctm',None)
if not A: return
x1 = node.x
y1 = node.y
x2 = x1 + node.width
y2 = y1 + node.height
D = kwds.copy()
D['rect']=DrawTimeCollector.transformAndFlatten(A,((x1,y1),(x2,y2)))
return D
@staticmethod
def transformAndFlatten(A,p):
''' transform an flatten a list of points
A transformation matrix
p points [(x0,y0),....(xk,yk).....]
'''
if tuple(A)!=(1,0,0,1,0,0):
iA = inverse(A)
p = transformPoints(iA,p)
return tuple(flatten(p))
@property
def pmcanv(self):
if not self._pmcanv:
import renderPM
self._pmcanv = renderPM.PMCanvas(1,1)
return self._pmcanv
def wedgeDrawTimeCallback(self,node,canvas,renderer,**kwds):
A = getattr(canvas,'ctm',None)
if not A: return
if isinstance(node,Ellipse):
c = self.pmcanv
c.ellipse(node.cx, node.cy, node.rx,node.ry)
p = c.vpath
p = [(x[1],x[2]) for x in p]
else:
p = node.asPolygon().points
p = [(p[i],p[i+1]) for i in range(0,len(p),2)]
D = kwds.copy()
D['poly'] = self.transformAndFlatten(A,p)
return D
def save(self,fnroot):
'''
save the current information known to this collector
fnroot is the root name of a resource to name the saved info
override this to get the right semantics for your collector
'''
import pprint
f=open(fnroot+'.default-collector.out','w')
try:
pprint.pprint(self._info,f)
finally:
f.close()
def xyDist(xxx_todo_changeme, xxx_todo_changeme1 ):
'''return distance between two points'''
(x0,y0) = xxx_todo_changeme
(x1,y1) = xxx_todo_changeme1
return hypot((x1-x0),(y1-y0))
def lineSegmentIntersect(xxx_todo_changeme2, xxx_todo_changeme3, xxx_todo_changeme4, xxx_todo_changeme5
):
(x00,y00) = xxx_todo_changeme2
(x01,y01) = xxx_todo_changeme3
(x10,y10) = xxx_todo_changeme4
(x11,y11) = xxx_todo_changeme5
p = x00,y00
r = x01-x00,y01-y00
q = x10,y10
s = x11-x10,y11-y10
rs = float(r[0]*s[1]-r[1]*s[0])
qp = q[0]-p[0],q[1]-p[1]
qpr = qp[0]*r[1]-qp[1]*r[0]
qps = qp[0]*s[1]-qp[1]*s[0]
if abs(rs)<1e-8:
if abs(qpr)<1e-8: return 'collinear'
return None
t = qps/rs
u = qpr/rs
if 0<=t<=1 and 0<=u<=1:
return p[0]+t*r[0], p[1]+t*r[1]
def makeCircularString(x, y, radius, angle, text, fontName, fontSize, inside=0, G=None,textAnchor='start'):
'''make a group with circular text in it'''
if not G: G = Group()
angle %= 360
pi180 = pi/180
phi = angle*pi180
width = stringWidth(text, fontName, fontSize)
sig = inside and -1 or 1
hsig = sig*0.5
sig90 = sig*90
if textAnchor!='start':
if textAnchor=='middle':
phi += sig*(0.5*width)/radius
elif textAnchor=='end':
phi += sig*float(width)/radius
elif textAnchor=='numeric':
phi += sig*float(numericXShift(textAnchor,text,width,fontName,fontSize,None))/radius
for letter in text:
width = stringWidth(letter, fontName, fontSize)
beta = float(width)/radius
h = Group()
h.add(String(0, 0, letter, fontName=fontName,fontSize=fontSize,textAnchor="start"))
h.translate(x+cos(phi)*radius,y+sin(phi)*radius) #translate to radius and angle
h.rotate((phi-hsig*beta)/pi180-sig90) # rotate as needed
G.add(h) #add to main group
phi -= sig*beta #increment
return G
class CustomDrawChanger:
'''
a class to simplify making changes at draw time
'''
def __init__(self):
self.store = None
def __call__(self,change,obj):
if change:
self.store = self._changer(obj)
assert isinstance(self.store,dict), '%s.changer should return a dict of changed attributes' % self.__class__.__name__
elif self.store is not None:
for a,v in self.store.items():
setattr(obj,a,v)
self.store = None
def _changer(self,obj):
'''
When implemented this method should return a dictionary of
original attribute values so that a future self(False,obj)
can restore them.
'''
raise RuntimeError('Abstract method _changer called')

View File

@@ -0,0 +1,233 @@
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 range(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/_180_pi
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 _segKey(a):
return (a.x0,a.x1,a.y0,a.y1,a.s,a.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 range(len(data)):
ds = data[s]
if not ds: continue
n = len(ds)
if n==1: continue
for i in range(1,n):
seg = _Segment(s,i,data)
if seg.a+abs(seg.b)>=small: a(seg)
S.sort(key=_segKey)
I = []
n = len(S)
for i in range(0,n-1):
s = S[i]
for j in range(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)]]))

View File

@@ -0,0 +1,404 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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
__version__=''' $Id$ '''
__doc__="""Render Drawing objects within others PDFs or standalone
Usage::
import renderpdf
renderpdf.draw(drawing, canvas, x, y)
Execute the script to see some test drawings.
changed
"""
from reportlab.graphics.shapes import *
from reportlab.pdfgen.canvas import Canvas
from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.lib.utils import getBytesIO
from reportlab import rl_config
from reportlab.graphics.renderbase import Renderer, StateTracker, getStateDelta, renderScaledDrawing
# the main entry point for users...
def draw(drawing, canvas, x, y, showBoundary=rl_config._unset_):
"""As it says"""
R = _PDFRenderer()
R.draw(renderScaledDrawing(drawing), canvas, x, y, showBoundary=showBoundary)
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):
path = image.path
# currently not implemented in other renderers
if path and (hasattr(path,'mode') or os.path.exists(image.path)):
self._canvas.drawInlineImage(
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):
if wedge.annular:
self.drawPath(wedge.asPolygon())
else:
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, enc = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text, stringObj.encoding
if not text_anchor in ['start','inherited']:
font, font_size = S['fontName'], S['fontSize']
textLen = stringWidth(text, font, font_size, enc)
if text_anchor=='end':
x -= textLen
elif text_anchor=='middle':
x -= textLen*0.5
elif text_anchor=='numeric':
x -= numericXShift(text_anchor,text,textLen,font,font_size,enc)
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 setStrokeColor(self,c):
self._canvas.setStrokeColor(c)
def setFillColor(self,c):
self._canvas.setFillColor(c)
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.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:
if isinstance(value,(list,tuple)) and len(value)==2 and isinstance(value[1],(tuple,list)):
phase = value[0]
value = value[1]
else:
phase = 0
self._canvas.setDash(value,phase)
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.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)
elif key=='fillOpacity':
if value is not None:
self._canvas.setFillAlpha(value)
elif key=='strokeOpacity':
if value is not None:
self._canvas.setStrokeAlpha(value)
elif key=='fillOverprint':
self._canvas.setFillOverprint(value)
elif key=='strokeOverprint':
self._canvas.setStrokeOverprint(value)
elif key=='overprintMask':
self._canvas.setOverprintMask(value)
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."""
d = renderScaledDrawing(d)
c = Canvas(fn)
if msg:
c.setFont(rl_config.defaultGraphicsFontName, 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(rl_config.defaultGraphicsFontName, 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 = getBytesIO()
drawToFile(d, s, msg=msg, showBoundary=showBoundary,autoSize=autoSize)
return s.getvalue()
#########################################################
#
# test code. First, define a bunch of drawings.
# Routine to draw them comes at the end.
#
#########################################################
def test(outDir='pdfout',shout=False):
from reportlab.graphics.shapes import _baseGFontName, _baseGFontNameBI
from reportlab.rl_config import verbose
import os
if not os.path.isdir(outDir):
os.mkdir(outDir)
fn = os.path.join(outDir,'renderPDF.pdf')
c = Canvas(fn)
c.setFont(_baseGFontName, 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(_baseGFontName, 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(_baseGFontNameBI,12)
c.drawString(80, y, 'Drawing %d' % i)
c.setFont(_baseGFontName,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()
if shout or verbose>2:
print('saved %s' % ascii(fn))
##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(shout=True)
import sys
if len(sys.argv)>1:
outdir = sys.argv[1]
else:
outdir = 'pdfout'
test(outdir,shout=True)
#testFlowable()

View File

@@ -0,0 +1,761 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history www.reportlab.co.uk/rl-cgi/viewcvs.cgi/rlextra/graphics/Csrc/renderPM/renderP.py
__version__=''' $Id$ '''
__doc__="""Render drawing objects in common bitmap formats
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, renderScaledDrawing
from reportlab.pdfbase.pdfmetrics import getFont, unicode2T1
from math import sin, cos, pi, ceil
from reportlab.lib.utils import getStringIO, getBytesIO, open_and_read, isUnicode
from reportlab import rl_config
class RenderPMError(Exception):
pass
import os, sys
try:
from reportlab.graphics import _renderPM
except ImportError as 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 https://www.reportlab.com/software/opensource/rl-addons/"))
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(renderScaledDrawing(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']
alpha = s['strokeOpacity']
if alpha is not None:
self._canvas.strokeOpacity = alpha
self._canvas.setStrokeColor(s['strokeColor'])
self._canvas.lineCap = s['strokeLineCap']
self._canvas.lineJoin = s['strokeLineJoin']
da = s['strokeDashArray']
if not da:
da = None
else:
if not isinstance(da,(list,tuple)):
da = da,
if len(da)!=2 or not isinstance(da[1],(list,tuple)):
da = 0, da #assume phase of 0
self._canvas.dashArray = da
alpha = s['fillOpacity']
if alpha is not None:
self._canvas.fillOpacity = alpha
self._canvas.setFillColor(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):
path = image.path
if isinstance(path,str):
if not (path and os.path.isfile(path)): return
im = _getImage().open(path).convert('RGB')
elif hasattr(path,'convert'):
im = path.convert('RGB')
else:
return
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.tobytes if hasattr(im,'tobytes') else 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):
canv = self._canvas
fill = canv.fillColor
if fill is not None:
S = self._tracker.getState()
text_anchor = S['textAnchor']
fontName = S['fontName']
fontSize = S['fontSize']
text = stringObj.text
x = stringObj.x
y = stringObj.y
if not text_anchor in ['start','inherited']:
textLen = stringWidth(text, fontName,fontSize)
if text_anchor=='end':
x -= textLen
elif text_anchor=='middle':
x -= textLen/2
elif text_anchor=='numeric':
x -= numericXShift(text_anchor,text,textLen,fontName,fontSize,stringObj.encoding)
else:
raise ValueError('bad value for textAnchor '+str(text_anchor))
canv.drawString(x,y,text,_fontInfo=(fontName,fontSize))
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 ValueError as e:
if not e.args[0].endswith("Can't find font!"): raise
#here's where we try to add a font to the canvas
try:
f = getFont(fontName)
_renderPM.makeT1Font(fontName,f.face.findT1File(),f.encoding.vector,open_and_read)
except:
s1, s2 = list(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 _convert2pilL(im):
return im.convert("L")
def _convert2pil1(im):
return im.convert("1")
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.tobytes if hasattr(im,'tobytes') else im.tostring)(),im.im.getpalette())
if not hasattr(fn,'write'):
open(os.path.splitext(fn)[0]+'.'+fmt.lower(),'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))
getattr(im,'frombytes',getattr(im,'fromstring'))(self._gs.pixBuf)
return im
def saveToFile(self,fn,fmt=None):
im = self.toPIL()
if fmt is None:
if not isinstance(fn,str):
raise ValueError("Invalid value '%s' for fn when fmt is None" % ascii(fn))
fmt = os.path.splitext(fn)[1]
if fmt.startswith('.'): fmt = fmt[1:]
configPIL = self.configPIL or {}
configPIL.setdefault('preConvertCB',None)
preConvertCB=configPIL.pop('preConvertCB')
if preConvertCB:
im = preConvertCB(im)
fmt = fmt.upper()
if fmt in ('GIF',):
im = _convert2pilp(im)
elif fmt in ('TIFF','TIFFP','TIFFL','TIF','TIFF1'):
if fmt.endswith('P'):
im = _convert2pilp(im)
elif fmt.endswith('L'):
im = _convert2pilL(im)
elif fmt.endswith('1'):
im = _convert2pil1(im)
fmt='TIFF'
elif fmt in ('PCT','PICT'):
return _saveAsPICT(im,fn,fmt,transparent=configPIL.get('transparent',None))
elif fmt in ('PNG','BMP', 'PPM'):
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'
elif fmt in ('GIF',):
pass
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 isinstance(fn,str): 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 isinstance(fn,str): im.save(fn+'_masked.gif','GIF')
for a,d in ('resolution',self._dpi),('resolution unit','inch'):
configPIL[a] = configPIL.get(a,d)
configPIL.setdefault('chops_invert',0)
if configPIL.pop('chops_invert'):
from PIL import ImageChops
im = ImageChops.invert(im)
configPIL.setdefault('preSaveCB',None)
preSaveCB=configPIL.pop('preSaveCB')
if preSaveCB:
im = preSaveCB(im)
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 = getBytesIO()
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 -= textLen
elif text_anchor=='middle':
x -= textLen/2.
elif text_anchor=='numeric':
x -= numericXShift(text_anchor,text,textLen,self.fontName,self.fontSize)
self.drawString(x,y,text)
def drawRightString(self, text, x, y):
self.drawCentredString(text,x,y,text_anchor='end')
def drawString(self, x, y, text, _fontInfo=None):
gs = self._gs
if _fontInfo:
fontName, fontSize = _fontInfo
else:
fontSize = gs.fontSize
fontName = gs.fontName
try:
gfont=getFont(gs.fontName)
except:
gfont = None
font = getFont(fontName)
if font._dynamicFont:
gs.drawString(x,y,text)
else:
fc = font
if not isUnicode(text):
try:
text = text.decode('utf8')
except UnicodeDecodeError as e:
i,j = e.args[2:4]
raise UnicodeDecodeError(*(e.args[:4]+('%s\n%s-->%s<--%s' % (e.args[4],text[i-10:i],text[i:j],text[j:j+10]),)))
FT = unicode2T1(text,[font]+font.substitutionFonts)
n = len(FT)
nm1 = n-1
for i in range(n):
f, t = FT[i]
if f!=fc:
_setFont(gs,f.fontName,fontSize)
fc = f
gs.drawString(x,y,t)
if i!=nm1:
x += f.stringWidth(t.decode(f.encName),fontSize)
if font!=fc:
_setFont(gs,fontName,fontSize)
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)
alpha = getattr(aColor,'alpha',None)
if alpha is not None:
self.fillOpacity = alpha
def setStrokeColor(self,aColor):
self.strokeColor = Color2Hex(aColor)
alpha = getattr(aColor,'alpha',None)
if alpha is not None:
self.strokeOpacity = alpha
restoreState = saveState
# compatibility routines
def setLineCap(self,cap):
self.lineCap = cap
def setLineJoin(self,join):
self.lineJoin = join
def setLineWidth(self,width):
self.strokeWidth = width
def drawToPMCanvas(d, dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_):
d = renderScaledDrawing(d)
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 = getBytesIO()
drawToFile(d,s,fmt=fmt, dpi=dpi, bg=bg, configPIL=configPIL)
return s.getvalue()
save = drawToFile
def test(outDir='pmout', shout=False):
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
from reportlab.rl_config import verbose
getAllTestDrawings = testshapes.getAllTestDrawings
drawings = []
if not os.path.isdir(outDir):
os.mkdir(outDir)
htmlTop = """<html><head><title>renderPM output results</title></head>
<body>
<h1>renderPM results of output</h1>
"""
htmlBottom = """</body>
</html>
"""
html = [htmlTop]
names = {}
argv = sys.argv[1:]
E = [a for a in argv if a.startswith('--ext=')]
if not E:
E = ['gif','tiff', 'png', 'jpg', 'pct', 'py', 'svg']
else:
for a in E:
argv.remove(a)
E = (','.join([a[6:] for a in E])).split(',')
errs = []
import traceback
from xml.sax.saxutils import escape
def handleError(name,fmt):
msg = 'Problem drawing %s fmt=%s file'%(name,fmt)
if shout or verbose>2: print(msg)
errs.append('<br/><h2 style="color:red">%s</h2>' % msg)
buf = getStringIO()
traceback.print_exc(file=buf)
errs.append('<pre>%s</pre>' % escape(buf.getvalue()))
#print in a loop, with their doc strings
for (drawing, docstring, name) in getAllTestDrawings(doTTF=hasattr(_renderPM,'ft_get_face')):
i = names[name] = names.setdefault(name,0)+1
if i>1: name += '.%02d' % (i-1)
if argv and name not in argv: continue
fnRoot = name
w = int(drawing.width)
h = int(drawing.height)
html.append('<hr><h2>Drawing %s</h2>\n<pre>%s</pre>' % (name, docstring))
for k in E:
if k in ['gif','png','jpg','pct']:
html.append('<p>%s format</p>\n' % k.upper())
try:
filename = '%s.%s' % (fnRoot, ext(k))
fullpath = os.path.join(outDir, 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})
elif k in ['py','svg']:
drawing.save(formats=['py','svg'],outDir=outDir,fnRoot=fnRoot)
else:
drawToFile(drawing,fullpath,fmt=k)
if k in ['gif','png','jpg']:
html.append('<img src="%s" border="1"><br>\n' % filename)
elif k=='py':
html.append('<a href="%s">python source</a><br>\n' % filename)
elif k=='svg':
html.append('<a href="%s">SVG</a><br>\n' % filename)
if shout or verbose>2: print('wrote %s'%ascii(fullpath))
except AttributeError:
handleError(name,k)
if os.environ.get('RL_NOEPSPREVIEW','0')=='1': drawing.__dict__['preview'] = 0
for k in ('eps', 'pdf'):
try:
drawing.save(formats=[k],outDir=outDir,fnRoot=fnRoot)
except:
handleError(name,k)
if errs:
html[0] = html[0].replace('</h1>',' <a href="#errors" style="color: red">(errors)</a></h1>')
html.append('<a name="errors"/>')
html.extend(errs)
html.append(htmlBottom)
htmlFileName = os.path.join(outDir, 'pm-index.html')
open(htmlFileName, 'w').writelines(html)
if sys.platform=='mac':
from reportlab.lib.utils import markfilename
markfilename(htmlFileName,ext='HTML')
if shout or verbose>2: print('wrote %s' % htmlFileName)
if __name__=='__main__':
test(shout=True)

View File

@@ -0,0 +1,930 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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$ '''
__doc__="""Render drawing objects in Postscript"""
from reportlab.pdfbase.pdfmetrics import getFont, stringWidth, unicode2T1 # for font info
from reportlab.lib.utils import getBytesIO, getStringIO, asBytes, char2int, rawBytes, asNative, isUnicode
from reportlab.lib.rl_accel import fp_str
from reportlab.lib.colors import black
from reportlab.graphics.renderbase import Renderer, StateTracker, getStateDelta, renderScaledDrawing
from reportlab.graphics.shapes import STATE_DEFAULTS
import math
from operator import getitem
from reportlab import rl_config
_ESCAPEDICT={}
for c in xrange(256):
if c<32 or c>=127:
_ESCAPEDICT[c]= '\\%03o' % c
elif c in (ord('\\'),ord('('),ord(')')):
_ESCAPEDICT[c] = '\\'+chr(c)
else:
_ESCAPEDICT[c] = chr(c)
del c
def _escape_and_limit(s):
s = asBytes(s)
R = []
aR = R.append
n = 0
for c in s:
c = _ESCAPEDICT[char2int(c)]
aR(c)
n += len(c)
if n>=200:
n = 0
aR('\\\n')
return ''.join(R)
# 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
xtraState = []
self._xtraState_push = xtraState.append
self._xtraState_pop = xtraState.pop
self.comments = 0
self.code = []
self.code_append = self.code.append
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):
if self.comments: 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 self.PostScriptLevel==1:
self._drawImageLevel1(image, x1,y1, x2=None,y2=None)
elif self.PostScriptLevel==2:
self._drawImageLevel2(image, x1,y1, x2=None,y2=None)
else :
raise ValueError('Unsupported Postscript Level %s' % self.PostScriptLevel)
def clear(self):
self.code_append('showpage') # ugh, this makes no sense oh well.
def _t1_re_encode(self):
if not self._fontsUsed: return
# for each font used, reencode the vectors
C = []
for fontName in self._fontsUsed:
fontObj = getFont(fontName)
if not fontObj._dynamicFont and fontObj.encName=='WinAnsiEncoding':
C.append('WinAnsiEncoding /%s /%s RE' % (fontName, fontName))
if C:
C.insert(0,PS_WinAnsiEncoding)
self.code.insert(1, self._sep.join(C))
def save(self,f=None):
if not hasattr(f,'write'):
_f = open(f,'wb')
else:
_f = 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
''' % (self.width,self.height))
self._t1_re_encode()
_f.write(rawBytes(self._sep.join(self.code)))
if _f is not f:
_f.close()
from reportlab.lib.utils import markfilename
markfilename(f,creatorcode='XPR3',filetype='EPSF')
def saveState(self):
self._xtraState_push((self._fontCodeLoc,))
self.code_append('gsave')
def restoreState(self):
self.code_append('grestore')
self._fontCodeLoc, = self._xtraState_pop()
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 isinstance(array,(float,int)):
self.code_append('[%s %s] 0 %s' % (array, phase, psoperation))
elif isinstance(array,(tuple,list)):
assert phase >= 0, "phase is a length in user space"
textarray = ' '.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.setColor(self._strokeColor)
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.
'''
try:
return _escape_and_limit(s)
except:
raise ValueError("cannot escape %s" % ascii(s))
def _issueT1String(self,fontObj,x,y,s):
fc = fontObj
code_append = self.code_append
fontSize = self._fontSize
fontsUsed = self._fontsUsed
escape = self._escape
if not isUnicode(s):
try:
s = s.decode('utf8')
except UnicodeDecodeError as e:
i,j = e.args[2:4]
raise UnicodeDecodeError(*(e.args[:4]+('%s\n%s-->%s<--%s' % (e.args[4],s[i-10:i],s[i:j],s[j:j+10]),)))
for f, t in unicode2T1(s,[fontObj]+fontObj.substitutionFonts):
if f!=fc:
psName = asNative(f.face.name)
code_append('(%s) findfont %s scalefont setfont' % (psName,fp_str(fontSize)))
if psName not in fontsUsed:
fontsUsed.append(psName)
fc = f
code_append('%s m (%s) show ' % (fp_str(x,y),escape(t)))
x += f.stringWidth(t.decode(f.encName),fontSize)
if fontObj!=fc:
self._font = None
self.setFont(fontObj.face.name,fontSize)
def drawString(self, x, y, s, angle=0):
if self._fillColor != None:
fontObj = getFont(self._font)
if not self.code[self._fontCodeLoc]:
psName = asNative(fontObj.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)
if angle!=0:
self.code_append('gsave %s translate %s rotate' % (fp_str(x,y),fp_str(angle)))
x = y = 0
if fontObj._dynamicFont:
s = self._escape(s)
self.code_append('%s m (%s) show ' % (fp_str(x,y),s))
else:
self._issueT1String(fontObj,x,y,s)
if angle!=0:
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 -= textLen
elif text_anchor=='middle':
x -= textLen/2.
elif text_anchor=='numeric':
x -= numericXShift(text_anchor,text,textLen,self._font,self._fontSize)
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???
rr = ['newpath'] # Round Rect code path
a = rr.append
# upper left corner ellipse is first
a(ellipsePath % (x1+rx, y1+ry, rx, -ry, 90, 180))
a(ellipsePath % (x1+rx, y2-ry, rx, -ry, 180, 270))
a(ellipsePath % (x2-rx, y2-ry, rx, -ry, 270, 360))
a(ellipsePath % (x2-rx, y1+ry, rx, -ry, 0, 90) )
a('closepath')
self._fillAndStroke(rr)
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:]
poly = []
a = poly.append
a("%s m" % fp_str(start))
for point in p:
a("%s l" % fp_str(point))
if closed:
a("closepath")
self._fillAndStroke(poly,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 = []
a = figureCode.append
first = 1
for part in partList:
op = part[0]
args = list(part[1:])
if op == figureLine:
if first:
first = 0
a("%s m" % fp_str(args[:2]))
else:
a("%s l" % fp_str(args[:2]))
a("%s l" % fp_str(args[2:]))
elif op == figureArc:
first = 0
x1,y1,x2,y2,startAngle,extent = args[:6]
a(self._genArcCode(x1,y1,x2,y2,startAngle,extent))
elif op == figureCurve:
if first:
first = 0
a("%s m" % fp_str(args[:2]))
else:
a("%s l" % fp_str(args[:2]))
a("%s curveto" % fp_str(args[2:]))
else:
raise TypeError("unknown figure operator: "+op)
if closed:
a("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)
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.tobytes if hasattr(myimage,'tobytes') else myimage.tostring)()
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 asBytes(input):
output.write('%02x' % char2int(char))
return output.getvalue()
def _drawImageLevel2(self, image, x1,y1, x2=None,y2=None): # Postscript Level2 version
'''At present we're handling only PIL'''
### what sort of image are we to draw
if image.mode=='L' :
imBitsPerComponent = 8
imNumComponents = 1
myimage = image
elif image.mode == '1':
myimage = image.convert('L')
imNumComponents = 1
myimage = image
else :
myimage = image.convert('RGB')
imNumComponents = 3
imBitsPerComponent = 8
imwidth, imheight = 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')
# 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.tobytes if hasattr(myimage,'tobytes') else myimage.tostring)()
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 reportlab.graphics.shapes import *
# 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(renderScaledDrawing(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=[]
a = P.append
for i in range(0,len(L),2):
a((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 %r'%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 %r'%node)
self._canvas._color = color
#restore things we might have lost (without actually doing anything).
for k, v in rDeltas.items():
if k in self._restores:
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) and not wedge.annular:
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:
P = wedge.asPolygon()
if isinstance(P,Path):
self.drawPath(P)
else:
self.drawPolygon(P)
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 -= textLen
elif text_anchor=='middle':
x -= textLen/2
elif text_anchor=='numeric':
x -= numericXShift(text_anchor,text,textLen,font,fontSize,encoding='winansi')
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:
if isinstance(value,(list,tuple)) and len(value)==2 and isinstance(value[1],(tuple,list)):
phase = value[0]
value = value[1]
else:
phase = 0
self._canvas.setDash(value,phase)
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 drawImage(self, image):
from reportlab.lib.utils import ImageReader
im = ImageReader(image.path)
x0 = image.x
y0 = image.y
x1 = image.width
if x1 is not None: x1 += x0
y1 = image.height
if y1 is not None: y1 += y0
self._canvas.drawImage(im._image,x0,y0,x1,y1)
def drawToFile(d,fn, showBoundary=rl_config.showBoundary,**kwd):
d = renderScaledDrawing(d)
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 = getBytesIO()
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',shout=False):
from reportlab.graphics import testshapes
from reportlab.rl_config import verbose
OLDFONTS = testshapes._FONTS[:]
testshapes._FONTS[:] = ['Times-Roman','Times-Bold','Times-Italic', 'Times-BoldItalic','Courier']
try:
import os
# save 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
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))
i = 0
for (d, docstring) in drawings:
filename = outDir + os.sep + 'renderPS_%d.eps'%i
drawToFile(d,filename)
if shout or verbose>2: print('renderPS test saved %s' % ascii(filename))
i += 1
finally:
testshapes._FONTS[:] = OLDFONTS
if __name__=='__main__':
import sys
if len(sys.argv)>1:
outdir = sys.argv[1]
else:
outdir = 'epsout'
test(outdir,shout=True)

View File

@@ -0,0 +1,929 @@
__doc__="""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, types, sys, os, codecs
from operator import getitem
from reportlab.pdfbase.pdfmetrics import stringWidth # for font info
from reportlab.lib.rl_accel import fp_str
from reportlab.lib.colors import black
from reportlab.lib.utils import asNative
from reportlab.graphics.renderbase import StateTracker, getStateDelta, Renderer, renderScaledDrawing
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 getBytesIO, RLString, isPy3, isUnicode, isBytes
from xml.dom import getDOMImplementation
### some constants ###
sin = math.sin
cos = math.cos
pi = math.pi
AREA_STYLES = 'stroke-width stroke-linecap stroke stroke-opacity fill fill-opacity stroke-dasharray id'.split()
LINE_STYLES = 'stroke-width stroke-linecap stroke stroke-opacity stroke-dasharray id'.split()
TEXT_STYLES = 'font-family font-weight font-style font-variant font-size id'.split()
### top-level user function ###
def drawToString(d, showBoundary=rl_config.showBoundary,**kwds):
"Returns a SVG as a string in memory, without touching the disk"
s = getBytesIO()
drawToFile(d, s, showBoundary=showBoundary,**kwds)
return s.getvalue()
def drawToFile(d, fn, showBoundary=rl_config.showBoundary,**kwds):
d = renderScaledDrawing(d)
c = SVGCanvas((d.width, d.height),**kwds)
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(renderScaledDrawing(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
class EncodedWriter(list):
'''
EncodedWriter(encoding) assumes .write will be called with
either unicode or utf8 encoded bytes. it will accumulate
unicode
'''
BOMS = {
'utf-32':codecs.BOM_UTF32,
'utf-32-be':codecs.BOM_UTF32_BE,
'utf-32-le':codecs.BOM_UTF32_LE,
'utf-16':codecs.BOM_UTF16,
'utf-16-be':codecs.BOM_UTF16_BE,
'utf-16-le':codecs.BOM_UTF16_LE,
}
def __init__(self,encoding,bom=False):
list.__init__(self)
self.encoding = encoding = codecs.lookup(encoding).name
if bom and '16' in encoding or '32' in encoding:
self.write(self.BOMS[encoding])
def write(self,u):
if isBytes(u):
try:
u = u.decode('utf-8')
except:
et, ev, tb = sys.exc_info()
ev = str(ev)
del et, tb
raise ValueError("String %r not encoded as 'utf-8'\nerror=%s" % (u,ev))
elif not isUnicode(u):
raise ValueError("EncodedWriter.write(%s) argument should be 'utf-8' bytes or str" % ascii(u))
self.append(u)
def getvalue(self):
r = ''.join(self)
del self[:]
return r
### classes ###
class SVGCanvas:
def __init__(self, size=(300,300), encoding='utf-8', verbose=0, bom=False, **kwds):
'''
verbose = 0 >0 means do verbose stuff
useClip = False True means don't use a clipPath definition put the global clip into the clip property
to get around an issue with safari
extraXmlDecl = '' use to add extra xml declarations
scaleGroupId = '' id of an extra group to add around the drawing to allow easy scaling
svgAttrs = {} dictionary of attributes to be applied to the svg tag itself
'''
self.verbose = verbose
self.encoding = codecs.lookup(encoding).name
self.bom = bom
useClip = kwds.pop('useClip',False)
self.fontHacks = kwds.pop('fontHacks',{})
self.extraXmlDecl = kwds.pop('extraXmlDecl','')
scaleGroupId = kwds.pop('scaleGroupId','')
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')
#Based on official example here http://www.w3.org/TR/SVG10/linking.html want:
#<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
# "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
#Thus,
#doctype = implementation.createDocumentType("svg",
# "-//W3C//DTD SVG 20010904//EN",
# "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd")
#
#However, putting that example through http://validator.w3.org/ recommends:
#<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
# "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
#So we'll use that for our SVG 1.0 output.
doctype = implementation.createDocumentType("svg",
"-//W3C//DTD SVG 1.0//EN",
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd")
self.doc = implementation.createDocument(None,"svg",doctype)
self.svg = self.doc.documentElement
svgAttrs = dict(
width = str(size[0]),
height=str(self.height),
preserveAspectRatio="xMinYMin meet",
viewBox="0 0 %d %d" % (self.width, self.height),
#baseProfile = "full", #disliked in V 1.0
#these suggested by Tim Roberts, as updated by peter@maubp.freeserve.co.uk
xmlns="http://www.w3.org/2000/svg",
version="1.0",
)
svgAttrs["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
svgAttrs.update(kwds.pop('svgAttrs',{}))
for k,v in svgAttrs.items():
self.svg.setAttribute(k,v)
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)
if not useClip:
# 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)
gtkw = dict(style="clip-path: url(#clip)")
else:
gtkw = dict(clip="0 0 %d %d" % (self.width,self.height))
self.groupTree = transformNode(self.doc, "g",
id="group",
transform="scale(1,-1) translate(0,-%d)" % self.height,
**gtkw
)
if scaleGroupId:
self.scaleTree = transformNode(self.doc, "g", id=scaleGroupId, transform="scale(1,1)")
self.scaleTree.appendChild(self.groupTree)
self.svg.appendChild(self.scaleTree)
else:
self.svg.appendChild(self.groupTree)
self.currGroup = self.groupTree
def save(self, fn=None):
writer = EncodedWriter(self.encoding,bom=self.bom)
self.doc.writexml(writer,addindent="\t",newl="\n",encoding=self.encoding)
if hasattr(fn,'write'):
f = fn
else:
if isPy3:
f = open(fn, 'w',encoding=self.encoding)
else:
f = open(fn, 'w')
svg = writer.getvalue()
exd = self.extraXmlDecl
if exd:
svg = svg.replace('?>','?>'+exd)
f.write(svg if isPy3 else svg.encode(self.encoding))
if f is not fn:
f.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=[], exclude='',**kwds):
style = self.style.copy()
style.update(kwds)
keys = list(style.keys())
if include:
keys = [k for k in keys if k in include]
if exclude:
exclude = exclude.split()
items = [k+': '+str(style[k]) for k in keys if k not in exclude]
else:
items = [k+': '+str(style[k]) for k in keys]
return '; '.join(items) + ';'
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
"""
return s.replace(chr(0x5C), r'\\' ).replace('(', '\(' ).replace(')', '\)')
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, link_info=None,styles=AREA_STYLES):
path = transformNode(self.doc, "path",
d=self.path, style=self._formatStyle(styles))
if link_info :
path = self._add_link(path, link_info)
self.currGroup.appendChild(path)
self.path = ''
### 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."""
if isinstance(array,(float,int)):
self.style['stroke-dasharray'] = ', '.join(map(str, ([array, phase])))
elif isinstance(array,(tuple,list)) 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)
alpha = color.normalizedAlpha
if alpha!=1:
self.style['stroke-opacity'] = '%s' % alpha
elif 'stroke-opacity' in self.style:
del self.style['stroke-opacity']
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)
alpha = color.normalizedAlpha
if alpha!=1:
self.style['fill-opacity'] = '%s' % alpha
elif 'fill-opacity' in self.style:
del self.style['fill-opacity']
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 = font
self._fontSize = fontSize
style = self.style
for k in TEXT_STYLES:
if k in style:
del style[k]
svgAttrs = self.fontHacks[font] if font in self.fontHacks else {}
if isinstance(font,RLString):
svgAttrs.update(iter(font.svgAttrs.items()))
if svgAttrs:
for k,v in svgAttrs.items():
a = 'font-'+k
if a in TEXT_STYLES:
style[a] = v
if 'font-family' not in style:
style['font-family'] = font
style['font-size'] = '%spx' % fontSize
def _add_link(self, dom_object, link_info) :
assert isinstance(link_info, dict)
link = transformNode(self.doc, "a", **link_info)
link.appendChild(dom_object)
return link
### shapes ###
def rect(self, x1,y1, x2,y2, rx=8, ry=8, link_info=None, **_svgAttrs):
"Draw a rectangle between x1,y1 and x2,y2."
if self.verbose: print("+++ SVGCanvas.rect")
x = min(x1,x2)
y = min(y1,y2)
kwds = {}
rect = transformNode(self.doc, "rect",
x=x, y=y, width=max(x1,x2)-x, height=max(y1,y2)-y,
style=self._formatStyle(AREA_STYLES),**_svgAttrs)
if link_info :
rect = self._add_link(rect, link_info)
self.currGroup.appendChild(rect)
def roundRect(self, x1,y1, x2,y2, rx=8, ry=8, link_info=None, **_svgAttrs):
"""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.
"""
kwds = {}
rect = transformNode(self.doc, "rect",
x=x1, y=y1, width=x2-x1, height=y2-y1, rx=rx, ry=ry,
style=self._formatStyle(AREA_STYLES), **_svgAttrs)
if link_info:
rect = self._add_link(rect, link_info)
self.currGroup.appendChild(rect)
def drawString(self, s, x, y, angle=0, link_info=None,**_svgAttrs):
s = asNative(s)
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),
**_svgAttrs
)
content = self.doc.createTextNode(s)
text.appendChild(content)
if link_info:
text = self._add_link(text, link_info)
self.currGroup.appendChild(text)
def drawCentredString(self, s, x, y, angle=0, text_anchor='middle', link_info=None):
if self.verbose: print("+++ SVGCanvas.drawCentredString")
if self._fillColor != None:
if not text_anchor in ['start', 'inherited']:
textLen = stringWidth(s,self._font,self._fontSize)
if text_anchor=='end':
x -= textLen
elif text_anchor=='middle':
x -= textLen/2.
elif text_anchor=='numeric':
x -= numericXShift(text_anchor,s,textLen,self._font,self._fontSize)
else:
raise ValueError('bad value for text_anchor ' + str(text_anchor))
self.drawString(x,y,text,angle=angle, link_info=link_info)
def drawRightString(self, text, x, y, angle=0, link_info=None):
self.drawCentredString(text,x,y,angle=angle,text_anchor='end', link_info=link_info)
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, link_info=None):
"""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(AREA_STYLES))
if link_info:
ellipse = self._add_link(ellipse, link_info)
self.currGroup.appendChild(ellipse)
def circle(self, xc, yc, r, link_info=None):
circle = transformNode(self.doc, "circle",
cx=xc, cy=yc, r=r,
style=self._formatStyle(AREA_STYLES))
if link_info:
circle = self._add_link(circle, link_info)
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, link_info=None):
assert len(points) >= 2, 'Polygon must have 2 or more points'
if self._strokeColor != None:
self.setColor(self._strokeColor)
pairs = []
for i in range(len(points)):
pairs.append("%f %f" % (points[i]))
pts = ', '.join(pairs)
polyline = transformNode(self.doc, "polygon",
points=pts, style=self._formatStyle(AREA_STYLES))
if link_info:
polyline = self._add_link(polyline, link_info)
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 range(len(points)):
pairs.append("%f %f" % (points[i]))
pts = ', '.join(pairs)
polyline = transformNode(self.doc, "polyline",
points=pts, style=self._formatStyle(AREA_STYLES,fill=None))
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(%r)" % node)
self._canvas.comment('begin node %r'%node)
color = self._canvas._color
style = self._canvas.style.copy()
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 %r'%node)
self._canvas._color = color
#restore things we might have lost (without actually doing anything).
for k, v in rDeltas.items():
if k in self._restores:
setattr(self._canvas,self._restores[k],v)
self._canvas.style = style
if self.verbose: print("### end _SVGRenderer.drawNode(%r)" % node)
_restores = {'strokeColor':'_strokeColor','strokeWidth': '_lineWidth','strokeLineCap':'_lineCap',
'strokeLineJoin':'_lineJoin','fillColor':'_fillColor','fontName':'_font',
'fontSize':'_fontSize'}
def _get_link_info_dict(self, obj):
#We do not want None or False as the link, even if it is the
#attribute's value - use the empty string instead.
url = getattr(obj, "hrefURL", "") or ""
title = getattr(obj, "hrefTitle", "") or ""
if url :
#Is it valid to have a link with no href? The XML requires
#the xlink:href to be present, but you might just want a
#tool tip shown (via the xlink:title attribute). Note that
#giving an href of "" is equivalent to "the current page"
#(a relative link saying go nowhere).
return {"xlink:href":url, "xlink:title":title, "target":"_top"}
#Currently of all the mainstream browsers I have tested, only Safari/webkit
#will show SVG images embedded in HTML using a simple <img src="..." /> tag.
#However, the links don't work (Safari 3.2.1 on the Mac).
#
#Therefore I use the following, which also works for Firefox, Opera, and
#IE 6.0 with Adobe SVG Viewer 6 beta:
#<object data="..." type="image/svg+xml" width="430" height="150" class="img">
#
#Once displayed, Firefox and Safari treat the SVG like a frame, and
#by default clicking on links acts "in frame" and replaces the image.
#Opera does what I expect, and replaces the whole page with the link.
#
#Therefore I use target="_top" to force the links to replace the whole page.
#This now works as expected on Safari 3.2.1, Firefox 3.0.6, Opera 9.20.
#Perhaps the target attribute should be an option, perhaps defaulting to
#"_top" as used here?
else :
return None
def drawGroup(self, group):
if self.verbose: print("### begin _SVGRenderer.drawGroup")
currGroup = self._canvas.startGroup()
a, b, c, d, e, f = self._tracker.getState()['transform']
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):
link_info = self._get_link_info_dict(rect)
svgAttrs = getattr(rect,'_svgAttrs',{})
if rect.rx == rect.ry == 0:
#plain old rectangle
self._canvas.rect(
rect.x, rect.y,
rect.x+rect.width, rect.y+rect.height, link_info=link_info, **svgAttrs)
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,
link_info=link_info, **svgAttrs)
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 -= textLen
elif text_anchor=='middle':
x -= textLen/2
elif text_anchor=='numeric':
x -= numericXShift(text_anchor,text,textLen,font,fontSize)
else:
raise ValueError('bad value for text_anchor ' + str(text_anchor))
self._canvas.drawString(text,x,y,link_info=self._get_link_info_dict(stringObj),**getattr(stringObj,'_svgAttrs',{}))
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, link_info=self._get_link_info_dict(circle))
def drawWedge(self, wedge):
yradius, radius1, yradius1 = wedge._xtraRadii()
if (radius1==0 or radius1 is None) and (yradius1==0 or yradius1 is None) and not wedge.annular:
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)
else:
P = wedge.asPolygon()
if isinstance(P,Path):
self.drawPath(P)
else:
self.drawPolygon(P)
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, link_info=self._get_link_info_dict(ellipse))
def drawPolygon(self, p):
self._canvas.polygon(_pointsFromList(p.points), closed=1, link_info=self._get_link_info_dict(p))
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 isClosed:
#Only try and add links to closed paths...
link_info = self._get_link_info_dict(path)
else :
c._fillColor = None
link_info = None
c._fillAndStroke([], clip=path.isClipPath, link_info=link_info)
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:
if isinstance(value,(list,tuple)) and len(value)==2 and isinstance(value[1],(tuple,list)):
phase = value[0]
value = value[1]
else:
phase = 0
self._canvas.setDash(value,phase)
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 test(outDir='out-svg'):
# 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))
i = 0
for (d, docstring) in drawings:
filename = os.path.join(outDir,'renderSVG_%d.svg' % i)
drawToFile(d, filename)
i += 1
from reportlab.graphics.testshapes import getDrawing01
d = getDrawing01()
drawToFile(d, os.path.join(outDir,"test.svg"))
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, os.path.join(outDir,"corplogo.svg"))
if __name__=='__main__':
test()

View File

@@ -0,0 +1,357 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/renderbase.py
__version__=''' $Id $ '''
__doc__='''Superclass for renderers to factor out common functionality and default implementations.'''
from reportlab.graphics.shapes import *
from reportlab.lib.validators import DerivedValue
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 prop in STATE_DEFAULTS:
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 'transform' in defaults:
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]}
from reportlab.graphics.shapes import _baseGFontName
deltas = [
{'fillColor':'red'},
{'fillColor':'green', 'strokeColor':'blue','fontName':_baseGFontName},
{'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
def renderScaledDrawing(d):
renderScale = d.renderScale
if renderScale!=1.0:
o = d
d = d.__class__(o.width*renderScale,o.height*renderScale)
d.__dict__ = o.__dict__.copy()
d.scale(renderScale,renderScale)
d.renderScale = 1.0
return d
class Renderer:
"""Virtual superclass for graphics renderers."""
def __init__(self):
self._tracker = StateTracker()
self._nodeStack = [] #track nodes visited
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) #this is the push()
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 getStateValue(self, key):
"""Return current state parameter for given key"""
currentState = self._tracker._combined[-1]
return currentState[key]
def fillDerivedValues(self, node):
"""Examine a node for any values which are Derived,
and replace them with their calculated values.
Generally things may look at the drawing or their
parent.
"""
for key, value in node.__dict__.items():
if isinstance(value, DerivedValue):
#just replace with default for key?
#print ' fillDerivedValues(%s)' % key
newValue = value.getValue(self, key)
#print ' got value of %s' % newValue
node.__dict__[key] = newValue
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 not node: return
if hasattr(node,'_canvas'):
ocanvas = 1
else:
node._canvas = canvas
ocanvas = None
self.fillDerivedValues(node)
dtcb = getattr(node,'_drawTimeCallback',None)
if dtcb:
dtcb(node,canvas=canvas,renderer=self)
#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)
if not node: continue
#here is where we do derived values - this seems to get everything. Touch wood.
self.fillDerivedValues(node)
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"
P = wedge.asPolygon()
if isinstance(P,Path):
self.drawPath(P)
else:
self.drawPolygon(P)
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__)

View File

@@ -0,0 +1 @@
__doc__="""Example drawings to review, used in autogenerated docs"""

View File

@@ -0,0 +1,73 @@
#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 reportlab.graphics.samples.excelcolors import *
class Bubble(_DrawingEditorMixin,Drawing):
def __init__(self,width=200,height=150,*args,**kw):
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')

View File

@@ -0,0 +1,84 @@
#Autogenerated by ReportLab guiedit do not edit
from reportlab.graphics.charts.legends import Legend
from reportlab.graphics.samples.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):
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')

View File

@@ -0,0 +1,83 @@
#Autogenerated by ReportLab guiedit do not edit
from reportlab.graphics.charts.legends import Legend
from reportlab.graphics.samples.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):
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')

View File

@@ -0,0 +1,45 @@
# 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)

View File

@@ -0,0 +1,65 @@
#Autogenerated by ReportLab guiedit do not edit
from reportlab.graphics.charts.piecharts import Pie
from reportlab.graphics.samples.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):
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')

View File

@@ -0,0 +1,54 @@
#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 reportlab.graphics.samples.excelcolors import *
class FilledRadarChart(_DrawingEditorMixin,Drawing):
def __init__(self,width=200,height=150,*args,**kw):
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.strandLabels.fontName = 'Helvetica'
self.chart.strandLabels.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')

View File

@@ -0,0 +1,83 @@
#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 reportlab.graphics.samples.excelcolors import *
class LineChart(_DrawingEditorMixin,Drawing):
def __init__(self,width=200,height=150,*args,**kw):
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')

View File

@@ -0,0 +1,94 @@
#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 reportlab.graphics.samples.excelcolors import *
class LineChartWithMarkers(_DrawingEditorMixin,Drawing):
def __init__(self,width=200,height=150,*args,**kw):
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')

View File

@@ -0,0 +1,66 @@
#Autogenerated by ReportLab guiedit do not edit
from reportlab.graphics.charts.legends import Legend
from reportlab.graphics.samples.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):
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.strandLabels.fontName = 'Helvetica'
self.chart.strandLabels.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')

View File

@@ -0,0 +1,58 @@
# runs all the GUIedit charts in this directory -
# makes a PDF sample for eaxh existing chart type
import sys
import glob
import inspect
import types
def moduleClasses(mod):
def P(obj, m=mod.__name__, CT=type):
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 = format.split( ',')
for i in range(0, len(formats)):
formats[i] == formats[i].strip().lower()
allfiles = glob.glob('*.py')
allfiles.sort()
for fn in allfiles:
f = fn.split('.')[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

View File

@@ -0,0 +1,71 @@
#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 reportlab.graphics.samples.excelcolors import *
class Scatter(_DrawingEditorMixin,Drawing):
def __init__(self,width=200,height=150,*args,**kw):
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')

View File

@@ -0,0 +1,82 @@
#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 reportlab.graphics.samples.excelcolors import *
class ScatterLines(_DrawingEditorMixin,Drawing):
def __init__(self,width=200,height=150,*args,**kw):
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')

View File

@@ -0,0 +1,72 @@
#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 reportlab.graphics.samples.excelcolors import *
class ScatterLinesMarkers(_DrawingEditorMixin,Drawing):
def __init__(self,width=200,height=150,*args,**kw):
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')

View File

@@ -0,0 +1,61 @@
#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 reportlab.graphics.samples.excelcolors import *
class SimplePie(_DrawingEditorMixin,Drawing):
def __init__(self,width=200,height=150,*args,**kw):
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)

View File

@@ -0,0 +1,85 @@
#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 reportlab.graphics.samples.excelcolors import *
class StackedBar(_DrawingEditorMixin,Drawing):
def __init__(self,width=200,height=150,*args,**kw):
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')

View File

@@ -0,0 +1,84 @@
#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 reportlab.graphics.samples.excelcolors import *
class StackedColumn(_DrawingEditorMixin,Drawing):
def __init__(self,width=200,height=150,*args,**kw):
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')

1467
reportlab/graphics/shapes.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,296 @@
#!/bin/env python
#Copyright ReportLab Europe Ltd. 2000-2012
#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 $ '''
__doc__="""Defines some standard drawings to use as test cases
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__)

View File

@@ -0,0 +1,580 @@
#!/bin/env python
#Copyright ReportLab Europe Ltd. 2000-2012
#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.
__version__ = ''' $Id $ '''
__doc__='''Execute this 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.
'''
import os, sys
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.lib.utils import asNative
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','Vera','Times-BoldItalic',]
def _setup():
from reportlab.pdfbase import pdfmetrics, ttfonts
pdfmetrics.registerFont(ttfonts.TTFont("Vera", "Vera.ttf"))
pdfmetrics.registerFont(ttfonts.TTFont("VeraBd", "VeraBd.ttf"))
pdfmetrics.registerFont(ttfonts.TTFont("VeraIt", "VeraIt.ttf"))
pdfmetrics.registerFont(ttfonts.TTFont("VeraBI", "VeraBI.ttf"))
F = ['Times-Roman','Courier','Helvetica','Vera', 'VeraBd', 'VeraIt', 'VeraBI']
if sys.platform=='win32':
for name, ttf in [
('Adventurer Light SF','Advlit.ttf'),('ArialMS','ARIAL.TTF'),
('Arial Unicode MS', 'ARIALUNI.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
return F
def resetFonts():
for f in _setup():
if f not in _FONTS:
_FONTS.append(f)
from reportlab.rl_config import register_reset
register_reset(resetFonts)
resetFonts()
#########################################################
#
# 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))
D.add(String(180,86, b'Special characters \xc2\xa2\xc2\xa9\xc2\xae\xc2\xa3\xce\xb1\xce\xb2', 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 green fill.
"""
green = colors.green
D = Drawing(400, 200) #, fillColor=green)
D.add(Line(10,10, 390,190))
D.add(Circle(100,100,20, fillColor=green))
D.add(Circle(200,100,40, fillColor=green))
D.add(Circle(300,100,30, fillColor=green))
D.add(Wedge(330,100,40, -10,40, fillColor=green))
D.add(PolyLine([120,10, 130,20, 140,10, 150,20, 160,10,
170,20, 180,10, 190,20, 200,10], fillColor=green))
D.add(Polygon([300,20, 350,20, 390,80, 300,75, 330,40], fillColor=green))
D.add(Ellipse(50,150, 40, 20, fillColor=green))
D.add(Rect(120,150, 60,30,
strokeWidth=10,
strokeColor=colors.yellow,
fillColor=green)) #square corners
D.add(Rect(220, 150, 60, 30, 10, 10, fillColor=green)) #round corners
D.add(String(10,50, 'Basic Shapes', fillColor=colors.black, fontName='Helvetica'))
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 reportlab.graphics.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 = "DarkGardenMK"
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'
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+asNative(b': I should be totally horizontal and enclosed in a box and end in alphabetagamma \xc2\xa2\xc2\xa9\xc2\xae\xc2\xa3\xca\xa5\xd0\x96\xd6\x83\xd7\x90\xd9\x82\xe0\xa6\x95\xce\xb1\xce\xb2\xce\xb3')
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(_FONTS)
if maxx>400 or maxy>200: _,_,D = drawit(_FONTS,maxx,maxy)
return D
##def getDrawing14():
## """This tests inherited properties. Each font should be as it says."""
## D = Drawing(400, 200)
##
## fontSize = 12
## D.fontName = 'Courier'
##
## g1 = Group(
## Rect(0, 0, 150, 20, fillColor=colors.yellow),
## String(5, 5, 'Inherited Courier', fontName=inherit, 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
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 = list(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())

View File

@@ -0,0 +1,529 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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$ '''
__doc__='''Base class for user-defined graphical widgets'''
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 key in self._attrMap, 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 = name.split('.', 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 = list(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 k not in self.__dict__:
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()
class ScaleWidget(Widget):
'''Contents with a scale and offset'''
_attrMap = AttrMap(
x = AttrMapValue(isNumber,desc="x offset"),
y = AttrMapValue(isNumber,desc="y offset"),
scale = AttrMapValue(isNumber,desc="scale"),
contents = AttrMapValue(None,desc="Contained drawable elements"),
)
def __init__(self,x=0,y=0,scale=1.0,contents=None):
self.x = x
self.y = y
if not contents: contents=[]
elif not isinstance(contents,(tuple,list)):
contents = (contents,)
self.contents = list(contents)
self.scale = scale
def draw(self):
return shapes.Group(transform=(self.scale,0,0,self.scale,self.x,self.y),*self.contents)
_ItemWrapper={}
class CloneMixin:
def clone(self,**kwds):
n = self.__class__()
n.__dict__.clear()
n.__dict__.update(self.__dict__)
if kwds: n.__dict__.update(kwds)
return n
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.
We try and make sensible use of tuple indeces.
line[(3,x)] is backed by line[(3,)], line[3] & line
"""
def __init__(self, exampleClass):
#give it same validation rules as what it holds
self.__dict__['_value'] = exampleClass()
self.__dict__['_children'] = {}
def wKlassFactory(self,Klass):
class WKlass(Klass,CloneMixin):
def __getattr__(self,name):
try:
return self.__class__.__bases__[0].__getattr__(self,name)
except:
i = self._index
if i:
c = self._parent._children
if i in c and name in c[i].__dict__:
return getattr(c[i],name)
elif len(i)==1:
i = i[0]
if i in c and name in c[i].__dict__:
return getattr(c[i],name)
return getattr(self._parent,name)
return WKlass
def __getitem__(self, index):
try:
return self._children[index]
except KeyError:
Klass = self._value.__class__
if Klass in _ItemWrapper:
WKlass = _ItemWrapper[Klass]
else:
_ItemWrapper[Klass] = WKlass = self.wKlassFactory(Klass)
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=list(child.__dict__.keys()): x in K,list(child._attrMap.keys())):
del child.__dict__[i]
self._children[index] = child
return child
def __contains__(self,key):
if type(key) in (type(()),type([])): key = tuple(key)
return key in self._children
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(list(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 range(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,desc='width of the stroke line'),
strokeLineCap = AttrMapValue(isNumber,desc='Line cap 0=butt, 1=round & 2=square',advancedUsage=1),
strokeLineJoin = AttrMapValue(isNumber,desc='Line join 0=miter, 1=round & 2=bevel',advancedUsage=1),
strokeMiterLimit = AttrMapValue(None,desc='miter limit control miter line joins',advancedUsage=1),
strokeDashArray = AttrMapValue(isListOfNumbersOrNone,desc='dashing patterns e.g. (1,3)'),
strokeOpacity = AttrMapValue(isNumber,desc='level of transparency (alpha) accepts values between 0..1',advancedUsage=1),
strokeColor = AttrMapValue(isColorOrNone,desc='the color of the stroke'),
fillColor = AttrMapValue(isColorOrNone,desc='the filling color'),
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 list(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)
from reportlab.graphics 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()

View File

@@ -0,0 +1,5 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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$ '''
__doc__='''Some non-chart widgets'''

View File

@@ -0,0 +1,304 @@
#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
__version__=''' $Id$ '''
__doc__="""This file is a
"""
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 &amp; 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()

View File

@@ -0,0 +1,880 @@
#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)
__version__=''' $Id$ '''
__doc__="""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]).
"""
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 reportlab.graphics.widgets.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 range(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 [x for x in self._attrMap['kind'].validate._enum if x is not None]
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.0),(fx.y-(1.2*labelFontSize)),
name, fillColor=colors.black, textAnchor='middle',
fontSize=labelFontSize))
labelFontSize = int(fx.size/4.0)
D.add(String(fx.x+(fx.size),(fx.y+((fx.size/2.0))),
"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.0), (s-((s/10.0)*4)), (s*0.65), (s-(s/10.0)*3), (s*0.65), 0, s], fillColor = colors.red, strokeColor = None, strokeWidth=0))
g.add(Polygon([0, 0, (s-((s/10.0)*3)), (s*0.35), (s-((s/10.0)*2)), (s*0.35), (s/10.0), 0], fillColor = colors.red, strokeColor = None, strokeWidth=0))
g.add(Polygon([w, s, (s+((s/10.0)*3)), (s*0.65), (s+((s/10.0)*2)), (s*0.65), w-(s/10.0), s], fillColor = colors.red, strokeColor = None, strokeWidth=0))
g.add(Polygon([w, (s/15.0), (s+((s/10.0)*4)), (s*0.35), (s+((s/10.0)*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.0
s9 = s/9.0
s7 = s/7.0
for starxcounter in range(5):
for starycounter in range(4):
ls = Star()
ls.size = lss
ls.x = 0-s/22.0+lss/2.0+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.0)+lss/2.0+s/14.0+starxcounter*s7
ls.fillColor = colors.mintcream
ls.y = s-(starycounter+1)*s9+(s/18.0)+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.0,x0=0,y0=s/2.0):
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.0, width=s*2, height=s/5.0,
fillColor = [colors.darkblue, colors.mintcream][i%2],
strokeColor = None,
strokeWidth=0)
g.add(stripe)
redwedge = Polygon(points = [ 0, 0, 4*s/5.0, (s/2.0), 0, s],
fillColor = colors.red, strokeColor = None, strokeWidth=0)
g.add(redwedge)
star = Star()
star.x = 2.5*s/10.0
star.y = s/2.0
star.size = 3*s/10.0
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.0)*2), 0, width=s/6.0, height=s,
fillColor = colors.mintcream, strokeColor = None, strokeWidth=0)
g.add(whitebox1)
whitebox2 = Rect(0, ((s/2.0)-(s/12.0)), width=w, height=s/6.0,
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, 0, width=(s*4)/3.0, height=s, fillColor = colors.mintcream, strokeColor = None, strokeWidth=0))
g.add(Rect((4*s)/3.0, 0, width=(s*2)/3.0, 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.0,cy=s/2.0,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.0
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.0), width=w, height=s/2.0, 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.0)*2), 0, width=s/6.0, height=s,
fillColor = colors.gold, strokeColor = None, strokeWidth=0)
g.add(box1)
box2 = Rect(0, ((s/2.0)-(s/12.0)), width=self._width, height=s/6.0,
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.0,
fillColor = colors.red,
strokeColor = None,
strokeWidth=0)
g.add(redbox)
bluewedge = Polygon(points = [ 0, 0, s, (s/2.0), 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.0, s*2, s/3.0,
fillColor = colors.mintcream,
strokeColor = None,
strokeWidth=0)
g.add(box)
greenbox = Rect(0, 0, width=s*2, height=s/3.0,
fillColor = colors.limegreen,
strokeColor = None,
strokeWidth=0)
g.add(greenbox)
blackbox = Rect(0, 2*s/3.0, width=s*2, height=s/3.0,
fillColor = colors.black,
strokeColor = None,
strokeWidth=0)
g.add(blackbox)
redwedge = Polygon(points = [ 0, 0, 2*s/3.0, (s/2.0), 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.0, r=s*0.3,
fillColor = colors.mintcream,
strokeColor = None,
strokeWidth=0)
g.add(whitecircle)
redcircle = Circle(cx=((s*0.39)*2), cy=s/2.0, r=s*0.24,
fillColor = colors.red,
strokeColor = None,
strokeWidth=0)
g.add(redcircle)
ws = Star()
ws.angle = 15
ws.size = s/5.0
ws.x = (s*0.5)*2+ws.size/2.0
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.0), (s/5.5), (s/2), (s-(s/5.5)),
fillColor = colors.mintcream, strokeColor = colors.mintcream, strokeWidth=(s/5.0)))
g.add(Line((s/5.5), (s/2.0), (s-(s/5.5)), (s/2.0),
fillColor = colors.mintcream, strokeColor = colors.mintcream, strokeWidth=s/5.0))
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.0
centery=s/2.0
radius=s/3.0
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.0
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.0
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.0) + (x * (0.35 * m))
st.y = (s/2.0) + (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.0), (w/2.0), s-(1.7*m), w-(1.7*m),(s/2.0),(w/2.0), 1.7*m],
fillColor = colors.yellow, strokeColor = None, strokeWidth=0))
g.add(Circle(cx=w/2.0, cy=s/2.0, r=3.5*m,
fillColor=colors.blue,strokeColor=None, strokeWidth=0))
g.add(Wedge((w/2.0)-(2*m), 0, 8.5*m, 50, 98.1, 8.5*m,
fillColor=colors.mintcream,strokeColor=None, strokeWidth=0))
g.add(Wedge((w/2.0), (s/2.0), 3.501*m, 156, 352, 3.501*m,
fillColor=colors.mintcream,strokeColor=None, strokeWidth=0))
g.add(Wedge((w/2.0)-(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.0) + 1.7*m,
fillColor = colors.green, strokeColor = None, strokeWidth=0))
g.add(Polygon(points = [ 1.7*m,(s/2.0), (w/2.0),s/2.0 - 2*m, w-(1.7*m),(s/2.0) , (w/2.0),1.7*m],
fillColor = colors.yellow, strokeColor = None, strokeWidth=0))
g.add(Wedge(w/2.0, s/2.0, 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.0)-(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.0),(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()

View File

@@ -0,0 +1,519 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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, isNumberOrNone
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(isNumberOrNone, 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 = int((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'''
cx = 0
cy = 0
for x,y in P:
cx+=x
cy+=y
n = float(len(P))
return cx/n, cy/n
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(xy,s=s,c=c,x0=x0,y0=y0):
x,y = xy
return (s*(y-y0)+c*(x-x0))
def orthogonalAxisDist(xy,s=s,c=c,x0=x0,y0=y0):
x,y = xy
return (c*(y-y0)+s*(x-x0))
L = list(map(parallelAxisDist,P))
L.sort()
a0, a1 = L[0], L[-1]
L = list(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 = list(map(lambda i, P=P:(P[i],P[i+1]),range(0,len(P),2)))
path = definePath([('moveTo',)+P[0]]+[('lineTo',)+x for x in P[1:]]+['closePath'],
fillColor=None, strokeColor=None)
path.isClipPath = 1
g = Group()
g.add(path)
angle = self.angle
orientation = 'vertical'
if angle==180:
angle = 0
elif angle in (90,270):
orientation ='horizontal'
angle = 0
rect = ShadedRect(strokeWidth=0,strokeColor=None,orientation=orientation)
for k in 'fillColorStart', 'fillColorEnd', 'numShades', 'cylinderMode':
setattr(rect,k,getattr(self,k))
g.add(rotatedEnclosingRect(P, 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')

View File

@@ -0,0 +1,245 @@
#Copyright ReportLab Europe Ltd. 2000-2013
#see license.txt for license details
__version__=''' $Id$ '''
__doc__="""This modules defines a collection of markers used in charts.
"""
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.lib.utils import isFunction, isClass
from reportlab.graphics.widgets.flags import Flag
from math import sin, cos, pi
_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','ArrowHead', 'FilledArrowHead'),
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'),
arrowBarbDx = AttrMapValue(isNumber, desc='arrow only the delta x for the barbs'),
arrowHeight = AttrMapValue(isNumber, desc='arrow only height'),
)
def __init__(self,*args,**kw):
self.setProperties(kw)
self._setKeywords(
kind = None,
strokeColor = black,
strokeWidth = 0.1,
fillColor = None,
size = 5,
x = 0,
y = 0,
dx = 0,
dy = 0,
angle = 0,
arrowBarbDx = -1.25,
arrowHeight = 1.875,
)
def clone(self,**kwds):
n = self.__class__(**self.__dict__)
if kwds: n.__dict__.update(kwds)
return n
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 range(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 _ArrowHead(self):
s = self.size
h = self.arrowHeight
b = self.arrowBarbDx
return self._doPolygon((0,0,b,-h,s,0,b,h))
def _doPolygon(self,P):
x, y = self.x+self.dx, self.y+self.dy
if x or y: P = list(map(lambda i,P=P,A=[x,y]: P[i] + A[i&1], list(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 range(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
_FilledArrowHead = _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 isFunction(uSymbol):
symbol = uSymbol(x, y, 5, color)
elif isClass(uSymbol) 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 hasattr(x,'__call__') or isinstance(x,Marker) or isinstance(x,Flag) or (isinstance(x,type) and issubclass(x,Widget))
isSymbol = _isSymbol()
def makeMarker(name,**kw):
if Marker._attrMap['kind'].validate(name):
m = Marker(**kw)
m.kind = name
elif name[-5:]=='_Flag' and Flag._attrMap['kind'].validate(name[:-5]):
m = 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')

View File

@@ -0,0 +1,933 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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)
__version__=''' $Id$ '''
__doc__="""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
"""
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
self.strokeWidth = 0
self.strokeColor = None
def draw(self):
# general widget bits
s = float(self.size) # abbreviate as we will use this a lot
g = shapes.Group()
x = self.x
y = self.y
s2 = s/2
s3 = s/3
s5 = s/5
g.add(shapes.Polygon(points = [
x,y+s3,
x,y+2*s3,
x+s2,y+2*s3,
x+s2,y+4*s5,
x+s,y+s2,
x+s2,y+s5,
x+s2,y+s3,
],
fillColor = self.fillColor,
strokeColor = self.strokeColor,
strokeWidth = self.strokeWidth,
)
)
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
self.strokeWidth = 0
self.strokeColor = None
def draw(self):
# general widget bits
s = float(self.size) # abbreviate as we will use this a lot
g = shapes.Group()
# arrow specific bits
x = self.x
y = self.y
s2 = s/2
s3 = s/3
s5 = s/5
s24 = s/24
g.add(shapes.Polygon(
points = [
x,y+11*s24,
x,y+13*s24,
x+18.75*s24, y+13*s24,
x+2*s3, y+2*s3,
x+s, y+s2,
x+2*s3, y+s3,
x+18.75*s24, y+11*s24,
],
fillColor = self.fillColor,
strokeColor = self.strokeColor,
strokeWidth = self.strokeWidth)
)
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()

View File

@@ -0,0 +1,161 @@
#!/usr/bin/env python
#Copyright ReportLab Europe Ltd. 2000-2012
#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.graphics.widgetbase import Widget
from reportlab.graphics.charts.textlabels import Label
from reportlab.graphics import shapes
from reportlab.lib import colors
from reportlab.lib.validators import *
from reportlab.lib.attrmap import *
from reportlab.graphics.shapes import Drawing
class TableWidget(Widget):
"""A two dimensions table of labels
"""
_attrMap = AttrMap(
x = AttrMapValue(isNumber, desc="x position of left edge of table"),
y = AttrMapValue(isNumber, desc="y position of bottom edge of table"),
width = AttrMapValue(isNumber, desc="table width"),
height = AttrMapValue(isNumber, desc="table height"),
borderStrokeColor = AttrMapValue(isColorOrNone, desc="table border color"),
fillColor = AttrMapValue(isColorOrNone, desc="table fill color"),
borderStrokeWidth = AttrMapValue(isNumber, desc="border line width"),
horizontalDividerStrokeColor = AttrMapValue(isColorOrNone, desc="table inner horizontal lines color"),
verticalDividerStrokeColor = AttrMapValue(isColorOrNone, desc="table inner vertical lines color"),
horizontalDividerStrokeWidth = AttrMapValue(isNumber, desc="table inner horizontal lines width"),
verticalDividerStrokeWidth = AttrMapValue(isNumber, desc="table inner vertical lines width"),
dividerDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array for dividerLines.'),
data = AttrMapValue(None, desc="a list of list of strings to be displayed in the cells"),
boxAnchor = AttrMapValue(isBoxAnchor, desc="location of the table anchoring point"),
fontName = AttrMapValue(isString, desc="text font in the table"),
fontSize = AttrMapValue(isNumber, desc="font size of the table"),
fontColor = AttrMapValue(isColorOrNone, desc="font color"),
alignment = AttrMapValue(OneOf("left", "right"), desc="Alignment of text within cells"),
textAnchor = AttrMapValue(OneOf('start','middle','end','numeric'), desc="Alignment of text within cells"),
)
def __init__(self, x=10, y=10, **kw):
self.x = x
self.y = y
self.width = 200
self.height = 100
self.borderStrokeColor = colors.black
self.fillColor = None
self.borderStrokeWidth = 0.5
self.horizontalDividerStrokeColor = colors.black
self.verticalDividerStrokeColor = colors.black
self.horizontalDividerStrokeWidth = 0.5
self.verticalDividerStrokeWidth = 0.25
self.dividerDashArray = None
self.data = [['North','South','East','West'],[100,110,120,130],['A','B','C','D']] # list of rows each row is a list of columns
self.boxAnchor = 'nw'
#self.fontName = None
self.fontSize = 8
self.fontColor = colors.black
self.alignment = 'right'
self.textAnchor = 'start'
for k, v in kw.items():
if k in list(self.__class__._attrMap.keys()):
setattr(self, k, v)
else:
raise ValueError('invalid argument supplied for class %s'%self.__class__)
def demo(self):
""" returns a sample of this widget with data
"""
d = Drawing(400, 200)
t = TableWidget()
d.add(t, name='table')
d.table.dividerDashArray = (1, 3, 2)
d.table.verticalDividerStrokeColor = None
d.table.borderStrokeWidth = 0
d.table.borderStrokeColor = colors.red
return d
def draw(self):
""" returns a group of shapes
"""
g = shapes.Group()
#overall border and fill
if self.borderStrokeColor or self.fillColor: # adds border and filling color
rect = shapes.Rect(self.x, self.y, self.width, self.height)
rect.fillColor = self.fillColor
rect.strokeColor = self.borderStrokeColor
rect.strokeWidth = self.borderStrokeWidth
g.add(rect)
#special case - for an empty table we want to avoid divide-by-zero
data = self.preProcessData(self.data)
rows = len(self.data)
cols = len(self.data[0])
#print "(rows,cols)=(%s, %s)"%(rows,cols)
row_step = self.height / float(rows)
col_step = self.width / float(cols)
#print "(row_step,col_step)=(%s, %s)"%(row_step,col_step)
# draw the grid
if self.horizontalDividerStrokeColor:
for i in range(rows): # make horizontal lines
x1 = self.x
x2 = self.x + self.width
y = self.y + row_step*i
#print 'line (%s, %s), (%s, %s)'%(x1, y, x2, y)
line = shapes.Line(x1, y, x2, y)
line.strokeDashArray = self.dividerDashArray
line.strokeWidth = self.horizontalDividerStrokeWidth
line.strokeColor = self.horizontalDividerStrokeColor
g.add(line)
if self.verticalDividerStrokeColor:
for i in range(cols): # make vertical lines
x = self.x+col_step*i
y1 = self.y
y2 = self.y + self.height
#print 'line (%s, %s), (%s, %s)'%(x, y1, x, y2)
line = shapes.Line(x, y1, x, y2)
line.strokeDashArray = self.dividerDashArray
line.strokeWidth = self.verticalDividerStrokeWidth
line.strokeColor = self.verticalDividerStrokeColor
g.add(line)
# since we plot data from down up, we reverse the list
self.data.reverse()
for (j, row) in enumerate(self.data):
y = self.y + j*row_step + 0.5*row_step - 0.5 * self.fontSize
for (i, datum) in enumerate(row):
if datum:
x = self.x + i*col_step + 0.5*col_step
s = shapes.String(x, y, str(datum), textAnchor=self.textAnchor)
s.fontName = self.fontName
s.fontSize = self.fontSize
s.fillColor = self.fontColor
g.add(s)
return g
def preProcessData(self, data):
"""preprocess and return a new array with at least one row
and column (use a None) if needed, and all rows the same
length (adding Nones if needed)
"""
if not data:
return [[None]]
#make all rows have similar number of cells, append None when needed
max_row = max( [len(x) for x in data] )
for rowNo, row in enumerate(data):
if len(row) < max_row:
row.extend([None]*(max_row-len(row)))
return data
#test
if __name__ == '__main__':
d = TableWidget().demo()
import os
d.save(formats=['pdf'],outDir=os.getcwd(),fnRoot=None)

29
reportlab/license.txt Normal file
View File

@@ -0,0 +1,29 @@
#####################################################################################
#
# Copyright (c) 2000-2008, ReportLab Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the company nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE OFFICERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#####################################################################################

View File

@@ -0,0 +1,6 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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__="""Internal functionality for creating PDF files - not part of API
"""

View File

@@ -0,0 +1,60 @@
#
"""
This is a utility to 'can' the widths data for certain CID fonts.
Now we're using Unicode, we don't need 20 CMAP files for each Asian
language, nor the widths of the non-normal characters encoded in each
font. we just want a dictionary of the character widths in a given
font which are NOT 1000 ems wide, keyed on Unicode character (not CID).
Running off CMAP files we get the following widths...::
>>> font = UnicodeCIDFont('HeiseiMin-W3')
>>> font.stringWidth(unicode(','), 10)
2.5
>>> font.stringWidth(unicode('m'), 10)
7.7800000000000002
>>> font.stringWidth(u'\u6771\u4EAC', 10)
20.0
>>>
"""
from pprint import pprint as pp
from reportlab.pdfbase._cidfontdata import defaultUnicodeEncodings
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
def run():
buf = []
buf.append('widthsByUnichar = {}')
for fontName, (language, encName) in defaultUnicodeEncodings.items():
print('handling %s : %s : %s' % (fontName, language, encName))
#this does just about all of it for us, as all the info
#we need is present.
font = UnicodeCIDFont(fontName)
widthsByCID = font.face._explicitWidths
cmap = font.encoding._cmap
nonStandardWidthsByUnichar = {}
for codePoint, cid in cmap.items():
width = widthsByCID.get(cid, 1000)
if width != 1000:
nonStandardWidthsByUnichar[chr(codePoint)] = width
print('created font width map (%d items). ' % len(nonStandardWidthsByUnichar))
buf.append('widthsByUnichar["%s"] = %s' % (fontName, repr(nonStandardWidthsByUnichar)))
src = '\n'.join(buf) + '\n'
open('canned_widths.py','w').write(src)
print('wrote canned_widths.py')
if __name__=='__main__':
run()

View File

@@ -0,0 +1,483 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#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
)
defaultUnicodeEncodings = {
#we ddefine a default Unicode encoding for each face name;
#this should be the most commonly used horizontal unicode encoding;
#also define a 3-letter language code.
'HeiseiMin-W3': ('jpn','UniJIS-UCS2-H'),
'HeiseiKakuGo-W5': ('jpn','UniJIS-UCS2-H'),
'STSong-Light': ('chs', 'UniGB-UCS2-H'),
'MSung-Light': ('cht', 'UniGB-UCS2-H'),
#'MHei-Medium': ('cht', 'UniGB-UCS2-H'),
'HYSMyeongJo-Medium': ('kor', 'UniKS-UCS2-H'),
'HYGothic-Medium': ('kor','UniKS-UCS2-H'),
}
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']
#declare separately those used for unicode
unicode_encodings = [enc for enc in allowedEncodings if 'UCS2' in enc]
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
}
#this data was derived from the above width information and removes all dependency on CMAP files as long as we only use the unicode fonts.
widthsByUnichar = {}
widthsByUnichar["MSung-Light"] = {u' ': 250, u'$': 490, u'(': 240, u',': 250, u'0': 500, u'4': 500, u'8': 500, u'<': 667, u'@': 921, u'D': 760, u'H': 802, u'L': 604, u'P': 563, u'T': 698, u'X': 771, u'\\': 520, u'`': 250, u'd': 521, u'h': 531, u'l': 240, u'p': 521, u't': 292, u'x': 479, u'|': 496, u'#': 668, u"'": 250, u'+': 667, u'/': 520, u'3': 500, u'7': 500, u';': 250, u'?': 396, u'C': 719, u'G': 771, u'K': 781, u'O': 823, u'S': 542, u'W': 948, u'[': 344, u'_': 500, u'c': 427, u'g': 469, u'k': 458, u'o': 500, u's': 333, u'w': 677, u'{': 480, u'"': 408, u'&': 698, u'*': 417, u'.': 250, u'2': 500, u'6': 500, u':': 250, u'>': 667, u'B': 615, u'F': 552, u'J': 354, u'N': 750, u'R': 729, u'V': 729, u'Z': 635, u'^': 469, u'b': 521, u'f': 271, u'j': 250, u'n': 531, u'r': 365, u'v': 458, u'z': 427, u'~': 667, u'!': 250, u'%': 875, u')': 240, u'-': 313, u'1': 500, u'5': 500, u'9': 500, u'=': 667, u'A': 677, u'E': 625, u'I': 354, u'M': 927, u'Q': 823, u'U': 771, u'Y': 677, u']': 344, u'a': 469, u'e': 438, u'i': 250, u'm': 802, u'q': 521, u'u': 521, u'y': 458, u'}': 480}
widthsByUnichar["HeiseiKakuGo-W5"] = {u'\uff81': 500, u'\uff85': 500, u'\uff89': 500, u'\uff8d': 500, u'\uff91': 500, u'\uff95': 500, u'\uff99': 500, u'\uff9d': 500, u' ': 277, u'$': 668, u'(': 445, u',': 305, u'0': 668, u'\u0332': 668, u'4': 668, u'8': 668, u'<': 668, u'@': 871, u'D': 699, u'H': 687, u'L': 582, u'P': 582, u'T': 641, u'X': 609, u'`': 590, u'\uff62': 500, u'd': 602, u'\uff66': 500, u'h': 582, u'\uff6a': 500, u'l': 234, u'\uff6e': 500, u'p': 602, u'\uff72': 500, u't': 441, u'\uff76': 500, u'x': 531, u'\uff7a': 500, u'|': 246, u'\uff7e': 500, u'\uff82': 500, u'\uff86': 500, u'\uff8a': 500, u'\uff8e': 500, u'\uff92': 500, u'\uff96': 500, u'\uff9a': 500, u'\uff9e': 500, u'#': 668, u"'": 305, u'+': 668, u'/': 539, u'3': 668, u'7': 668, u';': 305, u'?': 566, u'C': 652, u'G': 676, u'K': 664, u'O': 734, u'S': 605, u'W': 945, u'[': 445, u'_': 668, u'\uff61': 500, u'c': 547, u'\uff65': 500, u'g': 609, u'\uff69': 500, u'k': 539, u'\uff6d': 500, u'o': 605, u'\uff71': 500, u's': 508, u'\uff75': 500, u'w': 781, u'\uff79': 500, u'{': 449, u'\uff7d': 500, u'\u0300': 590, u'\uff83': 500, u'\u2002': 500, u'\uff87': 500, u'\uff8b': 500, u'\uff8f': 500, u'\uff93': 500, u'\uff97': 500, u'\uff9b': 500, u'\uff9f': 500, u'"': 500, u'\xa5': 668, u'&': 727, u'*': 508, u'.': 305, u'2': 668, u'6': 668, u':': 305, u'>': 668, u'B': 637, u'F': 555, u'J': 492, u'N': 707, u'\u203e': 500, u'R': 605, u'V': 727, u'Z': 574, u'^': 668, u'b': 609, u'\uff64': 500, u'f': 391, u'\uff68': 500, u'j': 277, u'\uff6c': 500, u'n': 582, u'\uff70': 500, u'r': 387, u'\uff74': 500, u'v': 562, u'\uff78': 500, u'z': 555, u'\uff7c': 500, u'~': 668, u'\uff80': 500, u'\u0303': 668, u'\uff84': 500, u'\uff88': 500, u'\uff8c': 500, u'\u2011': 379, u'\uff90': 500, u'\uff94': 500, u'\uff98': 500, u'\uff9c': 500, u'!': 305, u'%': 906, u')': 445, u'-': 379, u'1': 668, u'5': 668, u'9': 668, u'=': 668, u'A': 727, u'E': 574, u'I': 242, u'M': 789, u'Q': 734, u'U': 668, u'Y': 609, u']': 445, u'a': 555, u'\uff63': 500, u'e': 574, u'\uff67': 500, u'i': 234, u'\uffe8': 500, u'\uff6b': 500, u'm': 895, u'\uff6f': 500, u'q': 602, u'\uff73': 500, u'u': 582, u'\uff77': 500, u'y': 570, u'\uff7b': 500, u'}': 449, u'\uff7f': 500}
widthsByUnichar["HYSMyeongJo-Medium"] = {u' ': 333, u'$': 625, u'(': 500, u',': 291, u'0': 625, u'4': 625, u'8': 625, u'<': 833, u'D': 750, u'H': 791, u'L': 666, u'P': 666, u'T': 791, u'X': 708, u'\\': 375, u'`': 333, u'd': 583, u'h': 583, u'l': 291, u'p': 583, u't': 375, u'x': 625, u'|': 583, u'#': 833, u"'": 250, u'+': 833, u'/': 375, u'3': 625, u'7': 625, u';': 333, u'?': 500, u'C': 708, u'G': 750, u'K': 791, u'O': 750, u'S': 666, u'[': 500, u'_': 500, u'c': 541, u'g': 583, u'k': 583, u'o': 583, u's': 541, u'w': 833, u'{': 583, u'"': 416, u'&': 833, u'*': 500, u'.': 291, u'2': 625, u'6': 625, u':': 333, u'>': 916, u'B': 708, u'F': 666, u'J': 500, u'N': 791, u'R': 708, u'V': 750, u'Z': 666, u'^': 500, u'b': 583, u'f': 375, u'j': 333, u'n': 583, u'r': 458, u'v': 583, u'z': 500, u'~': 750, u'!': 416, u'%': 916, u')': 500, u'-': 833, u'1': 625, u'5': 625, u'9': 625, u'=': 833, u'A': 791, u'E': 708, u'I': 375, u'M': 916, u'Q': 750, u'U': 791, u'Y': 708, u']': 500, u'a': 541, u'e': 583, u'i': 291, u'm': 875, u'q': 583, u'u': 583, u'y': 625, u'}': 583}
widthsByUnichar["STSong-Light"] = {u' ': 207, u'$': 462, u'(': 374, u',': 238, u'0': 462, u'4': 462, u'8': 462, u'<': 605, u'@': 748, u'D': 739, u'H': 793, u'L': 526, u'P': 544, u'T': 607, u'X': 647, u'\\': 333, u'`': 239, u'd': 529, u'h': 518, u'l': 228, u'p': 524, u't': 277, u'x': 466, u'|': 258, u'#': 467, u"'": 239, u'+': 605, u'/': 334, u'3': 462, u'7': 462, u';': 238, u'?': 344, u'C': 695, u'G': 729, u'K': 666, u'O': 772, u'S': 465, u'W': 972, u'[': 374, u'_': 500, u'c': 427, u'g': 444, u'k': 495, u'o': 524, u's': 336, u'w': 652, u'{': 370, u'"': 342, u'&': 710, u'*': 423, u'.': 238, u'2': 462, u'6': 462, u':': 238, u'>': 605, u'B': 560, u'F': 511, u'J': 312, u'N': 758, u'R': 628, u'V': 711, u'Z': 607, u'^': 606, u'b': 503, u'f': 264, u'j': 230, u'n': 527, u'r': 338, u'v': 450, u'z': 407, u'~': 605, u'!': 270, u'%': 797, u')': 374, u'-': 375, u'1': 462, u'5': 462, u'9': 462, u'=': 605, u'A': 684, u'E': 563, u'I': 318, u'M': 896, u'Q': 772, u'U': 753, u'Y': 620, u']': 374, u'a': 417, u'e': 415, u'i': 241, u'm': 793, u'q': 504, u'u': 517, u'y': 452, u'}': 370}
widthsByUnichar["HeiseiMin-W3"] = {u'\uff81': 500, u'\u0302': 333, u'\uff85': 500, u'\u0306': 333, u'\uff89': 500, u'\u030a': 333, u'\uff8d': 500, u'\uff91': 500, u'\ufb02': 556, u'\uff95': 500, u'\uff99': 500, u'\uff9d': 500, u' ': 250, u'\xa3': 500, u'\u2122': 980, u'$': 500, u'(': 333, u'\xab': 500, u',': 250, u'\xaf': 333, u'0': 500, u'\xb3': 300, u'\u0332': 500, u'4': 500, u'\xb7': 250, u'8': 500, u'\xbb': 500, u'<': 564, u'\xbf': 444, u'@': 921, u'\xc3': 722, u'\u0142': 278, u'D': 722, u'\xc7': 667, u'H': 722, u'\xcb': 611, u'L': 611, u'\xcf': 333, u'P': 556, u'\xd3': 722, u'\u0152': 889, u'T': 611, u'X': 722, u'\xdb': 722, u'\\': 278, u'\xdf': 500, u'\uff64': 500, u'`': 333, u'\xe3': 444, u'\uff62': 500, u'd': 500, u'\xe7': 444, u'\uff66': 500, u'h': 500, u'\xeb': 444, u'\uff6a': 500, u'l': 278, u'\xef': 278, u'\uff6e': 500, u'p': 500, u'\xf3': 500, u'\uff72': 500, u't': 278, u'\uff76': 500, u'x': 500, u'\xfb': 500, u'\uff7a': 500, u'|': 200, u'\xff': 500, u'\u017e': 444, u'\u0301': 333, u'\uff82': 500, u'\u0305': 500, u'\uff86': 500, u'\uff8a': 500, u'\uff8e': 500, u'\u2013': 500, u'\uff92': 500, u'\uff96': 500, u'\uff9a': 500, u'\uff9e': 500, u'#': 500, u'\xa4': 500, u"'": 180, u'\u203a': 333, u'+': 564, u'\xac': 564, u'/': 278, u'\u0131': 278, u'3': 500, u'7': 500, u'\xb8': 333, u';': 278, u'\xbc': 750, u'?': 444, u'\u0141': 611, u'\xc0': 722, u'C': 667, u'\xc4': 722, u'G': 722, u'\xc8': 611, u'K': 722, u'\xcc': 333, u'O': 722, u'\xd0': 722, u'S': 556, u'\u2022': 350, u'\xd4': 722, u'W': 944, u'\uff78': 500, u'\xd8': 722, u'[': 333, u'\xdc': 722, u'_': 500, u'\u0161': 389, u'\xe0': 444, u'c': 444, u'\uff65': 500, u'\xe4': 444, u'g': 500, u'\uff69': 500, u'\xe8': 444, u'k': 500, u'\uff6d': 500, u'\xec': 278, u'o': 500, u'\uff71': 500, u'\xf0': 500, u's': 389, u'\uff75': 500, u'\xf4': 500, u'w': 722, u'\uff79': 500, u'\xf8': 500, u'{': 480, u'\uff7e': 500, u'\u017d': 611, u'\xfc': 500, u'\u0300': 333, u'\uff83': 500, u'\u2002': 500, u'\u0304': 333, u'\uff87': 500, u'\u0308': 333, u'\uff8b': 500, u'\u030c': 333, u'\uff8f': 500, u'\uff93': 500, u'\u2012': 500, u'\uff97': 500, u'\uff9b': 500, u'\u201a': 333, u'\uff9f': 500, u'\u201e': 444, u'\xa1': 333, u'"': 408, u'\xa5': 500, u'&': 778, u'\xa9': 760, u'\u0328': 333, u'*': 500, u'\xad': 564, u'.': 250, u'\uffe8': 500, u'2': 500, u'\xb5': 500, u'6': 500, u'\xb9': 300, u':': 278, u'\xbd': 750, u'>': 564, u'\xc1': 722, u'\uff61': 500, u'B': 667, u'\xc5': 722, u'F': 556, u'\xc9': 611, u'J': 389, u'\xcd': 333, u'N': 722, u'\xd1': 722, u'\u203e': 500, u'R': 667, u'\xd5': 722, u'V': 722, u'\xd9': 722, u'Z': 611, u'\xdd': 722, u'^': 469, u'\xe1': 444, u'\u0160': 556, u'b': 500, u'\xe5': 444, u'\u2039': 333, u'f': 333, u'\xe9': 444, u'\uff68': 500, u'j': 278, u'\xed': 278, u'\uff6c': 500, u'n': 500, u'\xf1': 500, u'\uff70': 500, u'r': 333, u'\xf5': 500, u'\uff74': 500, u'v': 500, u'\xf9': 500, u'\u0178': 722, u'z': 444, u'\xfd': 500, u'\uff7c': 500, u'~': 333, u'\uff80': 500, u'\u0303': 333, u'\uff84': 500, u'\u0307': 333, u'\uff88': 500, u'\u030b': 333, u'\uff8c': 500, u'\u2011': 333, u'\uff90': 500, u'\uff94': 500, u'\uff98': 500, u'\uff9c': 500, u'\u2044': 167, u'!': 333, u'\xa2': 500, u'%': 833, u'\u0327': 333, u'\xa6': 200, u')': 333, u'\xaa': 276, u'-': 333, u'\xae': 760, u'1': 500, u'\xb2': 300, u'5': 500, u'9': 500, u'\xba': 310, u'=': 564, u'\xbe': 750, u'A': 722, u'\u01c0': 200, u'\xc2': 722, u'E': 611, u'\xc6': 889, u'I': 333, u'\xca': 611, u'M': 889, u'\xce': 333, u'Q': 722, u'\u0153': 722, u'\xd2': 722, u'U': 722, u'\xd6': 722, u'Y': 722, u'\ufb01': 556, u'\xda': 722, u']': 333, u'\xde': 556, u'a': 444, u'\uff63': 500, u'\xe2': 444, u'e': 444, u'\uff67': 500, u'\xe6': 667, u'i': 278, u'\uff7d': 500, u'\uff6b': 500, u'\xea': 444, u'm': 778, u'\uff6f': 500, u'\xee': 278, u'q': 500, u'\uff73': 500, u'\xf2': 500, u'u': 500, u'\uff77': 500, u'\xf6': 500, u'y': 500, u'\uff7b': 500, u'\xfa': 500, u'}': 480, u'\uff7f': 500, u'\xfe': 500}
widthsByUnichar["HYGothic-Medium"] = {u' ': 500, u'$': 500, u'(': 500, u',': 500, u'0': 500, u'4': 500, u'8': 500, u'<': 500, u'@': 500, u'D': 500, u'H': 500, u'L': 500, u'P': 500, u'T': 500, u'X': 500, u'\\': 500, u'`': 500, u'd': 500, u'h': 500, u'l': 500, u'p': 500, u't': 500, u'x': 500, u'|': 500, u'#': 500, u"'": 500, u'+': 500, u'/': 500, u'3': 500, u'7': 500, u';': 500, u'?': 500, u'C': 500, u'G': 500, u'K': 500, u'O': 500, u'S': 500, u'W': 500, u'[': 500, u'_': 500, u'c': 500, u'g': 500, u'k': 500, u'o': 500, u's': 500, u'w': 500, u'{': 500, u'"': 500, u'&': 500, u'*': 500, u'.': 500, u'2': 500, u'6': 500, u':': 500, u'>': 500, u'B': 500, u'F': 500, u'J': 500, u'N': 500, u'R': 500, u'V': 500, u'Z': 500, u'^': 500, u'b': 500, u'f': 500, u'j': 500, u'n': 500, u'r': 500, u'v': 500, u'z': 500, u'!': 500, u'%': 500, u')': 500, u'-': 500, u'1': 500, u'5': 500, u'9': 500, u'=': 500, u'A': 500, u'E': 500, u'I': 500, u'M': 500, u'Q': 500, u'U': 500, u'Y': 500, u']': 500, u'a': 500, u'e': 500, u'i': 500, u'm': 500, u'q': 500, u'u': 500, u'y': 500, u'}': 500}
#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):
## words = text.split()
## out = []
## for word in words:
## if word == '[':
## out.append(word)
## else:
## out.append(word + ',')
## return eval(''.join(out))

View File

@@ -0,0 +1,255 @@
#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfbase/_fontdata.py
#$Header $
__version__=''' $Id$ '''
__doc__="""Database of font related things
- standardFonts - tuple of the 14 standard string font names
- standardEncodings - tuple of the known standard font names
- encodings - a mapping object from standard encoding names (and minor variants)
to the encoding vectors ie the tuple of string glyph names
- widthsByFontGlyph - fontname x glyphname --> width of glyph
- widthVectorsByFont - fontName -> vector of widths
This module defines a static, large data structure. At the request
of the Jython project, we have split this off into separate modules
as Jython cannot handle more than 64k of bytecode in the 'top level'
code of a Python module.
"""
import os, sys
# mapping of name to width vector, starts empty until fonts are added
# e.g. widths['Courier'] = [...600,600,600,...]
widthVectorsByFont = {}
fontsByName = {}
fontsByBaseEnc = {}
# this is a list of the standard 14 font names in Acrobat Reader
standardFonts = (
'Courier', 'Courier-Bold', 'Courier-Oblique', 'Courier-BoldOblique',
'Helvetica', 'Helvetica-Bold', 'Helvetica-Oblique', 'Helvetica-BoldOblique',
'Times-Roman', 'Times-Bold', 'Times-Italic', 'Times-BoldItalic',
'Symbol','ZapfDingbats')
standardFontAttributes = {
#family, bold, italic defined for basic ones
'Courier':('Courier',0,0),
'Courier-Bold':('Courier',1,0),
'Courier-Oblique':('Courier',0,1),
'Courier-BoldOblique':('Courier',1,1),
'Helvetica':('Helvetica',0,0),
'Helvetica-Bold':('Helvetica',1,0),
'Helvetica-Oblique':('Helvetica',0,1),
'Helvetica-BoldOblique':('Helvetica',1,1),
'Times-Roman':('Times-Roman',0,0),
'Times-Bold':('Times-Roman',1,0),
'Times-Italic':('Times-Roman',0,1),
'Times-BoldItalic':('Times-Roman',1,1),
'Symbol':('Symbol',0,0),
'ZapfDingbats':('ZapfDingbats',0,0)
}
#this maps fontnames to the equivalent filename root.
_font2fnrMapWin32 = {
'symbol': 'sy______',
'zapfdingbats': 'zd______',
'helvetica': '_a______',
'helvetica-bold': '_ab_____',
'helvetica-boldoblique': '_abi____',
'helvetica-oblique': '_ai_____',
'times-bold': '_eb_____',
'times-bolditalic': '_ebi____',
'times-italic': '_ei_____',
'times-roman': '_er_____',
'courier-bold': 'cob_____',
'courier-boldoblique': 'cobo____',
'courier': 'com_____',
'courier-oblique': 'coo_____',
}
if sys.platform in ('linux2',):
_font2fnrMapLinux2 ={
'symbol': 'Symbol',
'zapfdingbats': 'ZapfDingbats',
'helvetica': 'Arial',
'helvetica-bold': 'Arial-Bold',
'helvetica-boldoblique': 'Arial-BoldItalic',
'helvetica-oblique': 'Arial-Italic',
'times-bold': 'TimesNewRoman-Bold',
'times-bolditalic':'TimesNewRoman-BoldItalic',
'times-italic': 'TimesNewRoman-Italic',
'times-roman': 'TimesNewRoman',
'courier-bold': 'Courier-Bold',
'courier-boldoblique': 'Courier-BoldOblique',
'courier': 'Courier',
'courier-oblique': 'Courier-Oblique',
}
_font2fnrMap = _font2fnrMapLinux2
for k, v in _font2fnrMap.items():
if k in _font2fnrMapWin32.keys():
_font2fnrMapWin32[v.lower()] = _font2fnrMapWin32[k]
del k, v
else:
_font2fnrMap = _font2fnrMapWin32
def _findFNR(fontName):
return _font2fnrMap[fontName.lower()]
from reportlab.rl_config import T1SearchPath
from reportlab.lib.utils import rl_isfile
def _searchT1Dirs(n,rl_isfile=rl_isfile,T1SearchPath=T1SearchPath):
assert T1SearchPath!=[], "No Type-1 font search path"
for d in T1SearchPath:
f = os.path.join(d,n)
if rl_isfile(f): return f
return None
del T1SearchPath, rl_isfile
def findT1File(fontName,ext='.pfb'):
if sys.platform in ('linux2',) and ext=='.pfb':
try:
f = _searchT1Dirs(_findFNR(fontName))
if f: return f
except:
pass
try:
f = _searchT1Dirs(_font2fnrMapWin32[fontName.lower()]+ext)
if f: return f
except:
pass
return _searchT1Dirs(_findFNR(fontName)+ext)
# this lists the predefined font encodings - WinAnsi and MacRoman. We have
# not added MacExpert - it's possible, but would complicate life and nobody
# is asking. StandardEncoding means something special.
standardEncodings = ('WinAnsiEncoding','MacRomanEncoding','StandardEncoding','SymbolEncoding','ZapfDingbatsEncoding','PDFDocEncoding', 'MacExpertEncoding')
#this is the global mapping of standard encodings to name vectors
class _Name2StandardEncodingMap(dict):
'''Trivial fake dictionary with some [] magic'''
_XMap = {'winansi':'WinAnsiEncoding','macroman': 'MacRomanEncoding','standard':'StandardEncoding','symbol':'SymbolEncoding', 'zapfdingbats':'ZapfDingbatsEncoding','pdfdoc':'PDFDocEncoding', 'macexpert':'MacExpertEncoding'}
def __setitem__(self,x,v):
y = x.lower()
if y[-8:]=='encoding': y = y[:-8]
y = self._XMap[y]
if y in self: raise IndexError('Encoding %s is already set' % y)
dict.__setitem__(self,y,v)
def __getitem__(self,x):
y = x.lower()
if y[-8:]=='encoding': y = y[:-8]
y = self._XMap[y]
return dict.__getitem__(self,y)
encodings = _Name2StandardEncodingMap()
#due to compiled method size limits in Jython,
#we pull these in from separate modules to keep this module
#well under 64k. We might well be able to ditch many of
#these anyway now we run on Unicode.
from reportlab.pdfbase._fontdata_enc_winansi import WinAnsiEncoding
from reportlab.pdfbase._fontdata_enc_macroman import MacRomanEncoding
from reportlab.pdfbase._fontdata_enc_standard import StandardEncoding
from reportlab.pdfbase._fontdata_enc_symbol import SymbolEncoding
from reportlab.pdfbase._fontdata_enc_zapfdingbats import ZapfDingbatsEncoding
from reportlab.pdfbase._fontdata_enc_pdfdoc import PDFDocEncoding
from reportlab.pdfbase._fontdata_enc_macexpert import MacExpertEncoding
encodings.update({
'WinAnsiEncoding': WinAnsiEncoding,
'MacRomanEncoding': MacRomanEncoding,
'StandardEncoding': StandardEncoding,
'SymbolEncoding': SymbolEncoding,
'ZapfDingbatsEncoding': ZapfDingbatsEncoding,
'PDFDocEncoding': PDFDocEncoding,
'MacExpertEncoding': MacExpertEncoding,
})
ascent_descent = {
'Courier': (629, -157),
'Courier-Bold': (626, -142),
'Courier-BoldOblique': (626, -142),
'Courier-Oblique': (629, -157),
'Helvetica': (718, -207),
'Helvetica-Bold': (718, -207),
'Helvetica-BoldOblique': (718, -207),
'Helvetica-Oblique': (718, -207),
'Times-Roman': (683, -217),
'Times-Bold': (676, -205),
'Times-BoldItalic': (699, -205),
'Times-Italic': (683, -205),
'Symbol': (0, 0),
'ZapfDingbats': (0, 0)
}
# ditto about 64k limit - profusion of external files
import reportlab.pdfbase._fontdata_widths_courier
import reportlab.pdfbase._fontdata_widths_courierbold
import reportlab.pdfbase._fontdata_widths_courieroblique
import reportlab.pdfbase._fontdata_widths_courierboldoblique
import reportlab.pdfbase._fontdata_widths_helvetica
import reportlab.pdfbase._fontdata_widths_helveticabold
import reportlab.pdfbase._fontdata_widths_helveticaoblique
import reportlab.pdfbase._fontdata_widths_helveticaboldoblique
import reportlab.pdfbase._fontdata_widths_timesroman
import reportlab.pdfbase._fontdata_widths_timesbold
import reportlab.pdfbase._fontdata_widths_timesitalic
import reportlab.pdfbase._fontdata_widths_timesbolditalic
import reportlab.pdfbase._fontdata_widths_symbol
import reportlab.pdfbase._fontdata_widths_zapfdingbats
widthsByFontGlyph = {
'Courier':
reportlab.pdfbase._fontdata_widths_courier.widths,
'Courier-Bold':
reportlab.pdfbase._fontdata_widths_courierbold.widths,
'Courier-Oblique':
reportlab.pdfbase._fontdata_widths_courieroblique.widths,
'Courier-BoldOblique':
reportlab.pdfbase._fontdata_widths_courierboldoblique.widths,
'Helvetica':
reportlab.pdfbase._fontdata_widths_helvetica.widths,
'Helvetica-Bold':
reportlab.pdfbase._fontdata_widths_helveticabold.widths,
'Helvetica-Oblique':
reportlab.pdfbase._fontdata_widths_helveticaoblique.widths,
'Helvetica-BoldOblique':
reportlab.pdfbase._fontdata_widths_helveticaboldoblique.widths,
'Times-Roman':
reportlab.pdfbase._fontdata_widths_timesroman.widths,
'Times-Bold':
reportlab.pdfbase._fontdata_widths_timesbold.widths,
'Times-Italic':
reportlab.pdfbase._fontdata_widths_timesitalic.widths,
'Times-BoldItalic':
reportlab.pdfbase._fontdata_widths_timesbolditalic.widths,
'Symbol':
reportlab.pdfbase._fontdata_widths_symbol.widths,
'ZapfDingbats':
reportlab.pdfbase._fontdata_widths_zapfdingbats.widths,
}
#preserve the initial values here
def _reset(
initial_dicts=dict(
ascent_descent=ascent_descent.copy(),
fontsByBaseEnc=fontsByBaseEnc.copy(),
fontsByName=fontsByName.copy(),
standardFontAttributes=standardFontAttributes.copy(),
widthVectorsByFont=widthVectorsByFont.copy(),
widthsByFontGlyph=widthsByFontGlyph.copy(),
)
):
for k,v in initial_dicts.items():
d=globals()[k]
d.clear()
d.update(v)
from reportlab.rl_config import register_reset
register_reset(_reset)
del register_reset

View File

@@ -0,0 +1,28 @@
MacExpertEncoding = (None, None, None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
'space', 'exclamsmall', 'Hungarumlautsmall', 'centoldstyle', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall',
'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', 'hyphen',
'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', None,
'threequartersemdash', None, 'questionsmall', None, None, None, None, 'Ethsmall', None, None, 'onequarter',
'onehalf', 'threequarters', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds',
None, None, None, None, None, None, 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', None,
'parenrightinferior', 'Circumflexsmall', 'hypheninferior', 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall',
'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall',
'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall',
'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', None, None, 'asuperior', 'centsuperior', None, None, None,
None, 'Aacutesmall', 'Agravesmall', 'Acircumflexsmall', 'Adieresissmall', 'Atildesmall', 'Aringsmall',
'Ccedillasmall', 'Eacutesmall', 'Egravesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Iacutesmall', 'Igravesmall',
'Icircumflexsmall', 'Idieresissmall', 'Ntildesmall', 'Oacutesmall', 'Ogravesmall', 'Ocircumflexsmall',
'Odieresissmall', 'Otildesmall', 'Uacutesmall', 'Ugravesmall', 'Ucircumflexsmall', 'Udieresissmall', None,
'eightsuperior', 'fourinferior', 'threeinferior', 'sixinferior', 'eightinferior', 'seveninferior', 'Scaronsmall',
None, 'centinferior', 'twoinferior', None, 'Dieresissmall', None, 'Caronsmall', 'osuperior', 'fiveinferior', None,
'commainferior', 'periodinferior', 'Yacutesmall', None, 'dollarinferior', None, None, 'Thornsmall', None,
'nineinferior', 'zeroinferior', 'Zcaronsmall', 'AEsmall', 'Oslashsmall', 'questiondownsmall', 'oneinferior',
'Lslashsmall', None, None, None, None, None, None, 'Cedillasmall', None, None, None, None, None, 'OEsmall',
'figuredash', 'hyphensuperior', None, None, None, None, 'exclamdownsmall', None, 'Ydieresissmall', None,
'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior',
'ninesuperior', 'zerosuperior', None, 'esuperior', 'rsuperior', 'tsuperior', None, None, 'isuperior', 'ssuperior',
'dsuperior', None, None, None, None, None, 'lsuperior', 'Ogoneksmall', 'Brevesmall', 'Macronsmall', 'bsuperior',
'nsuperior', 'msuperior', 'commasuperior', 'periodsuperior', 'Dotaccentsmall', 'Ringsmall', None, None, None, None)

View File

@@ -0,0 +1,35 @@
MacRomanEncoding = (
None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, 'space', 'exclam',
'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand',
'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma',
'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four',
'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less',
'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright',
'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
'asciitilde', None, 'Adieresis', 'Aring', 'Ccedilla', 'Eacute',
'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', 'acircumflex',
'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave',
'ecircumflex', 'edieresis', 'iacute', 'igrave', 'icircumflex',
'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis',
'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger',
'degree', 'cent', 'sterling', 'section', 'bullet', 'paragraph',
'germandbls', 'registered', 'copyright', 'trademark', 'acute',
'dieresis', None, 'AE', 'Oslash', None, 'plusminus', None, None, 'yen',
'mu', None, None, None, None, None, 'ordfeminine', 'ordmasculine', None,
'ae', 'oslash', 'questiondown', 'exclamdown', 'logicalnot', None, 'florin',
None, None, 'guillemotleft', 'guillemotright', 'ellipsis', 'space', 'Agrave',
'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft',
'quotedblright', 'quoteleft', 'quoteright', 'divide', None, 'ydieresis',
'Ydieresis', 'fraction', 'currency', 'guilsinglleft', 'guilsinglright',
'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase',
'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute',
'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave',
'Oacute', 'Ocircumflex', None, 'Ograve', 'Uacute', 'Ucircumflex',
'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve',
'dotaccent', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron')

View File

@@ -0,0 +1,22 @@
PDFDocEncoding = (None, None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,
None,None,None,None,None,"breve","caron","circumflex",
"dotaccent","hungarumlaut","ogonek","ring","tilde","space","exclam","quotedbl","numbersign","dollar","percent",
"ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero",
"one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater",
"question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X",
"Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g",
"h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright",
"asciitilde",None,"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",None,"Euro","exclamdown","cent","sterling","currency","yen","brokenbar","section","dieresis",
"copyright","ordfeminine","guillemotleft","logicalnot",None,"registered","macron","degree","plusminus","twosuperior",
"threesuperior","acute","mu","paragraph","periodcentered","cedilla","onesuperior","ordmasculine","guillemotright",
"onequarter","onehalf","threequarters","questiondown","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")

View File

@@ -0,0 +1,15 @@
StandardEncoding =(None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,"space","exclam",
"quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus",
"comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon",
"semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O",
"P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore",
"quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y",
"z","braceleft","bar","braceright","asciitilde",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,
None,None,None,"exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft",
"guillemotleft","guilsinglleft","guilsinglright","fi","fl",None,"endash","dagger","daggerdbl","periodcentered",None,
"paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand",
None,"questiondown",None,"grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis",None,"ring",
"cedilla",None,"hungarumlaut","ogonek","caron","emdash",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,"AE",None,"ordfeminine",
None,None,None,None,"Lslash","Oslash","OE","ordmasculine",None,None,None,None,None,"ae",None,None,None,"dotlessi",None,None,"lslash","oslash",
"oe","germandbls",None,None,None,None)

View File

@@ -0,0 +1,30 @@
SymbolEncoding = (
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, 'space',
'exclam', 'universal', 'numbersign', 'existential', 'percent', 'ampersand', 'suchthat',
'parenleft', 'parenright', 'asteriskmath', 'plus', 'comma', 'minus', 'period', 'slash', 'zero',
'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon',
'less', 'equal', 'greater', 'question', 'congruent', 'Alpha', 'Beta', 'Chi', 'Delta', 'Epsilon',
'Phi', 'Gamma', 'Eta', 'Iota', 'theta1', 'Kappa', 'Lambda', 'Mu', 'Nu', 'Omicron', 'Pi', 'Theta',
'Rho', 'Sigma', 'Tau', 'Upsilon', 'sigma1', 'Omega', 'Xi', 'Psi', 'Zeta', 'bracketleft',
'therefore', 'bracketright', 'perpendicular', 'underscore', 'radicalex', 'alpha', 'beta', 'chi',
'delta', 'epsilon', 'phi', 'gamma', 'eta', 'iota', 'phi1', 'kappa', 'lambda', 'mu', 'nu',
'omicron', 'pi', 'theta', 'rho', 'sigma', 'tau', 'upsilon', 'omega1', 'omega', 'xi', 'psi', 'zeta',
'braceleft', 'bar', 'braceright', 'similar', None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, 'Euro', 'Upsilon1', 'minute', 'lessequal',
'fraction', 'infinity', 'florin', 'club', 'diamond', 'heart', 'spade', 'arrowboth', 'arrowleft',
'arrowup', 'arrowright', 'arrowdown', 'degree', 'plusminus', 'second', 'greaterequal', 'multiply',
'proportional', 'partialdiff', 'bullet', 'divide', 'notequal', 'equivalence', 'approxequal',
'ellipsis', 'arrowvertex', 'arrowhorizex', 'carriagereturn', 'aleph', 'Ifraktur', 'Rfraktur',
'weierstrass', 'circlemultiply', 'circleplus', 'emptyset', 'intersection', 'union',
'propersuperset', 'reflexsuperset', 'notsubset', 'propersubset', 'reflexsubset', 'element',
'notelement', 'angle', 'gradient', 'registerserif', 'copyrightserif', 'trademarkserif', 'product',
'radical', 'dotmath', 'logicalnot', 'logicaland', 'logicalor', 'arrowdblboth', 'arrowdblleft',
'arrowdblup', 'arrowdblright', 'arrowdbldown', 'lozenge', 'angleleft', 'registersans',
'copyrightsans', 'trademarksans', 'summation', 'parenlefttp', 'parenleftex', 'parenleftbt',
'bracketlefttp', 'bracketleftex', 'bracketleftbt', 'bracelefttp', 'braceleftmid', 'braceleftbt',
'braceex', None, 'angleright', 'integral', 'integraltp', 'integralex', 'integralbt',
'parenrighttp', 'parenrightex', 'parenrightbt', 'bracketrighttp', 'bracketrightex',
'bracketrightbt', 'bracerighttp', 'bracerightmid', 'bracerightbt', None)

View File

@@ -0,0 +1,37 @@
WinAnsiEncoding = (
None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, 'space', 'exclam',
'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand',
'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma',
'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four',
'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less',
'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright',
'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
'asciitilde', 'bullet', 'Euro', 'bullet', 'quotesinglbase', 'florin',
'quotedblbase', 'ellipsis', 'dagger', 'daggerdbl', 'circumflex',
'perthousand', 'Scaron', 'guilsinglleft', 'OE', 'bullet', 'Zcaron',
'bullet', 'bullet', 'quoteleft', 'quoteright', 'quotedblleft',
'quotedblright', 'bullet', 'endash', 'emdash', 'tilde', 'trademark',
'scaron', 'guilsinglright', 'oe', 'bullet', 'zcaron', 'Ydieresis',
'space', 'exclamdown', 'cent', 'sterling', 'currency', 'yen', 'brokenbar',
'section', 'dieresis', 'copyright', 'ordfeminine', 'guillemotleft',
'logicalnot', 'hyphen', 'registered', 'macron', 'degree', 'plusminus',
'twosuperior', 'threesuperior', 'acute', 'mu', 'paragraph', 'periodcentered',
'cedilla', 'onesuperior', 'ordmasculine', 'guillemotright', 'onequarter',
'onehalf', 'threequarters', 'questiondown', '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')

View File

@@ -0,0 +1,20 @@
ZapfDingbatsEncoding = ( None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
'space', 'a1', 'a2', 'a202', 'a3', 'a4', 'a5', 'a119', 'a118', 'a117', 'a11', 'a12', 'a13', 'a14',
'a15', 'a16', 'a105', 'a17', 'a18', 'a19', 'a20', 'a21', 'a22', 'a23', 'a24', 'a25', 'a26', 'a27',
'a28', 'a6', 'a7', 'a8', 'a9', 'a10', 'a29', 'a30', 'a31', 'a32', 'a33', 'a34', 'a35', 'a36',
'a37', 'a38', 'a39', 'a40', 'a41', 'a42', 'a43', 'a44', 'a45', 'a46', 'a47', 'a48', 'a49', 'a50',
'a51', 'a52', 'a53', 'a54', 'a55', 'a56', 'a57', 'a58', 'a59', 'a60', 'a61', 'a62', 'a63', 'a64',
'a65', 'a66', 'a67', 'a68', 'a69', 'a70', 'a71', 'a72', 'a73', 'a74', 'a203', 'a75', 'a204', 'a76',
'a77', 'a78', 'a79', 'a81', 'a82', 'a83', 'a84', 'a97', 'a98', 'a99', 'a100', None, 'a89', 'a90',
'a93', 'a94', 'a91', 'a92', 'a205', 'a85', 'a206', 'a86', 'a87', 'a88', 'a95', 'a96', None, None,
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None,
None, 'a101', 'a102', 'a103', 'a104', 'a106', 'a107', 'a108', 'a112', 'a111', 'a110', 'a109',
'a120', 'a121', 'a122', 'a123', 'a124', 'a125', 'a126', 'a127', 'a128', 'a129', 'a130', 'a131',
'a132', 'a133', 'a134', 'a135', 'a136', 'a137', 'a138', 'a139', 'a140', 'a141', 'a142', 'a143',
'a144', 'a145', 'a146', 'a147', 'a148', 'a149', 'a150', 'a151', 'a152', 'a153', 'a154', 'a155',
'a156', 'a157', 'a158', 'a159', 'a160', 'a161', 'a163', 'a164', 'a196', 'a165', 'a192', 'a166',
'a167', 'a168', 'a169', 'a170', 'a171', 'a172', 'a173', 'a162', 'a174', 'a175', 'a176', 'a177',
'a178', 'a179', 'a193', 'a180', 'a199', 'a181', 'a200', 'a182', None, 'a201', 'a183', 'a184',
'a197', 'a185', 'a194', 'a198', 'a186', 'a195', 'a187', 'a188', 'a189', 'a190', 'a191', None)

View File

@@ -0,0 +1,229 @@
widths = {'A': 600,
'AE': 600,
'Aacute': 600,
'Acircumflex': 600,
'Adieresis': 600,
'Agrave': 600,
'Aring': 600,
'Atilde': 600,
'B': 600,
'C': 600,
'Ccedilla': 600,
'D': 600,
'E': 600,
'Eacute': 600,
'Ecircumflex': 600,
'Edieresis': 600,
'Egrave': 600,
'Eth': 600,
'Euro': 600,
'F': 600,
'G': 600,
'H': 600,
'I': 600,
'Iacute': 600,
'Icircumflex': 600,
'Idieresis': 600,
'Igrave': 600,
'J': 600,
'K': 600,
'L': 600,
'Lslash': 600,
'M': 600,
'N': 600,
'Ntilde': 600,
'O': 600,
'OE': 600,
'Oacute': 600,
'Ocircumflex': 600,
'Odieresis': 600,
'Ograve': 600,
'Oslash': 600,
'Otilde': 600,
'P': 600,
'Q': 600,
'R': 600,
'S': 600,
'Scaron': 600,
'T': 600,
'Thorn': 600,
'U': 600,
'Uacute': 600,
'Ucircumflex': 600,
'Udieresis': 600,
'Ugrave': 600,
'V': 600,
'W': 600,
'X': 600,
'Y': 600,
'Yacute': 600,
'Ydieresis': 600,
'Z': 600,
'Zcaron': 600,
'a': 600,
'aacute': 600,
'acircumflex': 600,
'acute': 600,
'adieresis': 600,
'ae': 600,
'agrave': 600,
'ampersand': 600,
'aring': 600,
'asciicircum': 600,
'asciitilde': 600,
'asterisk': 600,
'at': 600,
'atilde': 600,
'b': 600,
'backslash': 600,
'bar': 600,
'braceleft': 600,
'braceright': 600,
'bracketleft': 600,
'bracketright': 600,
'breve': 600,
'brokenbar': 600,
'bullet': 600,
'c': 600,
'caron': 600,
'ccedilla': 600,
'cedilla': 600,
'cent': 600,
'circumflex': 600,
'colon': 600,
'comma': 600,
'copyright': 600,
'currency': 600,
'd': 600,
'dagger': 600,
'daggerdbl': 600,
'degree': 600,
'dieresis': 600,
'divide': 600,
'dollar': 600,
'dotaccent': 600,
'dotlessi': 600,
'e': 600,
'eacute': 600,
'ecircumflex': 600,
'edieresis': 600,
'egrave': 600,
'eight': 600,
'ellipsis': 600,
'emdash': 600,
'endash': 600,
'equal': 600,
'eth': 600,
'exclam': 600,
'exclamdown': 600,
'f': 600,
'fi': 600,
'five': 600,
'fl': 600,
'florin': 600,
'four': 600,
'fraction': 600,
'g': 600,
'germandbls': 600,
'grave': 600,
'greater': 600,
'guillemotleft': 600,
'guillemotright': 600,
'guilsinglleft': 600,
'guilsinglright': 600,
'h': 600,
'hungarumlaut': 600,
'hyphen': 600,
'i': 600,
'iacute': 600,
'icircumflex': 600,
'idieresis': 600,
'igrave': 600,
'j': 600,
'k': 600,
'l': 600,
'less': 600,
'logicalnot': 600,
'lslash': 600,
'm': 600,
'macron': 600,
'minus': 600,
'mu': 600,
'multiply': 600,
'n': 600,
'nine': 600,
'ntilde': 600,
'numbersign': 600,
'o': 600,
'oacute': 600,
'ocircumflex': 600,
'odieresis': 600,
'oe': 600,
'ogonek': 600,
'ograve': 600,
'one': 600,
'onehalf': 600,
'onequarter': 600,
'onesuperior': 600,
'ordfeminine': 600,
'ordmasculine': 600,
'oslash': 600,
'otilde': 600,
'p': 600,
'paragraph': 600,
'parenleft': 600,
'parenright': 600,
'percent': 600,
'period': 600,
'periodcentered': 600,
'perthousand': 600,
'plus': 600,
'plusminus': 600,
'q': 600,
'question': 600,
'questiondown': 600,
'quotedbl': 600,
'quotedblbase': 600,
'quotedblleft': 600,
'quotedblright': 600,
'quoteleft': 600,
'quoteright': 600,
'quotesinglbase': 600,
'quotesingle': 600,
'r': 600,
'registered': 600,
'ring': 600,
's': 600,
'scaron': 600,
'section': 600,
'semicolon': 600,
'seven': 600,
'six': 600,
'slash': 600,
'space': 600,
'sterling': 600,
't': 600,
'thorn': 600,
'three': 600,
'threequarters': 600,
'threesuperior': 600,
'tilde': 600,
'trademark': 600,
'two': 600,
'twosuperior': 600,
'u': 600,
'uacute': 600,
'ucircumflex': 600,
'udieresis': 600,
'ugrave': 600,
'underscore': 600,
'v': 600,
'w': 600,
'x': 600,
'y': 600,
'yacute': 600,
'ydieresis': 600,
'yen': 600,
'z': 600,
'zcaron': 600,
'zero': 600}

View File

@@ -0,0 +1,229 @@
widths = {'A': 600,
'AE': 600,
'Aacute': 600,
'Acircumflex': 600,
'Adieresis': 600,
'Agrave': 600,
'Aring': 600,
'Atilde': 600,
'B': 600,
'C': 600,
'Ccedilla': 600,
'D': 600,
'E': 600,
'Eacute': 600,
'Ecircumflex': 600,
'Edieresis': 600,
'Egrave': 600,
'Eth': 600,
'Euro': 600,
'F': 600,
'G': 600,
'H': 600,
'I': 600,
'Iacute': 600,
'Icircumflex': 600,
'Idieresis': 600,
'Igrave': 600,
'J': 600,
'K': 600,
'L': 600,
'Lslash': 600,
'M': 600,
'N': 600,
'Ntilde': 600,
'O': 600,
'OE': 600,
'Oacute': 600,
'Ocircumflex': 600,
'Odieresis': 600,
'Ograve': 600,
'Oslash': 600,
'Otilde': 600,
'P': 600,
'Q': 600,
'R': 600,
'S': 600,
'Scaron': 600,
'T': 600,
'Thorn': 600,
'U': 600,
'Uacute': 600,
'Ucircumflex': 600,
'Udieresis': 600,
'Ugrave': 600,
'V': 600,
'W': 600,
'X': 600,
'Y': 600,
'Yacute': 600,
'Ydieresis': 600,
'Z': 600,
'Zcaron': 600,
'a': 600,
'aacute': 600,
'acircumflex': 600,
'acute': 600,
'adieresis': 600,
'ae': 600,
'agrave': 600,
'ampersand': 600,
'aring': 600,
'asciicircum': 600,
'asciitilde': 600,
'asterisk': 600,
'at': 600,
'atilde': 600,
'b': 600,
'backslash': 600,
'bar': 600,
'braceleft': 600,
'braceright': 600,
'bracketleft': 600,
'bracketright': 600,
'breve': 600,
'brokenbar': 600,
'bullet': 600,
'c': 600,
'caron': 600,
'ccedilla': 600,
'cedilla': 600,
'cent': 600,
'circumflex': 600,
'colon': 600,
'comma': 600,
'copyright': 600,
'currency': 600,
'd': 600,
'dagger': 600,
'daggerdbl': 600,
'degree': 600,
'dieresis': 600,
'divide': 600,
'dollar': 600,
'dotaccent': 600,
'dotlessi': 600,
'e': 600,
'eacute': 600,
'ecircumflex': 600,
'edieresis': 600,
'egrave': 600,
'eight': 600,
'ellipsis': 600,
'emdash': 600,
'endash': 600,
'equal': 600,
'eth': 600,
'exclam': 600,
'exclamdown': 600,
'f': 600,
'fi': 600,
'five': 600,
'fl': 600,
'florin': 600,
'four': 600,
'fraction': 600,
'g': 600,
'germandbls': 600,
'grave': 600,
'greater': 600,
'guillemotleft': 600,
'guillemotright': 600,
'guilsinglleft': 600,
'guilsinglright': 600,
'h': 600,
'hungarumlaut': 600,
'hyphen': 600,
'i': 600,
'iacute': 600,
'icircumflex': 600,
'idieresis': 600,
'igrave': 600,
'j': 600,
'k': 600,
'l': 600,
'less': 600,
'logicalnot': 600,
'lslash': 600,
'm': 600,
'macron': 600,
'minus': 600,
'mu': 600,
'multiply': 600,
'n': 600,
'nine': 600,
'ntilde': 600,
'numbersign': 600,
'o': 600,
'oacute': 600,
'ocircumflex': 600,
'odieresis': 600,
'oe': 600,
'ogonek': 600,
'ograve': 600,
'one': 600,
'onehalf': 600,
'onequarter': 600,
'onesuperior': 600,
'ordfeminine': 600,
'ordmasculine': 600,
'oslash': 600,
'otilde': 600,
'p': 600,
'paragraph': 600,
'parenleft': 600,
'parenright': 600,
'percent': 600,
'period': 600,
'periodcentered': 600,
'perthousand': 600,
'plus': 600,
'plusminus': 600,
'q': 600,
'question': 600,
'questiondown': 600,
'quotedbl': 600,
'quotedblbase': 600,
'quotedblleft': 600,
'quotedblright': 600,
'quoteleft': 600,
'quoteright': 600,
'quotesinglbase': 600,
'quotesingle': 600,
'r': 600,
'registered': 600,
'ring': 600,
's': 600,
'scaron': 600,
'section': 600,
'semicolon': 600,
'seven': 600,
'six': 600,
'slash': 600,
'space': 600,
'sterling': 600,
't': 600,
'thorn': 600,
'three': 600,
'threequarters': 600,
'threesuperior': 600,
'tilde': 600,
'trademark': 600,
'two': 600,
'twosuperior': 600,
'u': 600,
'uacute': 600,
'ucircumflex': 600,
'udieresis': 600,
'ugrave': 600,
'underscore': 600,
'v': 600,
'w': 600,
'x': 600,
'y': 600,
'yacute': 600,
'ydieresis': 600,
'yen': 600,
'z': 600,
'zcaron': 600,
'zero': 600}

View File

@@ -0,0 +1,229 @@
widths = {'A': 600,
'AE': 600,
'Aacute': 600,
'Acircumflex': 600,
'Adieresis': 600,
'Agrave': 600,
'Aring': 600,
'Atilde': 600,
'B': 600,
'C': 600,
'Ccedilla': 600,
'D': 600,
'E': 600,
'Eacute': 600,
'Ecircumflex': 600,
'Edieresis': 600,
'Egrave': 600,
'Eth': 600,
'Euro': 600,
'F': 600,
'G': 600,
'H': 600,
'I': 600,
'Iacute': 600,
'Icircumflex': 600,
'Idieresis': 600,
'Igrave': 600,
'J': 600,
'K': 600,
'L': 600,
'Lslash': 600,
'M': 600,
'N': 600,
'Ntilde': 600,
'O': 600,
'OE': 600,
'Oacute': 600,
'Ocircumflex': 600,
'Odieresis': 600,
'Ograve': 600,
'Oslash': 600,
'Otilde': 600,
'P': 600,
'Q': 600,
'R': 600,
'S': 600,
'Scaron': 600,
'T': 600,
'Thorn': 600,
'U': 600,
'Uacute': 600,
'Ucircumflex': 600,
'Udieresis': 600,
'Ugrave': 600,
'V': 600,
'W': 600,
'X': 600,
'Y': 600,
'Yacute': 600,
'Ydieresis': 600,
'Z': 600,
'Zcaron': 600,
'a': 600,
'aacute': 600,
'acircumflex': 600,
'acute': 600,
'adieresis': 600,
'ae': 600,
'agrave': 600,
'ampersand': 600,
'aring': 600,
'asciicircum': 600,
'asciitilde': 600,
'asterisk': 600,
'at': 600,
'atilde': 600,
'b': 600,
'backslash': 600,
'bar': 600,
'braceleft': 600,
'braceright': 600,
'bracketleft': 600,
'bracketright': 600,
'breve': 600,
'brokenbar': 600,
'bullet': 600,
'c': 600,
'caron': 600,
'ccedilla': 600,
'cedilla': 600,
'cent': 600,
'circumflex': 600,
'colon': 600,
'comma': 600,
'copyright': 600,
'currency': 600,
'd': 600,
'dagger': 600,
'daggerdbl': 600,
'degree': 600,
'dieresis': 600,
'divide': 600,
'dollar': 600,
'dotaccent': 600,
'dotlessi': 600,
'e': 600,
'eacute': 600,
'ecircumflex': 600,
'edieresis': 600,
'egrave': 600,
'eight': 600,
'ellipsis': 600,
'emdash': 600,
'endash': 600,
'equal': 600,
'eth': 600,
'exclam': 600,
'exclamdown': 600,
'f': 600,
'fi': 600,
'five': 600,
'fl': 600,
'florin': 600,
'four': 600,
'fraction': 600,
'g': 600,
'germandbls': 600,
'grave': 600,
'greater': 600,
'guillemotleft': 600,
'guillemotright': 600,
'guilsinglleft': 600,
'guilsinglright': 600,
'h': 600,
'hungarumlaut': 600,
'hyphen': 600,
'i': 600,
'iacute': 600,
'icircumflex': 600,
'idieresis': 600,
'igrave': 600,
'j': 600,
'k': 600,
'l': 600,
'less': 600,
'logicalnot': 600,
'lslash': 600,
'm': 600,
'macron': 600,
'minus': 600,
'mu': 600,
'multiply': 600,
'n': 600,
'nine': 600,
'ntilde': 600,
'numbersign': 600,
'o': 600,
'oacute': 600,
'ocircumflex': 600,
'odieresis': 600,
'oe': 600,
'ogonek': 600,
'ograve': 600,
'one': 600,
'onehalf': 600,
'onequarter': 600,
'onesuperior': 600,
'ordfeminine': 600,
'ordmasculine': 600,
'oslash': 600,
'otilde': 600,
'p': 600,
'paragraph': 600,
'parenleft': 600,
'parenright': 600,
'percent': 600,
'period': 600,
'periodcentered': 600,
'perthousand': 600,
'plus': 600,
'plusminus': 600,
'q': 600,
'question': 600,
'questiondown': 600,
'quotedbl': 600,
'quotedblbase': 600,
'quotedblleft': 600,
'quotedblright': 600,
'quoteleft': 600,
'quoteright': 600,
'quotesinglbase': 600,
'quotesingle': 600,
'r': 600,
'registered': 600,
'ring': 600,
's': 600,
'scaron': 600,
'section': 600,
'semicolon': 600,
'seven': 600,
'six': 600,
'slash': 600,
'space': 600,
'sterling': 600,
't': 600,
'thorn': 600,
'three': 600,
'threequarters': 600,
'threesuperior': 600,
'tilde': 600,
'trademark': 600,
'two': 600,
'twosuperior': 600,
'u': 600,
'uacute': 600,
'ucircumflex': 600,
'udieresis': 600,
'ugrave': 600,
'underscore': 600,
'v': 600,
'w': 600,
'x': 600,
'y': 600,
'yacute': 600,
'ydieresis': 600,
'yen': 600,
'z': 600,
'zcaron': 600,
'zero': 600}

View File

@@ -0,0 +1,229 @@
widths = {'A': 600,
'AE': 600,
'Aacute': 600,
'Acircumflex': 600,
'Adieresis': 600,
'Agrave': 600,
'Aring': 600,
'Atilde': 600,
'B': 600,
'C': 600,
'Ccedilla': 600,
'D': 600,
'E': 600,
'Eacute': 600,
'Ecircumflex': 600,
'Edieresis': 600,
'Egrave': 600,
'Eth': 600,
'Euro': 600,
'F': 600,
'G': 600,
'H': 600,
'I': 600,
'Iacute': 600,
'Icircumflex': 600,
'Idieresis': 600,
'Igrave': 600,
'J': 600,
'K': 600,
'L': 600,
'Lslash': 600,
'M': 600,
'N': 600,
'Ntilde': 600,
'O': 600,
'OE': 600,
'Oacute': 600,
'Ocircumflex': 600,
'Odieresis': 600,
'Ograve': 600,
'Oslash': 600,
'Otilde': 600,
'P': 600,
'Q': 600,
'R': 600,
'S': 600,
'Scaron': 600,
'T': 600,
'Thorn': 600,
'U': 600,
'Uacute': 600,
'Ucircumflex': 600,
'Udieresis': 600,
'Ugrave': 600,
'V': 600,
'W': 600,
'X': 600,
'Y': 600,
'Yacute': 600,
'Ydieresis': 600,
'Z': 600,
'Zcaron': 600,
'a': 600,
'aacute': 600,
'acircumflex': 600,
'acute': 600,
'adieresis': 600,
'ae': 600,
'agrave': 600,
'ampersand': 600,
'aring': 600,
'asciicircum': 600,
'asciitilde': 600,
'asterisk': 600,
'at': 600,
'atilde': 600,
'b': 600,
'backslash': 600,
'bar': 600,
'braceleft': 600,
'braceright': 600,
'bracketleft': 600,
'bracketright': 600,
'breve': 600,
'brokenbar': 600,
'bullet': 600,
'c': 600,
'caron': 600,
'ccedilla': 600,
'cedilla': 600,
'cent': 600,
'circumflex': 600,
'colon': 600,
'comma': 600,
'copyright': 600,
'currency': 600,
'd': 600,
'dagger': 600,
'daggerdbl': 600,
'degree': 600,
'dieresis': 600,
'divide': 600,
'dollar': 600,
'dotaccent': 600,
'dotlessi': 600,
'e': 600,
'eacute': 600,
'ecircumflex': 600,
'edieresis': 600,
'egrave': 600,
'eight': 600,
'ellipsis': 600,
'emdash': 600,
'endash': 600,
'equal': 600,
'eth': 600,
'exclam': 600,
'exclamdown': 600,
'f': 600,
'fi': 600,
'five': 600,
'fl': 600,
'florin': 600,
'four': 600,
'fraction': 600,
'g': 600,
'germandbls': 600,
'grave': 600,
'greater': 600,
'guillemotleft': 600,
'guillemotright': 600,
'guilsinglleft': 600,
'guilsinglright': 600,
'h': 600,
'hungarumlaut': 600,
'hyphen': 600,
'i': 600,
'iacute': 600,
'icircumflex': 600,
'idieresis': 600,
'igrave': 600,
'j': 600,
'k': 600,
'l': 600,
'less': 600,
'logicalnot': 600,
'lslash': 600,
'm': 600,
'macron': 600,
'minus': 600,
'mu': 600,
'multiply': 600,
'n': 600,
'nine': 600,
'ntilde': 600,
'numbersign': 600,
'o': 600,
'oacute': 600,
'ocircumflex': 600,
'odieresis': 600,
'oe': 600,
'ogonek': 600,
'ograve': 600,
'one': 600,
'onehalf': 600,
'onequarter': 600,
'onesuperior': 600,
'ordfeminine': 600,
'ordmasculine': 600,
'oslash': 600,
'otilde': 600,
'p': 600,
'paragraph': 600,
'parenleft': 600,
'parenright': 600,
'percent': 600,
'period': 600,
'periodcentered': 600,
'perthousand': 600,
'plus': 600,
'plusminus': 600,
'q': 600,
'question': 600,
'questiondown': 600,
'quotedbl': 600,
'quotedblbase': 600,
'quotedblleft': 600,
'quotedblright': 600,
'quoteleft': 600,
'quoteright': 600,
'quotesinglbase': 600,
'quotesingle': 600,
'r': 600,
'registered': 600,
'ring': 600,
's': 600,
'scaron': 600,
'section': 600,
'semicolon': 600,
'seven': 600,
'six': 600,
'slash': 600,
'space': 600,
'sterling': 600,
't': 600,
'thorn': 600,
'three': 600,
'threequarters': 600,
'threesuperior': 600,
'tilde': 600,
'trademark': 600,
'two': 600,
'twosuperior': 600,
'u': 600,
'uacute': 600,
'ucircumflex': 600,
'udieresis': 600,
'ugrave': 600,
'underscore': 600,
'v': 600,
'w': 600,
'x': 600,
'y': 600,
'yacute': 600,
'ydieresis': 600,
'yen': 600,
'z': 600,
'zcaron': 600,
'zero': 600}

Some files were not shown because too many files have changed in this diff Show More