# Copyright (C) 2002-2006 Alexei Gilchrist and Paul Cochrane
#
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# $Id: base.py,v 1.30 2006/06/06 12:39:48 paultcochrane Exp $
"""
Base objects
"""
__revision__ = '$Revision: 1.30 $'
import copy
import types
# -------------------------------------------------------------------------
class PyScriptError(Exception):
"""
Handles a PyScript error
"""
pass
class FontError(Exception):
"""
Handles a font error
"""
pass
# -------------------------------------------------------------------------
UNITS = {
"inch":72,
"points":1,
"cm":28.346,
"mm":2.8346
}
# -------------------------------------------------------------------------
class PsObj(object):
"""
Base Class that most pyscript objects should subclass
"""
def __init__(self, **options):
'''
can pass a dict of atributes to set
'''
object.__init__(self)
self(**options)
def __call__(self, **options):
'''
Set a whole lot of attributes in one go
eg::
obj.set(bg=Color(.3),linewidth=2)
@return: self
@rtype: self
'''
# first do non-property ones
# this will raise an exception if class doesn't have attribute
# I think this is good.
prop = []
for key, value in options.items():
if isinstance(eval('self.__class__.%s'%key), property):
prop.append((key, value))
else:
self.__class__.__setattr__(self, key, value)
# now the property ones
# (which are functions of the non-property ones)
for key, value in prop:
self.__class__.__setattr__(self, key, value)
# for convenience return a reference to us
return self
def copy(self, **options):
'''
return a copy of this object
with listed attributes modified
eg::
newobj=obj.copy(bg=Color(.3))
@rtype: self
'''
# here for convenience
obj = copy.deepcopy(self)
obj(**options)
return obj
def __repr__(self):
'''
Return a representation of this object
@rtype: string
'''
return str(self.__class__)
def __str__(self):
'''
return actual postscript string to generate object
@rtype: string
'''
return self.prebody()+self.body()+self.postbody()
def prebody(self):
'''
convenience function to allow clean subclassing
@rtype: string
'''
return ''
def body(self):
'''
subclasses should overide this for generating postscipt code
@rtype: string
'''
return ''
def postbody(self):
'''
convenience function to allow clean subclassing
@rtype: string
'''
return ''
def bbox(self):
"""
return objects bounding box
this can be a Null Bbox() if object doesn't
draw anything on the page.)
NB that the bbox should be dynamically calculated and take
into account the transformation matrix if it applies
@return: A bounding box object
@rtype: Bbox
"""
return ''
# -------------------------------------------------------------------------
class Color(PsObj):
"""
Class to encode a postscript color
There are five ways to specify the color:
- Color(C,M,Y,K) =CMYKColor
- Color(R,G,B) =RGBColor
- Color(G) = Gray
- Color('yellow') etc, see L{COLORS}
- Color('#FF0000') Hex string, must start with '#'
"""
COLORS = {
"aliceblue":(240, 248, 255),
"antiquewhite":(250, 235, 215),
"aqua":(0, 255, 255),
"aquamarine":(127, 255, 212),
"azure":(240, 255, 255),
"beige":(245, 245, 220),
"bisque":(255, 228, 196),
"black":(0, 0, 0),
"blanchedalmond":(255, 235, 205),
"blue":(0, 0, 255),
"blueviolet":(138, 43, 226),
"brown":(165, 42, 42),
"burlywood":(222, 184, 135),
"cadetblue":(95, 158, 160),
"chartreuse":(127, 255, 0),
"chocolate":(210, 105, 30),
"coral":(255, 127, 80),
"cornflowerblue":(100, 149, 237),
"cornsilk":(255, 248, 220),
"crimson":(220, 20, 60),
"cyan":(0, 255, 255),
"darkblue":(0, 0, 139),
"darkcyan":(0, 139, 139),
"darkgoldenrod":(184, 134, 11),
"darkgray":(169, 169, 169),
"darkgrey":(169, 169, 169),
"darkgreen":(0, 100, 0),
"darkkhaki":(189, 183, 107),
"darkmagenta":(139, 0, 139),
"darkolivegreen":(85, 107, 47),
"darkorange":(255, 140, 0),
"darkorchid":(153, 50, 204),
"darkred":(139, 0, 0),
"darksalmon":(233, 150, 122),
"darkseagreen":(143, 188, 143),
"darkslateblue":(72, 61, 139),
"darkslategray":(47, 79, 79),
"darkslategrey":(47, 79, 79),
"darkturquoise":(0, 206, 209),
"darkviolet":(148, 0, 211),
"deeppink":(255, 20, 147),
"deepskyblue":(0, 191, 255),
"dimgray":(105, 105, 105),
"dimgrey":(105, 105, 105),
"dodgerblue":(30, 144, 255),
"firebrick":(178, 34, 34),
"floralwhite":(255, 250, 240),
"forestgreen":(34, 139, 34),
"fuchsia":(255, 0, 255),
"gainsboro":(220, 220, 220),
"ghostwhite":(248, 248, 255),
"gold":(255, 215, 0),
"goldenrod":(218, 165, 32),
"gray":(128, 128, 128),
"grey":(128, 128, 128),
"green":(0, 128, 0),
"greenyellow":(173, 255, 47),
"honeydew":(240, 255, 240),
"hotpink":(255, 105, 180),
"indianred":(205, 92, 92),
"indigo":(75, 0, 130),
"ivory":(255, 255, 240),
"khaki":(240, 230, 140),
"lavender":(230, 230, 250),
"lavenderblush":(255, 240, 245),
"lawngreen":(124, 252, 0),
"lemonchiffon":(255, 250, 205),
"lightblue":(173, 216, 230),
"lightcoral":(240, 128, 128),
"lightcyan":(224, 255, 255),
"lightgoldenrod":(250, 250, 210),
"lightgreen":(144, 238, 144),
"lightgray":(211, 211, 211),
"lightgrey":(211, 211, 211),
"lightpink":(255, 182, 193),
"lightsalmon":(255, 160, 122),
"lightseagreen":(32, 178, 170),
"lightskyblue":(135, 206, 250),
"lightslategray":(119, 136, 153),
"lightslategrey":(119, 136, 153),
"lightsteelblue":(176, 196, 222),
"lightyellow":(255, 255, 224),
"lime":(0, 255, 0),
"limegreen":(50, 205, 50),
"linen":(250, 240, 230),
"magenta":(255, 0, 255),
"maroon":(128, 0, 0),
"mediumaquamarine":(102, 205, 170),
"mediumblue":(0, 0, 205),
"mediumorchid":(186, 85, 211),
"mediumpurple":(147, 112, 219),
"mediumseagreen":(60, 179, 113),
"mediumslateblue":(123, 104, 238),
"mediumspringgreen":(0, 250, 154),
"mediumturquoise":(72, 209, 204),
"mediumvioletred":(199, 21, 133),
"midnightblue":(25, 25, 112),
"mintcream":(245, 255, 250),
"mistyrose":(255, 228, 225),
"moccasin":(255, 228, 181),
"navajowhite":(255, 222, 173),
"navy":(0, 0, 128),
"oldlace":(253, 245, 230),
"olive":(128, 128, 0),
"olivedrab":(107, 142, 35),
"orange":(255, 165, 0),
"orangered":(255, 69, 0),
"orchid":(218, 112, 214),
"palegoldenrod":(238, 232, 170),
"palegreen":(152, 251, 152),
"paleturquoise":(175, 238, 238),
"palevioletred":(219, 112, 147),
"papayawhip":(255, 239, 213),
"peachpuff":(255, 218, 185),
"peru":(205, 133, 63),
"pink":(255, 192, 203),
"plum":(221, 160, 221),
"powderblue":(176, 224, 230),
"purple":(128, 0, 128),
"red":(255, 0, 0),
"rosybrown":(188, 143, 143),
"royalblue":(65, 105, 225),
"saddlebrown":(139, 69, 19),
"salmon":(250, 128, 114),
"sandybrown":(244, 164, 96),
"seagreen":(46, 139, 87),
"seashell":(255, 245, 238),
"sienna":(160, 82, 45),
"silver":(192, 192, 192),
"skyblue":(135, 206, 235),
"slateblue":(106, 90, 205),
"slategray":(112, 128, 144),
"slategrey":(112, 128, 144),
"snow":(255, 250, 250),
"springgreen":(0, 255, 127),
"steelblue":(70, 130, 180),
"tan":(210, 180, 140),
"teal":(0, 128, 128),
"thistle":(216, 191, 216),
"tomato":(255, 99, 71),
"turquoise":(64, 224, 208),
"violet":(238, 130, 238),
"wheat":(245, 222, 179),
"white":(255, 255, 255),
"whitesmoke":(245, 245, 245),
"yellow":(255, 255, 0),
"yellowgreen":(154, 205, 50),
}
def __init__(self, *col, **options):
"""
Initialisation of the colour object
@param col:
@type col:
@param options:
@type options:
"""
# some sanity checks
if type(col[0]) == types.StringType:
if col[0][0] == "#" and len(col[0]) == 7:
# hex color scheme eg #A0FF00
col = (int(col[0][1:3], 16) / 255.0,
int(col[0][3:5], 16) / 255.0,
int(col[0][5:7], 16) / 255.0)
else:
# named color
col = col[0].lower()
col = self.COLORS[col]
# renormalise so that values are in [0,1]
col = (col[0]/255., col[1]/255., col[2]/255.)
assert len(col) > 0 and len(col) < 5
for ii in col:
assert ii >= 0 and ii <= 1
self.color = col
PsObj.__init__(self, **options)
def body(self):
"""
Returns the body of the postscript for the Color object
"""
color = self.color
if len(color) == 1:
# grayscale color
ps = " %g setgray " % color
elif len(color) == 3:
# rgb color
ps = " %g %g %g setrgbcolor " % color
elif len(color) == 4:
# cmyk color
ps = " %g %g %g %g setcmykcolor " % color
else:
raise ValueError, "Unknown color"
return ps
def __mul__(self, other):
'''
colors can be multiplied by a numeric factor.
factors less than 1 will darken the colors,
factors grater than will will lighten the colors.
(this depends on how the colors where specified)
eg::
Color(.2,.6,.6)*.5 = Color(.1,.3,.3)
'''
assert other >= 0 #and other<=1
newcol = []
for ii in self.color:
newcol.append(min(1, ii*other))
# XXX this breaks things:
#return Color(tuple(newcol))
return apply(Color, tuple(newcol))
# -------------------------------------------------------------------------
class Dash(PsObj):
"""
Class to encode postscript dash pattern
Argument is a list of lengths for alternating dash and spaces
eg:
Dash(3) 3 on, 3 off, ...
Dash(2,offset=1) 1 on, 2 off, 2 on, 2 off, ...
Dash(2,1) 2 on, 1 off, 2 on, 1 off, ...
Dash(3,5,offset=6) 2 off, 3 on, 5 off, 3 on, 5 off, ...
Dash(3,1,1,1) 3 on, 1 off, 1 on, 1 off, 3 on, ...
@cvar offset: initially fastforward this much into the pattern
"""
pattern = (2, )
offset = 0
def __init__(self, *args, **options):
"""
Initialisation of the Dash object
"""
if len(args) > 0:
self.pattern = args
PsObj.__init__(self, **options)
def body(self):
"""
Returns the postscript of a Dash object
"""
# if we don't start with a space horrible things happen
pat = ""
delim = ' '
pat = "[ "+ delim.join(["%g" % l for l in self.pattern])
pat = pat + "] %g setdash " % self.offset
return pat
# vim: expandtab shiftwidth=4:
|