pjs/layout/generic/nsImageMap.cpp

827 строки
21 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "nsIImageMap.h"
#include "nsString.h"
#include "nsVoidArray.h"
#include "nsCoord.h"
#include "nsIRenderingContext.h"
#include "nsIPresContext.h"
#include "nsIURL.h"
#include "nsXIFConverter.h"
#include "nsISizeOfHandler.h"
class Area {
public:
Area(const nsString& aBaseURL, const nsString& aHREF,
const nsString& aTarget, const nsString& aAltText,
PRBool aSuppress);
virtual ~Area();
void ParseCoords(const nsString& aSpec);
virtual PRBool IsInside(nscoord x, nscoord y) = 0;
virtual void Draw(nsIPresContext& aCX,
nsIRenderingContext& aRC) = 0;
virtual void GetShapeName(nsString& aResult) const = 0;
void ToHTML(nsString& aResult);
/**
* Translate the content object into the (XIF) XML Interchange Format
* XIF is an intermediate form of the content model, the buffer
* will then be parsed into any number of formats including HTML, TXT, etc.
*/
virtual void BeginConvertToXIF(nsXIFConverter& aConverter) const;
virtual void ConvertContentToXIF(nsXIFConverter& aConverter) const;
virtual void FinishConvertToXIF(nsXIFConverter& aConverter) const;
const nsString& GetBase() const { return mBase; }
const nsString& GetHREF() const { return mHREF; }
const nsString& GetTarget() const { return mTarget; }
const nsString& GetAltText() const { return mAltText; }
PRBool GetSuppress() const { return mSuppressFeedback; }
virtual void SizeOf(nsISizeOfHandler* aHandler) const;
nsString mBase;
nsString mHREF;
nsString mTarget;
nsString mAltText;
nscoord* mCoords;
PRInt32 mNumCoords;
PRBool mSuppressFeedback;
};
Area::Area(const nsString& aBaseURL, const nsString& aHREF,
const nsString& aTarget, const nsString& aAltText,
PRBool aSuppress)
: mBase(aBaseURL), mHREF(aHREF), mTarget(aTarget), mAltText(aAltText),
mSuppressFeedback(aSuppress)
{
mCoords = nsnull;
mNumCoords = 0;
}
Area::~Area()
{
delete [] mCoords;
}
void
Area::SizeOf(nsISizeOfHandler* aHandler) const
{
aHandler->Add(sizeof(*this));
aHandler->Add((size_t) (- ((PRInt32)sizeof(nsString) * 4) ) );
mBase.SizeOf(aHandler);
mHREF.SizeOf(aHandler);
mTarget.SizeOf(aHandler);
mAltText.SizeOf(aHandler);
aHandler->Add(mNumCoords * sizeof(nscoord));
}
// XXX move into nsCRT
#define XP_IS_SPACE(_ch) \
(((_ch) == ' ') || ((_ch) == '\t') || ((_ch) == '\r') || ((_ch) == '\n'))
#include <stdlib.h>
#define XP_ATOI(_s) ::atoi(_s)
// XXX straight copy from laymap.c
static nscoord* lo_parse_coord_list(char *str, PRInt32* value_cnt)
{
char *tptr;
char *n_str;
PRInt32 i, cnt;
PRInt32 *value_list;
/*
* Nothing in an empty list
*/
*value_cnt = 0;
if ((str == NULL)||(*str == '\0'))
{
return((PRInt32 *)NULL);
}
/*
* Skip beginning whitespace, all whitespace is empty list.
*/
n_str = str;
while (XP_IS_SPACE(*n_str))
{
n_str++;
}
if (*n_str == '\0')
{
return((PRInt32 *)NULL);
}
/*
* Make a pass where any two numbers separated by just whitespace
* are given a comma separator. Count entries while passing.
*/
cnt = 0;
while (*n_str != '\0')
{
PRBool has_comma;
/*
* Skip to a separator
*/
tptr = n_str;
while ((!XP_IS_SPACE(*tptr))&&(*tptr != ',')&&(*tptr != '\0'))
{
tptr++;
}
n_str = tptr;
/*
* If no more entries, break out here
*/
if (*n_str == '\0')
{
break;
}
/*
* Skip to the end of the separator, noting if we have a
* comma.
*/
has_comma = PR_FALSE;
while ((XP_IS_SPACE(*tptr))||(*tptr == ','))
{
if (*tptr == ',')
{
if (has_comma == PR_FALSE)
{
has_comma = PR_TRUE;
}
else
{
break;
}
}
tptr++;
}
/*
* If this was trailing whitespace we skipped, we are done.
*/
if ((*tptr == '\0')&&(has_comma == PR_FALSE))
{
break;
}
/*
* Else if the separator is all whitespace, and this is not the
* end of the string, add a comma to the separator.
*/
else if (has_comma == PR_FALSE)
{
*n_str = ',';
}
/*
* count the entry skipped.
*/
cnt++;
n_str = tptr;
}
/*
* count the last entry in the list.
*/
cnt++;
*value_cnt = cnt;
/*
* Allocate space for the coordinate array.
*/
value_list = new nscoord[cnt];
if (value_list == NULL)
{
return((PRInt32 *)NULL);
}
/*
* Second pass to copy integer values into list.
*/
tptr = str;
for (i=0; i<cnt; i++)
{
char *ptr;
ptr = strchr(tptr, ',');
if (ptr != NULL)
{
*ptr = '\0';
}
/*
* Strip whitespace in front of number because I don't
* trust atoi to do it on all platforms.
*/
while (XP_IS_SPACE(*tptr))
{
tptr++;
}
if (*tptr == '\0')
{
value_list[i] = 0;
}
else
{
value_list[i] = (nscoord)XP_ATOI(tptr);
}
if (ptr != NULL)
{
*ptr = ',';
tptr = (char *)(ptr + 1);
}
}
return(value_list);
}
void Area::ParseCoords(const nsString& aSpec)
{
char* cp = aSpec.ToNewCString();
mCoords = lo_parse_coord_list(cp, &mNumCoords);
delete cp;
}
void Area::ToHTML(nsString& aResult)
{
aResult.Truncate();
aResult.Append("<AREA SHAPE=");
nsAutoString shape;
GetShapeName(shape);
aResult.Append(shape);
aResult.Append(" COORDS=\"");
if (nsnull != mCoords) {
PRInt32 i, n = mNumCoords;
for (i = 0; i < n; i++) {
aResult.Append(mCoords[i], 10);
if (i < n - 1) {
aResult.Append(',');
}
}
}
aResult.Append("\" HREF=\"");
aResult.Append(mHREF);
aResult.Append("\"");
if (0 < mTarget.Length()) {
aResult.Append(" TARGET=\"");
aResult.Append(mTarget);
aResult.Append("\"");
}
if (0 < mAltText.Length()) {
aResult.Append(" ALT=\"");
aResult.Append(mAltText);
aResult.Append("\"");
}
if (mSuppressFeedback) {
aResult.Append(" SUPPRESS");
}
aResult.Append('>');
}
/**
* Translate the content object into the (XIF) XML Interchange Format
* XIF is an intermediate form of the content model, the buffer
* will then be parsed into any number of formats including HTML, TXT, etc.
*/
void Area::BeginConvertToXIF(nsXIFConverter& aConverter) const
{
nsAutoString tag("area");
aConverter.BeginStartTag(tag);
nsAutoString name("shape");
nsAutoString shape;
GetShapeName(shape);
aConverter.AddAttribute(name,shape);
nsAutoString coords;
if (nsnull != mCoords) {
PRInt32 i, n = mNumCoords;
for (i = 0; i < n; i++) {
coords.Append(mCoords[i], 10);
if (i < n - 1) {
coords.Append(',');
}
}
}
name.SetString("coords");
aConverter.AddAttribute(name,coords);
name.SetString("href");
aConverter.AddAttribute(name,mHREF);
if (0 < mTarget.Length()) {
name.SetString("target");
aConverter.AddAttribute(name,mTarget);
}
if (0 < mAltText.Length()) {
name.SetString("alt");
aConverter.AddAttribute(name,mTarget);
}
if (mSuppressFeedback) {
name.SetString("suppress");
aConverter.AddAttribute(name);
}
}
void Area::FinishConvertToXIF(nsXIFConverter& aConverter) const
{
nsAutoString tag("area");
aConverter.FinishStartTag(tag,PR_TRUE);
}
void Area::ConvertContentToXIF(nsXIFConverter& aConverter) const
{
// Nothing needs to be done here, all of the logic
// is handled in the start and finish methods
}
//----------------------------------------------------------------------
class DefaultArea : public Area {
public:
DefaultArea(const nsString& aBaseURL, const nsString& aHREF,
const nsString& aTarget, const nsString& aAltText,
PRBool aSuppress);
~DefaultArea();
virtual PRBool IsInside(nscoord x, nscoord y);
virtual void Draw(nsIPresContext& aCX,
nsIRenderingContext& aRC);
virtual void GetShapeName(nsString& aResult) const;
};
DefaultArea::DefaultArea(const nsString& aBaseURL, const nsString& aHREF,
const nsString& aTarget, const nsString& aAltText,
PRBool aSuppress)
: Area(aBaseURL, aHREF, aTarget, aAltText, aSuppress)
{
}
DefaultArea::~DefaultArea()
{
}
PRBool DefaultArea::IsInside(nscoord x, nscoord y)
{
return PR_TRUE;
}
void DefaultArea::Draw(nsIPresContext& aCX, nsIRenderingContext& aRC)
{
}
void DefaultArea::GetShapeName(nsString& aResult) const
{
aResult.Append("default");
}
//----------------------------------------------------------------------
class RectArea : public Area {
public:
RectArea(const nsString& aBaseURL, const nsString& aHREF,
const nsString& aTarget, const nsString& aAltText,
PRBool aSuppress);
~RectArea();
virtual PRBool IsInside(nscoord x, nscoord y);
virtual void Draw(nsIPresContext& aCX,
nsIRenderingContext& aRC);
virtual void GetShapeName(nsString& aResult) const;
};
RectArea::RectArea(const nsString& aBaseURL, const nsString& aHREF,
const nsString& aTarget, const nsString& aAltText,
PRBool aSuppress)
: Area(aBaseURL, aHREF, aTarget, aAltText, aSuppress)
{
}
RectArea::~RectArea()
{
}
PRBool RectArea::IsInside(nscoord x, nscoord y)
{
if (mNumCoords >= 4) { // Note: > is for nav compatabilty
nscoord x1 = mCoords[0];
nscoord y1 = mCoords[1];
nscoord x2 = mCoords[2];
nscoord y2 = mCoords[3];
if ((x1 > x2)|| (y1 > y2)) {
// Can't be inside a screwed up rect
return PR_FALSE;
}
if ((x >= x1) && (x <= x2) && (y >= y1) && (y <= y2)) {
return PR_TRUE;
}
}
return PR_FALSE;
}
void RectArea::Draw(nsIPresContext& aCX, nsIRenderingContext& aRC)
{
if (mNumCoords >= 4) {
float p2t = aCX.GetPixelsToTwips();
nscoord x1 = NSIntPixelsToTwips(mCoords[0], p2t);
nscoord y1 = NSIntPixelsToTwips(mCoords[1], p2t);
nscoord x2 = NSIntPixelsToTwips(mCoords[2], p2t);
nscoord y2 = NSIntPixelsToTwips(mCoords[3], p2t);
if ((x1 > x2)|| (y1 > y2)) {
return;
}
aRC.DrawRect(x1, y1, x2 - x1, y2 - y1);
}
}
void RectArea::GetShapeName(nsString& aResult) const
{
aResult.Append("rect");
}
//----------------------------------------------------------------------
class PolyArea : public Area {
public:
PolyArea(const nsString& aBaseURL, const nsString& aHREF,
const nsString& aTarget, const nsString& aAltText,
PRBool aSuppress);
~PolyArea();
virtual PRBool IsInside(nscoord x, nscoord y);
virtual void Draw(nsIPresContext& aCX,
nsIRenderingContext& aRC);
virtual void GetShapeName(nsString& aResult) const;
};
PolyArea::PolyArea(const nsString& aBaseURL, const nsString& aHREF,
const nsString& aTarget, const nsString& aAltText,
PRBool aSuppress)
: Area(aBaseURL, aHREF, aTarget, aAltText, aSuppress)
{
}
PolyArea::~PolyArea()
{
}
PRBool PolyArea::IsInside(nscoord x, nscoord y)
{
if (mNumCoords >= 6) {
PRInt32 intersects = 0;
nscoord wherex = x;
nscoord wherey = y;
PRInt32 totalv = mNumCoords / 2;
PRInt32 totalc = totalv * 2;
nscoord xval = mCoords[totalc - 2];
nscoord yval = mCoords[totalc - 1];
PRInt32 end = totalc;
PRInt32 pointer = 1;
if ((yval >= wherey) != (mCoords[pointer] >= wherey))
if ((xval >= wherex) == (mCoords[0] >= wherex))
intersects += (xval >= wherex) ? 1 : 0;
else
intersects += ((xval - (yval - wherey) *
(mCoords[0] - xval) /
(mCoords[pointer] - yval)) >= wherex) ? 1 : 0;
// XXX I wonder what this is doing; this is a translation of ptinpoly.c
while (pointer < end) {
yval = mCoords[pointer];
pointer += 2;
if (yval >= wherey) {
while((pointer < end) && (mCoords[pointer] >= wherey))
pointer+=2;
if (pointer >= end)
break;
if ((mCoords[pointer-3] >= wherex) ==
(mCoords[pointer-1] >= wherex)) {
intersects += (mCoords[pointer-3] >= wherex) ? 1 : 0;
} else {
intersects +=
((mCoords[pointer-3] - (mCoords[pointer-2] - wherey) *
(mCoords[pointer-1] - mCoords[pointer-3]) /
(mCoords[pointer] - mCoords[pointer - 2])) >= wherex) ? 1:0;
}
} else {
while((pointer < end) && (mCoords[pointer] < wherey))
pointer+=2;
if (pointer >= end)
break;
if ((mCoords[pointer-3] >= wherex) ==
(mCoords[pointer-1] >= wherex)) {
intersects += (mCoords[pointer-3] >= wherex) ? 1:0;
} else {
intersects +=
((mCoords[pointer-3] - (mCoords[pointer-2] - wherey) *
(mCoords[pointer-1] - mCoords[pointer-3]) /
(mCoords[pointer] - mCoords[pointer - 2])) >= wherex) ? 1:0;
}
}
}
if ((intersects & 1) != 0) {
return PR_TRUE;
}
}
return PR_FALSE;
}
void PolyArea::Draw(nsIPresContext& aCX, nsIRenderingContext& aRC)
{
if (mNumCoords >= 6) {
float p2t = aCX.GetPixelsToTwips();
nscoord x0 = NSIntPixelsToTwips(mCoords[0], p2t);
nscoord y0 = NSIntPixelsToTwips(mCoords[1], p2t);
for (PRInt32 i = 2; i < mNumCoords; i += 2) {
nscoord x1 = NSIntPixelsToTwips(mCoords[i], p2t);
nscoord y1 = NSIntPixelsToTwips(mCoords[i+1], p2t);
aRC.DrawLine(x0, y0, x1, y1);
x0 = x1;
y0 = y1;
}
aRC.DrawLine(x0, y0, mCoords[0], mCoords[1]);
}
}
void PolyArea::GetShapeName(nsString& aResult) const
{
aResult.Append("polygon");
}
//----------------------------------------------------------------------
class CircleArea : public Area {
public:
CircleArea(const nsString& aBaseURL, const nsString& aHREF,
const nsString& aTarget, const nsString& aAltText,
PRBool aSuppress);
~CircleArea();
virtual PRBool IsInside(nscoord x, nscoord y);
virtual void Draw(nsIPresContext& aCX,
nsIRenderingContext& aRC);
virtual void GetShapeName(nsString& aResult) const;
};
CircleArea::CircleArea(const nsString& aBaseURL, const nsString& aHREF,
const nsString& aTarget, const nsString& aAltText,
PRBool aSuppress)
: Area(aBaseURL, aHREF, aTarget, aAltText, aSuppress)
{
}
CircleArea::~CircleArea()
{
}
PRBool CircleArea::IsInside(nscoord x, nscoord y)
{
// Note: > is for nav compatabilty
if (mNumCoords >= 3) {
nscoord x1 = mCoords[0];
nscoord y1 = mCoords[1];
nscoord radius = mCoords[2];
if (radius < 0) {
return PR_FALSE;
}
nscoord dx = x1 - x;
nscoord dy = y1 - y;
nscoord dist = (dx * dx) + (dy * dy);
if (dist <= (radius * radius)) {
return PR_TRUE;
}
}
return PR_FALSE;
}
void CircleArea::Draw(nsIPresContext& aCX, nsIRenderingContext& aRC)
{
if (mNumCoords >= 3) {
float p2t = aCX.GetPixelsToTwips();
nscoord x1 = NSIntPixelsToTwips(mCoords[0], p2t);
nscoord y1 = NSIntPixelsToTwips(mCoords[1], p2t);
nscoord radius = NSIntPixelsToTwips(mCoords[2], p2t);
if (radius < 0) {
return;
}
nscoord x = x1 - radius / 2;
nscoord y = y1 - radius / 2;
nscoord w = 2 * radius;
aRC.DrawEllipse(x, y, w, w);
}
}
void CircleArea::GetShapeName(nsString& aResult) const
{
aResult.Append("circle");
}
//----------------------------------------------------------------------
static NS_DEFINE_IID(kIImageMapIID, NS_IIMAGEMAP_IID);
class ImageMapImpl : public nsIImageMap {
public:
ImageMapImpl(nsIAtom* aTag);
~ImageMapImpl();
NS_DECL_ISUPPORTS
NS_IMETHOD SetName(const nsString& aMapName);
NS_IMETHOD GetName(nsString& aResult);
NS_IMETHOD AddArea(const nsString& aBaseURL,
const nsString& aShape,
const nsString& aCoords,
const nsString& aHREF,
const nsString& aTarget,
const nsString& aAltText,
PRBool aSuppress);
NS_IMETHOD IsInside(nscoord aX, nscoord aY,
nsIURL* aDocURL,
nsString& aAbsURL,
nsString& aTarget,
nsString& aAltText,
PRBool* aSuppress);
NS_IMETHOD IsInside(nscoord aX, nscoord aY);
NS_IMETHOD Draw(nsIPresContext& aCX, nsIRenderingContext& aRC);
NS_IMETHOD SizeOf(nsISizeOfHandler* aHandler) const;
nsString mName;
nsIAtom* mTag;
nsVoidArray mAreas;
};
ImageMapImpl::ImageMapImpl(nsIAtom* aTag)
{
NS_INIT_REFCNT();
mTag = aTag;
NS_IF_ADDREF(aTag);
}
ImageMapImpl::~ImageMapImpl()
{
NS_IF_RELEASE(mTag);
PRInt32 i, n = mAreas.Count();
for (i = 0; i < n; i++) {
Area* area = (Area*) mAreas.ElementAt(i);
delete area;
}
}
NS_IMPL_ISUPPORTS(ImageMapImpl, kIImageMapIID);
NS_IMETHODIMP ImageMapImpl::SetName(const nsString& aMapName)
{
mName = aMapName;
return NS_OK;
}
NS_IMETHODIMP ImageMapImpl::GetName(nsString& aResult)
{
aResult = mName;
return NS_OK;
}
NS_IMETHODIMP ImageMapImpl::AddArea(const nsString& aBaseURL,
const nsString& aShape,
const nsString& aCoords,
const nsString& aHREF,
const nsString& aTarget,
const nsString& aAltText,
PRBool aSuppress)
{
Area* area;
if ((0 == aShape.Length()) || aShape.EqualsIgnoreCase("rect")) {
area = new RectArea(aBaseURL, aHREF, aTarget, aAltText, aSuppress);
} else if (aShape.EqualsIgnoreCase("poly") ||
aShape.EqualsIgnoreCase("polygon")) {
area = new PolyArea(aBaseURL, aHREF, aTarget, aAltText, aSuppress);
} else if (aShape.EqualsIgnoreCase("circle")) {
area = new CircleArea(aBaseURL, aHREF, aTarget, aAltText, aSuppress);
}
else {
area = new DefaultArea(aBaseURL, aHREF, aTarget, aAltText, aSuppress);
}
area->ParseCoords(aCoords);
mAreas.AppendElement(area);
return NS_OK;
}
NS_IMETHODIMP ImageMapImpl::IsInside(nscoord aX, nscoord aY,
nsIURL* aDocURL,
nsString& aAbsURL,
nsString& aTarget,
nsString& aAltText,
PRBool* aSuppress)
{
PRInt32 i, n = mAreas.Count();
for (i = 0; i < n; i++) {
Area* area = (Area*) mAreas.ElementAt(i);
if (area->IsInside(aX, aY)) {
NS_MakeAbsoluteURL(aDocURL, area->mBase, area->mHREF, aAbsURL);
aTarget = area->mTarget;
aAltText = area->mAltText;
*aSuppress = area->mSuppressFeedback;
return NS_OK;
}
}
return NS_NOT_INSIDE;
}
NS_IMETHODIMP ImageMapImpl::IsInside(nscoord aX, nscoord aY)
{
PRInt32 i, n = mAreas.Count();
for (i = 0; i < n; i++) {
Area* area = (Area*) mAreas.ElementAt(i);
if (area->IsInside(aX, aY)) {
if ((area->mHREF).Length() > 0) {
return NS_OK;
}
}
}
return NS_NOT_INSIDE;
}
NS_IMETHODIMP ImageMapImpl::Draw(nsIPresContext& aCX, nsIRenderingContext& aRC)
{
PRInt32 i, n = mAreas.Count();
for (i = 0; i < n; i++) {
Area* area = (Area*) mAreas.ElementAt(i);
area->Draw(aCX, aRC);
}
return NS_OK;
}
NS_IMETHODIMP
ImageMapImpl::SizeOf(nsISizeOfHandler* aHandler) const
{
aHandler->Add(sizeof(*this));
aHandler->Add((size_t) (- ((PRInt32) sizeof(mName))));
mName.SizeOf(aHandler);
if (!aHandler->HaveSeen(mTag)) {
mTag->SizeOf(aHandler);
}
aHandler->Add((size_t) (- ((PRInt32) sizeof(mAreas))));
mAreas.SizeOf(aHandler);
PRInt32 i, n = mAreas.Count();
for (i = 0; i < n; i++) {
Area* area = (Area*) mAreas[i];
area->SizeOf(aHandler);
}
return NS_OK;
}
NS_HTML nsresult NS_NewImageMap(nsIImageMap** aInstancePtrResult,
nsIAtom* aTag)
{
NS_PRECONDITION(nsnull != aInstancePtrResult, "null ptr");
if (nsnull == aInstancePtrResult) {
return NS_ERROR_NULL_POINTER;
}
ImageMapImpl* it = new ImageMapImpl(aTag);
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
return it->QueryInterface(kIImageMapIID, (void**) aInstancePtrResult);
}