mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-11 00:59:41 +00:00
Added printing requirements
This commit is contained in:
@@ -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/
|
||||
|
||||
|
||||
BIN
reportlab.zip
BIN
reportlab.zip
Binary file not shown.
6
reportlab/MANIFEST.in
Normal file
6
reportlab/MANIFEST.in
Normal 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
48
reportlab/__init__.py
Normal 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
|
||||
8
reportlab/fonts/00readme.txt
Normal file
8
reportlab/fonts/00readme.txt
Normal 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.
|
||||
|
||||
14
reportlab/fonts/DarkGarden-changelog.txt
Normal file
14
reportlab/fonts/DarkGarden-changelog.txt
Normal 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
|
||||
339
reportlab/fonts/DarkGarden-copying-gpl.txt
Normal file
339
reportlab/fonts/DarkGarden-copying-gpl.txt
Normal 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.
|
||||
26
reportlab/fonts/DarkGarden-copying.txt
Normal file
26
reportlab/fonts/DarkGarden-copying.txt
Normal 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.
|
||||
106
reportlab/fonts/DarkGarden-readme.txt
Normal file
106
reportlab/fonts/DarkGarden-readme.txt
Normal 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
11067
reportlab/fonts/DarkGarden.sfd
Normal file
File diff suppressed because it is too large
Load Diff
429
reportlab/fonts/DarkGardenMK.afm
Normal file
429
reportlab/fonts/DarkGardenMK.afm
Normal 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
|
||||
BIN
reportlab/fonts/DarkGardenMK.pfb
Normal file
BIN
reportlab/fonts/DarkGardenMK.pfb
Normal file
Binary file not shown.
BIN
reportlab/fonts/Vera.ttf
Normal file
BIN
reportlab/fonts/Vera.ttf
Normal file
Binary file not shown.
BIN
reportlab/fonts/VeraBI.ttf
Normal file
BIN
reportlab/fonts/VeraBI.ttf
Normal file
Binary file not shown.
BIN
reportlab/fonts/VeraBd.ttf
Normal file
BIN
reportlab/fonts/VeraBd.ttf
Normal file
Binary file not shown.
BIN
reportlab/fonts/VeraIt.ttf
Normal file
BIN
reportlab/fonts/VeraIt.ttf
Normal file
Binary file not shown.
124
reportlab/fonts/bitstream-vera-license.txt
Normal file
124
reportlab/fonts/bitstream-vera-license.txt
Normal 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.
|
||||
|
||||
6
reportlab/graphics/__init__.py
Normal file
6
reportlab/graphics/__init__.py
Normal 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'''
|
||||
|
||||
59
reportlab/graphics/barcode/README
Normal file
59
reportlab/graphics/barcode/README
Normal 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
|
||||
24
reportlab/graphics/barcode/TODO
Normal file
24
reportlab/graphics/barcode/TODO
Normal 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
|
||||
1
reportlab/graphics/barcode/VERSION
Normal file
1
reportlab/graphics/barcode/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
0.9
|
||||
136
reportlab/graphics/barcode/__init__.py
Normal file
136
reportlab/graphics/barcode/__init__.py
Normal 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)
|
||||
321
reportlab/graphics/barcode/code128.py
Normal file
321
reportlab/graphics/barcode/code128.py
Normal 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
|
||||
245
reportlab/graphics/barcode/code39.py
Normal file
245
reportlab/graphics/barcode/code39.py
Normal 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
|
||||
234
reportlab/graphics/barcode/code93.py
Normal file
234
reportlab/graphics/barcode/code93.py
Normal 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:]
|
||||
749
reportlab/graphics/barcode/common.py
Normal file
749
reportlab/graphics/barcode/common.py
Normal 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
|
||||
350
reportlab/graphics/barcode/eanbc.py
Normal file
350
reportlab/graphics/barcode/eanbc.py
Normal 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
|
||||
81
reportlab/graphics/barcode/fourstate.py
Normal file
81
reportlab/graphics/barcode/fourstate.py
Normal 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/
|
||||
196
reportlab/graphics/barcode/lto.py
Normal file
196
reportlab/graphics/barcode/lto.py
Normal 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()
|
||||
209
reportlab/graphics/barcode/qr.py
Normal file
209
reportlab/graphics/barcode/qr.py
Normal 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)
|
||||
1130
reportlab/graphics/barcode/qrencoder.py
Normal file
1130
reportlab/graphics/barcode/qrencoder.py
Normal file
File diff suppressed because it is too large
Load Diff
200
reportlab/graphics/barcode/test.py
Normal file
200
reportlab/graphics/barcode/test.py
Normal 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'))
|
||||
232
reportlab/graphics/barcode/usps.py
Normal file
232
reportlab/graphics/barcode/usps.py
Normal 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]
|
||||
386
reportlab/graphics/barcode/usps4s.py
Normal file
386
reportlab/graphics/barcode/usps4s.py
Normal 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()
|
||||
307
reportlab/graphics/barcode/widgets.py
Normal file
307
reportlab/graphics/barcode/widgets.py
Normal 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')
|
||||
5
reportlab/graphics/charts/__init__.py
Normal file
5
reportlab/graphics/charts/__init__.py
Normal 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'''
|
||||
94
reportlab/graphics/charts/areas.py
Normal file
94
reportlab/graphics/charts/areas.py
Normal 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
|
||||
2332
reportlab/graphics/charts/axes.py
Normal file
2332
reportlab/graphics/charts/axes.py
Normal file
File diff suppressed because it is too large
Load Diff
2298
reportlab/graphics/charts/barcharts.py
Normal file
2298
reportlab/graphics/charts/barcharts.py
Normal file
File diff suppressed because it is too large
Load Diff
165
reportlab/graphics/charts/dotbox.py
Normal file
165
reportlab/graphics/charts/dotbox.py
Normal 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")
|
||||
400
reportlab/graphics/charts/doughnut.py
Normal file
400
reportlab/graphics/charts/doughnut.py
Normal 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')
|
||||
716
reportlab/graphics/charts/legends.py
Normal file
716
reportlab/graphics/charts/legends.py
Normal 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
|
||||
715
reportlab/graphics/charts/linecharts.py
Normal file
715
reportlab/graphics/charts/linecharts.py
Normal 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
|
||||
1150
reportlab/graphics/charts/lineplots.py
Normal file
1150
reportlab/graphics/charts/lineplots.py
Normal file
File diff suppressed because it is too large
Load Diff
82
reportlab/graphics/charts/markers.py
Normal file
82
reportlab/graphics/charts/markers.py
Normal 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
|
||||
1660
reportlab/graphics/charts/piecharts.py
Normal file
1660
reportlab/graphics/charts/piecharts.py
Normal file
File diff suppressed because it is too large
Load Diff
186
reportlab/graphics/charts/slidebox.py
Normal file
186
reportlab/graphics/charts/slidebox.py
Normal 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")
|
||||
408
reportlab/graphics/charts/spider.py
Normal file
408
reportlab/graphics/charts/spider.py
Normal 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')
|
||||
466
reportlab/graphics/charts/textlabels.py
Normal file
466
reportlab/graphics/charts/textlabels.py
Normal 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
|
||||
390
reportlab/graphics/charts/utils.py
Normal file
390
reportlab/graphics/charts/utils.py
Normal 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')
|
||||
233
reportlab/graphics/charts/utils3d.py
Normal file
233
reportlab/graphics/charts/utils3d.py
Normal 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)]]))
|
||||
404
reportlab/graphics/renderPDF.py
Normal file
404
reportlab/graphics/renderPDF.py
Normal 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()
|
||||
761
reportlab/graphics/renderPM.py
Normal file
761
reportlab/graphics/renderPM.py
Normal 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)
|
||||
930
reportlab/graphics/renderPS.py
Normal file
930
reportlab/graphics/renderPS.py
Normal 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)
|
||||
929
reportlab/graphics/renderSVG.py
Normal file
929
reportlab/graphics/renderSVG.py
Normal 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()
|
||||
357
reportlab/graphics/renderbase.py
Normal file
357
reportlab/graphics/renderbase.py
Normal 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__)
|
||||
1
reportlab/graphics/samples/__init__.py
Normal file
1
reportlab/graphics/samples/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__doc__="""Example drawings to review, used in autogenerated docs"""
|
||||
73
reportlab/graphics/samples/bubble.py
Normal file
73
reportlab/graphics/samples/bubble.py
Normal 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')
|
||||
84
reportlab/graphics/samples/clustered_bar.py
Normal file
84
reportlab/graphics/samples/clustered_bar.py
Normal 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')
|
||||
83
reportlab/graphics/samples/clustered_column.py
Normal file
83
reportlab/graphics/samples/clustered_column.py
Normal 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')
|
||||
45
reportlab/graphics/samples/excelcolors.py
Normal file
45
reportlab/graphics/samples/excelcolors.py
Normal 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)
|
||||
|
||||
65
reportlab/graphics/samples/exploded_pie.py
Normal file
65
reportlab/graphics/samples/exploded_pie.py
Normal 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')
|
||||
54
reportlab/graphics/samples/filled_radar.py
Normal file
54
reportlab/graphics/samples/filled_radar.py
Normal 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')
|
||||
83
reportlab/graphics/samples/line_chart.py
Normal file
83
reportlab/graphics/samples/line_chart.py
Normal 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')
|
||||
94
reportlab/graphics/samples/linechart_with_markers.py
Normal file
94
reportlab/graphics/samples/linechart_with_markers.py
Normal 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')
|
||||
66
reportlab/graphics/samples/radar.py
Normal file
66
reportlab/graphics/samples/radar.py
Normal 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')
|
||||
58
reportlab/graphics/samples/runall.py
Normal file
58
reportlab/graphics/samples/runall.py
Normal 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
|
||||
71
reportlab/graphics/samples/scatter.py
Normal file
71
reportlab/graphics/samples/scatter.py
Normal 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')
|
||||
82
reportlab/graphics/samples/scatter_lines.py
Normal file
82
reportlab/graphics/samples/scatter_lines.py
Normal 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')
|
||||
72
reportlab/graphics/samples/scatter_lines_markers.py
Normal file
72
reportlab/graphics/samples/scatter_lines_markers.py
Normal 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')
|
||||
61
reportlab/graphics/samples/simple_pie.py
Normal file
61
reportlab/graphics/samples/simple_pie.py
Normal 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)
|
||||
85
reportlab/graphics/samples/stacked_bar.py
Normal file
85
reportlab/graphics/samples/stacked_bar.py
Normal 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')
|
||||
84
reportlab/graphics/samples/stacked_column.py
Normal file
84
reportlab/graphics/samples/stacked_column.py
Normal 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
1467
reportlab/graphics/shapes.py
Normal file
File diff suppressed because it is too large
Load Diff
296
reportlab/graphics/testdrawings.py
Normal file
296
reportlab/graphics/testdrawings.py
Normal 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__)
|
||||
580
reportlab/graphics/testshapes.py
Normal file
580
reportlab/graphics/testshapes.py
Normal 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())
|
||||
529
reportlab/graphics/widgetbase.py
Normal file
529
reportlab/graphics/widgetbase.py
Normal 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()
|
||||
5
reportlab/graphics/widgets/__init__.py
Normal file
5
reportlab/graphics/widgets/__init__.py
Normal 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'''
|
||||
304
reportlab/graphics/widgets/eventcal.py
Normal file
304
reportlab/graphics/widgets/eventcal.py
Normal 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 & 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()
|
||||
880
reportlab/graphics/widgets/flags.py
Normal file
880
reportlab/graphics/widgets/flags.py
Normal 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()
|
||||
519
reportlab/graphics/widgets/grids.py
Normal file
519
reportlab/graphics/widgets/grids.py
Normal 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')
|
||||
245
reportlab/graphics/widgets/markers.py
Normal file
245
reportlab/graphics/widgets/markers.py
Normal 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')
|
||||
933
reportlab/graphics/widgets/signsandsymbols.py
Normal file
933
reportlab/graphics/widgets/signsandsymbols.py
Normal 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()
|
||||
161
reportlab/graphics/widgets/table.py
Normal file
161
reportlab/graphics/widgets/table.py
Normal 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
29
reportlab/license.txt
Normal 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.
|
||||
#
|
||||
#####################################################################################
|
||||
6
reportlab/pdfbase/__init__.py
Normal file
6
reportlab/pdfbase/__init__.py
Normal 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
|
||||
"""
|
||||
60
reportlab/pdfbase/_can_cmap_data.py
Normal file
60
reportlab/pdfbase/_can_cmap_data.py
Normal 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()
|
||||
|
||||
483
reportlab/pdfbase/_cidfontdata.py
Normal file
483
reportlab/pdfbase/_cidfontdata.py
Normal 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))
|
||||
255
reportlab/pdfbase/_fontdata.py
Normal file
255
reportlab/pdfbase/_fontdata.py
Normal 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
|
||||
28
reportlab/pdfbase/_fontdata_enc_macexpert.py
Normal file
28
reportlab/pdfbase/_fontdata_enc_macexpert.py
Normal 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)
|
||||
|
||||
35
reportlab/pdfbase/_fontdata_enc_macroman.py
Normal file
35
reportlab/pdfbase/_fontdata_enc_macroman.py
Normal 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')
|
||||
22
reportlab/pdfbase/_fontdata_enc_pdfdoc.py
Normal file
22
reportlab/pdfbase/_fontdata_enc_pdfdoc.py
Normal 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")
|
||||
|
||||
15
reportlab/pdfbase/_fontdata_enc_standard.py
Normal file
15
reportlab/pdfbase/_fontdata_enc_standard.py
Normal 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)
|
||||
|
||||
30
reportlab/pdfbase/_fontdata_enc_symbol.py
Normal file
30
reportlab/pdfbase/_fontdata_enc_symbol.py
Normal 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)
|
||||
|
||||
37
reportlab/pdfbase/_fontdata_enc_winansi.py
Normal file
37
reportlab/pdfbase/_fontdata_enc_winansi.py
Normal 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')
|
||||
|
||||
20
reportlab/pdfbase/_fontdata_enc_zapfdingbats.py
Normal file
20
reportlab/pdfbase/_fontdata_enc_zapfdingbats.py
Normal 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)
|
||||
|
||||
229
reportlab/pdfbase/_fontdata_widths_courier.py
Normal file
229
reportlab/pdfbase/_fontdata_widths_courier.py
Normal 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}
|
||||
229
reportlab/pdfbase/_fontdata_widths_courierbold.py
Normal file
229
reportlab/pdfbase/_fontdata_widths_courierbold.py
Normal 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}
|
||||
229
reportlab/pdfbase/_fontdata_widths_courierboldoblique.py
Normal file
229
reportlab/pdfbase/_fontdata_widths_courierboldoblique.py
Normal 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}
|
||||
229
reportlab/pdfbase/_fontdata_widths_courieroblique.py
Normal file
229
reportlab/pdfbase/_fontdata_widths_courieroblique.py
Normal 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
Reference in New Issue
Block a user