"""Support for client-side image maps."""
import string
class Shape:
"""shaped regions for client-side image maps."""
def __init__(self, kind, coords, url, target=""):
self.kind = kind
self.coords = coords
self.url = url
self.target = target
def pointin(self, x, y):
"""predicate: Are x,y coordinates within region?"""
isin = 0
if self.kind == 'rect':
if self.coords[0][0] <= x <= self.coords[1][0] and \
self.coords[0][1] <= y <= self.coords[1][1]:
isin = 1
elif self.kind == 'circle':
# is the distance from the point to the center of the
# circle less than the radius?
distance_squared = pow((self.coords[0][0] - x), 2) + \
pow((self.coords[0][1] - y), 2)
if distance_squared <= pow(self.coords[1], 2):
isin = 1
elif self.kind == 'poly':
isin = self.poly_pointin(x, y)
elif self.kind == 'default':
isin = 1
return isin
def poly_pointin(self, x, y):
"""Is point (x,y) inside polygon with vertices coords?
From C code by Paul Bourke at
<http://www.auckland.ac.nz/arch/pdbourke/geometry/insidepoly.html>
Determining whether or not a point lies on the interior of a polygon.
The algorithm is a little pesky because a point on an edge of the
polygon doesn't appear to be treated as *in* the polygon. Not doing
anything about this at the moment.
"""
counter = 0
p1 = self.coords[0]
for i in range(1,len(self.coords)):
p2 = self.coords[i]
if y > min(p1[1], p2[1]):
if y <= max(p1[1], p2[1]):
if x <= max (p1[0], p2[0]):
if p1[1] != p2[1]:
xintersect = \
(y-p1[1])*(p2[0]-p1[0])/(p2[1]-p1[1])+p1[0]
if p1[0] == p2[0] or x <= xintersect:
counter = counter + 1
p1 = p2
return counter % 2
class MapInfo:
"""Holds shapes during parsing.
The shapes are copied into a MapThunk object when the map is used.
"""
def __init__(self, name):
self.name = name
self.shapes = []
def add_shape(self, kind, coords, url, target=""):
self.shapes.append(Shape(kind, coords, url, target))
class MapThunk:
"""Map interface for an ImageWindow, will wait for MAP to be parsed.
The <MAP> tag may not have been parsed by the time the user clicks
on the image, particularly if the USEMAP attribute specifies a MAP
in a different page. Initially, the map has no shapes and it waits
until the method url() is called, which calls force to load the
shapes from the parser. If force() fails, then url returns None
and the next call to url() will also invoke force().
get_shape() memoizes the shape object at a particular (x,y)
coordinate because the lookup could be slow when there are many
shapes. not sure if this is necessary/desirable.
"""
def __init__(self, context, name):
"""Link MapThunk to the context containing the map."""
self.context = context
self.name = name
self.shapes = []
self.waiting = 1
self.memo = {}
def force(self):
"""Try to load shapes from the context."""
try:
map = self.context.image_maps[self.name]
except KeyError:
pass
else:
self.shapes = map.shapes
self.waiting = 0
def url(self, x, y):
"""Get url associated with shape at (x,y)."""
# first check to see if the map has been parsed
if self.waiting == 1:
self.force()
if self.waiting == 1:
return None, None
# get the shape and return url
shape = self.get_shape(x, y)
if shape:
return shape.url, shape.target
else:
return None, None
def get_shape(self, x, y):
"""Get shape at coords (x,y)."""
try:
# memoize good for lots of shapes
return self.memo[(x,y)]
except KeyError:
# does this iterate through in order?
# it should so that overlapping shapes are handled properly
for shape in self.shapes:
if shape.pointin(x, y) == 1:
self.memo[(x,y)] = shape
return shape
return None
|