/* AbiWord
 * Copyright (C) 1998 AbiSource, Inc.
 * Copyright (C) 2002 Tomas Frydrych, <tomas@frydrych.uklinux.net>
 *
 * 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.
 */

#include <ctype.h>
#include <math.h>
#include <string.h>

#include "xap_App.h"
#include "xap_Prefs.h"
#include "xap_EncodingManager.h"
#include "gr_Graphics.h"
#include "gr_CharWidths.h"
#include "ut_assert.h"
#include "ut_string.h"
#include "ut_units.h"
#include "ut_sleep.h"
#include "ut_growbuf.h"
#include "ut_debugmsg.h"
#include "ut_OverstrikingChars.h"
#include "ut_TextIterator.h"
#include "gr_Caret.h"
#include "gr_RenderInfo.h"

// static class member initializations
UT_uint32 GR_Font::s_iAllocCount = 0;
UT_VersionInfo GR_Graphics::s_Version;

GR_Font::GR_Font()
	:m_pCharWidths(NULL)
{
	s_iAllocCount++;
	m_iAllocNo = s_iAllocCount;
}

GR_Font::~GR_Font()
{
	// need this so children can clean up
}

/*!
  Return the hash key used by the cache to fetch the font
  This method may be overridden to compute it in real time if needed
 */
const UT_String & GR_Font::hashKey(void) const
{
	return m_hashKey;
}

/*!
	Return the char width from the cache.
	Compute the width if needed, and cache it.
 */
UT_sint32 GR_Font::getCharWidthFromCache (UT_UCSChar c) const
{
	// the way GR_CharWidthsCache is implemented will cause problems
	// fro any graphics plugin that wants to use the cache -- we will
	// need to instantiate the cache into a static member of
	// GR_Graphics so that the plugin could get to it without calling
	// the static getCharWidthCache()
#ifndef ABI_GRAPHICS_PLUGIN_NO_WIDTHS
	// first of all, handle 0-width spaces ...
	if(c == 0xFEFF || c == 0x200B || c == UCS_LIGATURE_PLACEHOLDER)
		return 0;
	
	UT_sint32	iWidth = GR_CW_UNKNOWN;

	if (m_pCharWidths == NULL) {
		m_pCharWidths = GR_CharWidthsCache::getCharWidthCache()->getWidthsForFont(this);
	}
	iWidth = m_pCharWidths->getWidth(c);
	if (iWidth == GR_CW_UNKNOWN) {
		iWidth = measureUnremappedCharForCache(c);
		m_pCharWidths->setWidth(c, iWidth);
	}

	return iWidth;
#else
	UT_return_val_if_fail(UT_NOT_IMPLEMENTED,0);
#endif
}

bool GR_Font::doesGlyphExist(UT_UCS4Char g)
{
	if(getCharWidthFromCache(g) == GR_CW_ABSENT)
	{
		UT_DEBUGMSG(("GR_Font::doesGlyphExist: glyph 0x%04x absent from font\n",g));
		return false;
	}
	
	return true;
}

/*!
	Implements a GR_CharWidths.
	Override if you which to instanciate a subclass.
 */
GR_CharWidths* GR_Font::newFontWidths(void) const
{
#ifndef ABI_GRAPHICS_PLUGIN_NO_WIDTHS
	return new GR_CharWidths();
#else
	return NULL;
#endif
}

GR_Graphics::GR_Graphics()
	: m_pApp(NULL),
	  m_iZoomPercentage(100),
	  m_iFontAllocNo(0),
	  m_pRect(NULL),
	  m_pCaret(NULL),
	  m_bIsPortrait(true),
	  m_bSpawnedRedraw(false),
	  m_bExposePending(false),
	  m_bIsExposedAreaAccessed(false),
	  m_bDontRedraw(false),
	  m_bDoMerge(false),
	  m_iPrevYOffset(0),
	  m_iPrevXOffset(0),
	  m_hashFontCache(19),
	  m_paintCount(0)
{
}

GR_Font* GR_Graphics::findFont(const char* pszFontFamily,
							   const char* pszFontStyle,
							   const char* pszFontVariant,
							   const char* pszFontWeight,
							   const char* pszFontStretch,
							   const char* pszFontSize)
{
	GR_Font * pFont = NULL;

	// NOTE: we currently favor a readable hash key to make debugging easier
	// TODO: speed things up with a smaller key (the three AP pointers?)
	UT_String key;

	UT_String_sprintf(key,"%s;%s;%s;%s;%s;%s",pszFontFamily, pszFontStyle, pszFontVariant, pszFontWeight, pszFontStretch, pszFontSize);
	GR_Font *pEntry = m_hashFontCache.pick(key.c_str());
	if (!pEntry)
	{
		// TODO -- note that we currently assume font-family to be a single name,
		// TODO -- not a list.  This is broken.

		pFont = _findFont(pszFontFamily, pszFontStyle, pszFontVariant, pszFontWeight, pszFontStretch, pszFontSize);
		UT_ASSERT(pFont);
		xxx_UT_DEBUGMSG(("Insert font %x in gr_Graphics cache \n",pFont));
		// add it to the cache
		
		if(pFont)
			m_hashFontCache.insert(key.c_str(), pFont);
	}
	else
	{
		pFont = pEntry;
	}
	return pFont;
}

GR_Graphics::~GR_Graphics()
{
	DELETEP(m_pCaret);
}

void GR_Graphics::_destroyFonts ()
{
	m_hashFontCache.purgeData();
	m_hashFontCache.clear ();
}

/*!
 * WARNING! WARNING! WARNING!
 * Only gr_UnixGraphics should call this!
 * Because xap_UnixFontManager "owns" it's fonts, keeps it's own cache of them
 * and will happily destroy them. If it does this, the cache of pointers here
 * is no longer valid.
 * It would be better to simply remove the font from this cache too but the
 * key used in the xap_UnixFontManager class is different fromt the key used
 * here.
 * Until a better solution appears I'll leave this here. Other classes should
 * use _destroyFonts to remove the cache. 
 */
void GR_Graphics::invalidateCache(void)
{
	m_hashFontCache.clear ();
}

void GR_Graphics::beginPaint ()
{
	if (m_paintCount == 0)
		_beginPaint ();

	m_paintCount++;
}

void GR_Graphics::endPaint ()
{
	m_paintCount--;

	if (m_paintCount == 0)
		_endPaint ();
}

UT_sint32 GR_Graphics::tdu(UT_sint32 layoutUnits) const
{
	double d = (static_cast<double>(layoutUnits) * static_cast<double>(getDeviceResolution()) * static_cast<double>(getZoomPercentage())) / (100. * static_cast<double>(getResolution())) + 0.1;
	return static_cast<UT_sint32>(d);
}

/*!
 * This method converts to device units while taking account of the X-scroll
 * offset. This will always give the exact same logical location on the screen
 * no matter what the X-scroll offset is. This fixes off-by-1-pixel bugs in X.
 */
UT_sint32 GR_Graphics::_tduX(UT_sint32 layoutUnits) const
{
	return tdu(layoutUnits+getPrevXOffset()) - tdu(getPrevXOffset());
}

/*!
 * This method converts to device units while taking account of the Y-scroll
 * offset. This will always give the exact same logical location on the screen
 * no matter what the Y-scroll offset is. This fixes off-by-1-pixel bugs in Y.
 */
UT_sint32 GR_Graphics::_tduY(UT_sint32 layoutUnits) const
{
	return tdu(layoutUnits+getPrevYOffset()) - tdu(getPrevYOffset());
}

/*!
 * This method converts rectangle widths and heights to device units while 
 * taking account rounding down errors.
 * This fixes off-by-1-pixel-bugs in Rectangle widths and heights.
 */
UT_sint32 GR_Graphics::_tduR(UT_sint32 layoutUnits) const
{
	UT_sint32 idh = tdu(layoutUnits);
	if(tlu(idh) < layoutUnits)
	{
		idh += 1;
	}
	return idh;
}

UT_sint32 GR_Graphics::tlu(UT_sint32 deviceUnits) const
{
	return static_cast<UT_sint32>((static_cast<double>(deviceUnits) * static_cast<double>(getResolution()) * 100.) / (static_cast<double>(getDeviceResolution()) * static_cast<double>(getZoomPercentage())));;
}

double GR_Graphics::tduD(double layoutUnits) const
{
	return (layoutUnits * static_cast<double>(getDeviceResolution()) * static_cast<double>(getZoomPercentage())) / (100.0 * static_cast<double>(getResolution()));
}

double GR_Graphics::tluD(double deviceUnits) const
{
	return (deviceUnits * static_cast<double>(getResolution()) / static_cast<double>(getDeviceResolution())) * 100.0 / static_cast<double>(getZoomPercentage());
}

UT_sint32	GR_Graphics::ftlu(UT_sint32 fontUnits) const
{
	UT_sint32 itmp = fontUnits * (UT_sint32)getResolution();
	return (itmp/ (UT_sint32)getDeviceResolution());
}

double	GR_Graphics::ftluD(double fontUnits) const
{
	return (fontUnits * static_cast<double>(getResolution()) / static_cast<double>(getDeviceResolution()));
}

void GR_Graphics::setLineProperties ( double    inWidthPixels, 
				      JoinStyle inJoinStyle,
				      CapStyle  inCapStyle,
				      LineStyle inLineStyle )
{
  UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
}

UT_uint32 GR_Graphics::getMaxCharacterWidth(const UT_UCSChar*s, UT_uint32 Length)
{
	UT_GrowBufElement *pWidths = new UT_GrowBufElement[Length];

	measureString(s, 0, Length, pWidths);

	UT_sint32 MaxWidth = 0;

	for(UT_uint32 i = 0; i < Length; i++)
	{
		if(pWidths[i] > MaxWidth)
			MaxWidth = pWidths[i];
	}

	DELETEPV(pWidths);

	return MaxWidth;
}

UT_uint32 GR_Graphics::measureString(const UT_UCSChar* s, int iOffset,
										 int num,  UT_GrowBufElement* pWidths)
{
	// Generic base class version defined in terms of measureUnRemappedChar().
	// Platform versions can roll their own if it makes a performance difference.
	UT_ASSERT(s);

	UT_sint32 stringWidth = 0, charWidth;
	for (int i = 0; i < num; i++)
    {
		UT_UCSChar currentChar = s[i + iOffset];

		{
			charWidth = measureUnRemappedChar(currentChar);

			if(charWidth == GR_CW_UNKNOWN || charWidth ==GR_CW_ABSENT)
				charWidth = 0;
			else if(UT_isOverstrikingChar(currentChar) != UT_NOT_OVERSTRIKING && charWidth > 0)
				charWidth = -charWidth;
			
			// if the widths is < 0 we are dealing with an
			// overstriking character, which does not count for
			// the overall width
			if(charWidth > 0)
				stringWidth += charWidth;
		}

		if (pWidths)
			pWidths[i] = charWidth;
    }
	return stringWidth;
}


void GR_Graphics::setZoomPercentage(UT_uint32 iZoom)
{
	UT_ASSERT(iZoom > 0);

	m_iZoomPercentage = iZoom;

	// invalidate stored font allocation number (change of zoom
	// requires font of different size to be loaded into the device
	// context)
	m_iFontAllocNo = 0xffffffff;
}


const char * GR_Graphics::invertDimension(UT_Dimension dim, double dValue) const
{
	// return pointer to static buffer -- use it quickly.

	double dInches = dValue / UT_LAYOUT_RESOLUTION;

	return UT_convertInchesToDimensionString( dim, dInches);
}

bool GR_Graphics::scaleDimensions(const char * szLeftIn, const char * szWidthIn,
									 UT_uint32 iWidthAvail,
									 UT_sint32 * piLeft, UT_uint32 * piWidth) const
{
	/* Scale the given left-offset and width using the width available.
	** Compute the actual left-offset and actual width used.
	** We allow the given left-offset to be a number.
	** We allow the given width to be a number or "*"; where "*" indicates
	** we take all remaining space available.
	**
	** NOTE: This routine can also be used for vertical calculations.
	*/

	UT_ASSERT(szLeftIn);
	UT_ASSERT(szWidthIn);

	UT_sint32 iLeft = UT_convertToLogicalUnits(szLeftIn);
	UT_uint32 iWidth;

	if (szWidthIn[0] == '*')
		iWidth = iWidthAvail - iLeft;
	else
		iWidth = UT_convertToLogicalUnits(szWidthIn);

	if (piLeft)
		*piLeft = iLeft;
	if (piWidth)
		*piWidth = iWidth;

	return true;
}

void GR_Graphics::flush(void)
{
	// default implementation does nothing
}
/*!
 * Draw the specified image at the location specified in local units 
 * (xDest,yDest). xDest and yDest are in logical units.
 */
void GR_Graphics::drawImage(GR_Image* pImg, UT_sint32 xDest, UT_sint32 yDest)
{
   if (pImg)
     pImg->render(this, xDest, yDest);
}

/*!
 * Create a new image from the Raster rgba byte buffer defined by pBB.
 * The dimensions of iWidth and iHeight are in logical units but the image
 * doesn't scale if the resolution or zoom changes. Instead you must create
 * a new image.
 */
GR_Image* GR_Graphics::createNewImage(const char* pszName, const UT_ByteBuf* pBB, UT_sint32 iDisplayWidth, UT_sint32 iDisplayHeight, GR_Image::GRType iType)
{
   GR_VectorImage * vectorImage = NULL;

   if (iType == GR_Image::GRT_Unknown) {
      if (GR_Image::getBufferType(pBB) == GR_Image::GRT_Vector)
	vectorImage = new GR_VectorImage(pszName);
   }
   else if (iType == GR_Image::GRT_Vector) {
      vectorImage = new GR_VectorImage(pszName);
   }

   if (vectorImage) {
      vectorImage->convertFromBuffer(pBB, iDisplayWidth, iDisplayHeight);
   }

   return vectorImage;
}

bool GR_Graphics::_PtInPolygon(UT_Point * pts,UT_uint32 nPoints,UT_sint32 x,UT_sint32 y)
{
    UT_uint32 i,j;
    bool bResult = false;
    for (i = 0,j = nPoints - 1;i < nPoints;j = i++){
        if ((((pts[i].y <= y) && (y < pts[j].y)) || ((pts[j].y <= y) && (y < pts[i].y))) &&
            (x < (pts[j].x - pts[i].x) * (y - pts[i].y) / (pts[j].y - pts[i].y) + pts[i].x))
        {
            bResult = !bResult;
        }
    }
    return (bResult);
}

void GR_Graphics::polygon(UT_RGBColor& c,UT_Point *pts,UT_uint32 nPoints)
{
    UT_sint32 minX,maxX,minY,maxY,x,y;
    minX = maxX = pts[0].x;
    minY = maxY = pts[0].y;
    for(UT_uint32 i = 0;i < nPoints - 1;i++){
        minX = UT_MIN(minX,pts[i].x);
        maxX = UT_MAX(maxX,pts[i].x);
        minY = UT_MIN(minY,pts[i].y);
        maxY = UT_MAX(maxY,pts[i].y);
    }
    for(x = minX;x <= maxX;x++){
        for(y = minY;y <= maxY;y++){
            if(_PtInPolygon(pts,nPoints,x,y)){
                fillRect(c,x,y,1,1);
            }
        }
    }
 }

/*!
 * Hand shaking variables to let the App know when expose events are being
 * handled.
 * Returns true if the exposed redraw method is running.
 */
const bool  GR_Graphics::isSpawnedRedraw(void) const
{
	return m_bSpawnedRedraw;
}

/*!
 * sets/clears the redraw running variable
 */
void  GR_Graphics::setSpawnedRedraw( bool exposeState)
{
	m_bSpawnedRedraw = exposeState;
}

/*!
 * Informs if there are unprocessed expose information present
 * Returns true is so.
 */
const bool GR_Graphics::isExposePending(void) const
{
	return m_bExposePending;
}

/*!
 * sets the exposed Pending state.
 */
void GR_Graphics::setExposePending(bool exposeState)
{
	m_bExposePending = exposeState;
	if(!exposeState)
	{
		m_PendingExposeArea = m_RecentExposeArea;
	}
}

/*!
 * Sets the Most recent expose rectangle to something sane after repaint.
 *
 */
void GR_Graphics::setRecentRect(UT_Rect * pRect)
{
	m_RecentExposeArea = *pRect;
}


/*!
 * Informs if a process is accessing the global merged expose area.
 * Returns true is so.
 */
const bool GR_Graphics::isExposedAreaAccessed(void) const
{
	return m_bIsExposedAreaAccessed;
}

/*!
 * sets the varaible explaining the state of the Merged area
 */
void GR_Graphics::setExposedAreaAccessed(bool exposeState)
{
	m_bIsExposedAreaAccessed = exposeState;
}

/*!
 * Methods to manipulate the expose rectangle.
 */

/*!
 * Set values inside the PendingArea Rectangle
\param x the x-coord of the upper left corner.
\param y the y-coord of the upper left corner.
\param width the width of the rectangle.
\param height the height of the rectangle.
*/
void  GR_Graphics::setPendingRect( UT_sint32 x, UT_sint32 y, UT_sint32 width, UT_sint32 height)
{
	m_PendingExposeArea.set(x,y,width,height);
}

/*!
 * Do a union of the current rectangle with the one presented in the
 * parameter list. The makes the new rectangle the smallest possible that
 * covers both rectangles.
\param  UT_Rect * pRect pointer to the rectangle to merge with.
*/
void  GR_Graphics::unionPendingRect( UT_Rect * pRect)
{
	m_PendingExposeArea.unionRect(pRect);
}

/*!
\returns a const pointer to the PendingExposeArea.
*/
const UT_Rect * GR_Graphics::getPendingRect(void) const
{
	return & m_PendingExposeArea;
}

/*
 * Hand shaking fail safe variable sto make sure we don'tr repaint till
 * we're absolueltely ready.
 */
void  GR_Graphics::setDontRedraw(bool bDontRedraw)
{
	m_bDontRedraw = bDontRedraw;
}

/*
 * Hand shaking fail safe variable sto make sure we don'tr repaint till
 * we're absolueltely ready.
 */
bool GR_Graphics::isDontRedraw(void)
{
	return m_bDontRedraw;
}

/*!
 * Alternate method to tell the doRepaint to merge the next expose so that
 * expands of exposed area due to scrolls can be merged without doing a
 * display update.
 \returns the doMerge boolean (set from scroll)
*/
bool GR_Graphics::doMerge(void) const
{
	return m_bDoMerge;
}

/*!
 * Sets the do merge boolean
 * Set for scroll, clear from doRepaint
 */
void GR_Graphics::setDoMerge( bool bMergeState)
{
	m_bDoMerge = bMergeState;
}

/*!
 * Method to handle expose events with a background repainter. Events that
 * Pass through here are rapidly delt with by either expanding an
 * already existing expose rectangle to cover the expose rectangle of
 *  the current event or if there is no pending expose rectangle, because
 *  the background repainter has cleared it, set a new expose rectangle.
\param UT_Rect *rClip the rectangle of the expose event.
*/
void GR_Graphics::doRepaint( UT_Rect * rClip)
{
//
// Look if we have a pending expose left over.
//
	xxx_UT_DEBUGMSG(("SEVIOR: Starting doRepaint \n"));
	while(isSpawnedRedraw())
	{
		UT_usleep(100);
	}
//
// Stop the repainter
//
	setDontRedraw(true);
//
// Get a lock on the expose rectangle
//
	while(isExposedAreaAccessed())
	{
		UT_usleep(10); // 10 microseconds
	}
	setExposedAreaAccessed(true);
	if(isExposePending() || doMerge())
	{
		//
        // If so merge in the current expose area
        //
		xxx_UT_DEBUGMSG(("Doing a union in expose handler\n"));
		unionPendingRect( rClip);
		setRecentRect(rClip);
		setDoMerge(false);
	}
	else
	{
//
// Otherwise Load the current expose area into the redraw area.
//
		xxx_UT_DEBUGMSG(("Setting Exposed Area in expose handler \n"));
		setPendingRect(rClip->left,rClip->top,rClip->width,rClip->height);
		setRecentRect(rClip);
  	}
//
// Release expose rectangle lock
//
	setExposedAreaAccessed(false);
//
// Tell the repainter there is something to repaint.
//
	setExposePending(true);
//
// Allow the repainter to paint
//
	setDontRedraw(false);
	xxx_UT_DEBUGMSG(("SEVIOR: Finished doRepaint \n"));
//
// OK this event is handled.
//
}

/*!
 * This method fills the distination rectangle with a piece of the image pImg.
 * The size and location of the piece of the image is defined by src. 
 * src and dest are in logical units.
*/
void GR_Graphics::fillRect(GR_Image * pImg, const UT_Rect & src, const UT_Rect & dest)
{
	GR_Image * pImageSection = pImg->createImageSegment(this, src);
	UT_return_if_fail(pImageSection);
	drawImage(pImageSection,dest.left,dest.top);
	delete pImageSection;
}
/*!
 * Fill the specified rectangle with color defined by "c". The dimensions
 * of UT_Rect are in logical units.
 */
void GR_Graphics::fillRect(const UT_RGBColor& c, const UT_Rect &r)
{
	fillRect(c, r.left, r.top, r.width, r.height);
}
#if XAP_DONTUSE_XOR
#else
void GR_Graphics::xorRect(UT_sint32 x, UT_sint32 y, UT_sint32 w, UT_sint32 h)
{
	xorLine(x,     y,     x + w, y);
	xorLine(x + w, y,     x + w, y + h);
	xorLine(x + w, y + h, x,     y + h);
	xorLine(x,     y + h, x,     y);
}

void GR_Graphics::xorRect(const UT_Rect& r)
{
	xorRect(r.left, r.top, r.width, r.height);
}

#endif

/////////////////////////////////////////////////////////////////////////////////
//
//  COMPLEX SCRIPT PROCESSING FUNCTIONS
//
//

/*!
    itemize() analyses text represented by text, notionally dividing
    it into segments that from the point of the shaper are uniform;
    this notional division is stored in GR_Itemization I.
    
    The default implementation that only deals with bidi reordering

    derrived classes can either provide entirely different
    implementation, or call the default implementation first and then
    further divide the results into items with same shaping needs
 */
#ifndef ABI_GRAPHICS_PLUGIN
bool GR_Graphics::itemize(UT_TextIterator & text, GR_Itemization & I)
{
	UT_return_val_if_fail(text.getStatus() == UTIter_OK, false);
	
	I.clear();
	UT_uint32 iCurOffset = 0, iLastOffset = 0;
	UT_uint32 iPosStart = text.getPosition();

	// the main loop that will span the whole text of the iterator
	while(text.getStatus() == UTIter_OK)
	{
		UT_BidiCharType iPrevType, iLastStrongType = UT_BIDI_UNSET, iType;
		
		UT_UCS4Char c = text.getChar();
		
		UT_return_val_if_fail(text.getStatus() == UTIter_OK, false);

		iType = UT_bidiGetCharType(c);
#if 0
		// this branch of code breaks at all direction bounaries
		// it is disabled because doing that causes bug 8099
		iCurOffset = iLastOffset = text.getPosition();
		++text;
		
		// this loop will cover a single homogenous item
		while(text.getStatus() == UTIter_OK)
		{
			iPrevType = iType;

			c = text.getChar();
			UT_return_val_if_fail(text.getStatus() == UTIter_OK, false);

			// remember the offset
			iLastOffset = text.getPosition();
			
			iType = UT_bidiGetCharType(c);
			if(iType != iPrevType)
			{
				break;
			}

			++text;
		}
#else
		//we have to break the text into chunks that each will go into a
		//separate run in a manner that will ensure that the text will
		//be correctly processed later. The most obvious way is to
		//break every time we encounter a change of directional
		//properties. Unfortunately that means breaking at each white
		//space, which adds a huge amount of processing due to
		//allocating and deleting runs when loading a
		//document. The following code tries to catch out the obvious
		//cases when the span can remain intact. Tomas, Jan 28, 2003

		// remember where we are ...
		iCurOffset = iLastOffset = text.getPosition();
		++text;

		UT_BidiCharType iNextType;
		
		// this loop will cover a single homogenous item
		while(text.getStatus() == UTIter_OK)
		{
			iPrevType = iType;
			if(UT_BIDI_IS_STRONG(iType))
				iLastStrongType = iType;
			
			c = text.getChar();
			UT_return_val_if_fail(text.getStatus() == UTIter_OK, false);

			// remember the offset
			iLastOffset = text.getPosition();
			++text;
			
			iType = UT_bidiGetCharType(c);
			if(iType != iPrevType)
			{
				// potential direction boundary see if we can ignore
				// it
				bool bIgnore = false;
#if 0
				// this assumption is not true; for instance in the
				// sequence ") " the parenthesis and the space can
				// resolve to different directions
				// 
				// I am leaving it here so that I do not add it one
				// day again (Tomas, Apr 10, 2003)
				
				if(UT_BIDI_IS_NEUTRAL(iPrevType) && UT_BIDI_IS_NEUTRAL(iType))
				{
					// two neutral characters in a row will have the same
					// direction
					xxx_UT_DEBUGMSG(("GR_Graphics::itemize: ntrl->ntrl (c=0x%04x)\n",c));
					bIgnore = true;
				}
				else
#endif
				if(UT_BIDI_IS_STRONG(iPrevType) && UT_BIDI_IS_NEUTRAL(iType))
				{
					// we can ignore a neutral character following a
					// strong one if it is followed by a strong
					// character of identical type to the previous one
					xxx_UT_DEBUGMSG(("GR_Graphics::itemize: strong->ntrl (c=0x%04x)\n",c));
					
					// take a peek at what follows
					UT_uint32 iOldPos = text.getPosition();
					
					while(text.getStatus() == UTIter_OK)
					{
						UT_UCS4Char c = text.getChar();
						UT_return_val_if_fail(text.getStatus() == UTIter_OK, false);

						++text;
						
						iNextType = UT_bidiGetCharType(c);
						xxx_UT_DEBUGMSG(("GR_Graphics::itemize: iNextType 0x%04x\n", iNextType));
						
						if(iNextType == iPrevType)
						{
							bIgnore = true;
							break;
						}

						// if the next character is strong, we cannot
						// ignore the boundary
						if(UT_BIDI_IS_STRONG(iNextType))
							break;
					}

					// restore position
					text.setPosition(iOldPos);
				}
				else if(UT_BIDI_IS_NEUTRAL(iPrevType) && UT_BIDI_IS_STRONG(iType))
				{
					// a neutral character followed by a strong one -- we
					// can ignore it, if the neutral character was
					// preceeded by a strong character of the same
					// type
					if(iType == iLastStrongType)
					{
						bIgnore = true;
					}
					xxx_UT_DEBUGMSG(("GR_Graphics::itemize: ntrl->strong (c=0x%04x)\n",c));
				}
				else
				{
					// in all other cases we will split
					xxx_UT_DEBUGMSG(("GR_Graphics::itemize: other (c=0x%04x)\n",pSpan[i]));
				}

				xxx_UT_DEBUGMSG(("GR_Graphics::itemize: bIgnore %d\n",static_cast<UT_uint32>(bIgnore)));
				if(!bIgnore)
					break;
			}
			
		}
#endif
		
		I.addItem(iCurOffset - iPosStart, new GR_XPItem(GRScriptType_Undefined));
	}

	// add an extra record of type Void to allow for calculation of
	// length of the last item
	// iLastOffset is the offset of the last valid character; the Void
	// offset is one beyond that
	I.addItem(iLastOffset - iPosStart + 1, new GR_XPItem(GRScriptType_Void));
	return true;
}

/*!
    shape() processes the information encapsulated by GR_ShapingInfo
    si and stores results in GR_*RenderInfo* pri.

    If the contents of pri are NULL the function must create a new
    instance of GR_*RenderInfo of the appropriate type and store the
    pointer in pri; it also must store pointer to this graphics
    instance in pri->m_pGraphics.

    If ri indicates that the text is justified, appropriate processing
    needs to be done

    This function is tied closely together to a class derrived from
    GR_RenderInfo which may contain caches of various data that will
    speed subsequent calls to prepareToRenderChars() and renderChars()
*/
bool GR_Graphics::shape(GR_ShapingInfo & si, GR_RenderInfo *& pri)
{
	if(!si.m_pItem || si.m_pItem->getType() == GRScriptType_Void)
		return false;

	if(!pri)
	{
		pri = new GR_XPRenderInfo(si.m_pItem->getType());
		UT_return_val_if_fail(pri, false);
		pri->m_pGraphics = this;
	}

	GR_XPRenderInfo * pRI = (GR_XPRenderInfo *)pri;
	GR_ContextGlyph  cg;

	
	// make sure that the buffers are of sufficient size ...
	if(si.m_iLength > pRI->m_iBufferSize) //buffer too small, reallocate
	{
		delete[] pRI->m_pChars;
		delete[] pRI->m_pWidths;
			
		pRI->m_pChars = new UT_UCS4Char[si.m_iLength + 1];
		UT_return_val_if_fail(pRI->m_pChars, false);

		pRI->m_pWidths = new UT_sint32[si.m_iLength + 1];
		UT_return_val_if_fail(pRI->m_pWidths, false);

		pRI->m_iBufferSize = si.m_iLength + 1;
	}

	pRI->m_iLength = si.m_iLength;
	pRI->m_iTotalLength = si.m_iLength;
	pRI->m_eScriptType = si.m_pItem->getType();
	pRI->m_pItem = si.m_pItem;
	
	if(si.m_eShapingRequired == GRSR_None)
	{
		// our run only contains non-shaping, non-ligating
		// characters, we will process it using the much faster
		// copyString()
		pRI->m_eShapingResult = cg.copyString(si.m_Text,pRI->m_pChars, si.m_iLength, si.m_pLang,
											  si.m_iVisDir,
											  GR_Font::s_doesGlyphExist, si.m_pFont);
	}
	else
	{
		pRI->m_eShapingResult = cg.renderString(si.m_Text,pRI->m_pChars, si.m_iLength, si.m_pLang,
												si.m_iVisDir,
												GR_Font::s_doesGlyphExist, si.m_pFont);
	}

	pRI->m_eState = GRSR_BufferClean;
	
	if(pRI->m_eShapingResult == GRSR_Error)
		return false;

	if(pRI->isJustified())
		justify(*pRI);

	// make sure that we invalidate the static buffers if we own them
	if(pRI->s_pOwner == pRI)
		pRI->s_pOwner = NULL;
	
	return true;
}

void GR_Graphics::appendRenderedCharsToBuff(GR_RenderInfo & ri, UT_GrowBuf & buf) const
{
	UT_return_if_fail(ri.getType() == GRRI_XP);
	
	GR_XPRenderInfo & RI = (GR_XPRenderInfo &) ri;
	buf.append(reinterpret_cast<UT_GrowBufElement *>(RI.m_pChars),RI.m_iLength);
}

UT_sint32 GR_Graphics::getTextWidth(GR_RenderInfo & ri)
{
	UT_return_val_if_fail(ri.getType() == GRRI_XP, 0);
	GR_XPRenderInfo & RI = (GR_XPRenderInfo &) ri;

	// NB: the width array is in VISUAL order, but offset is a logical offset
	bool bReverse = (ri.m_iVisDir == UT_BIDI_RTL);
	
	UT_sint32 iWidth = 0;
	for (UT_sint32 i = ri.m_iOffset; i < ri.m_iLength + ri.m_iOffset; ++i)
	{
		UT_uint32 k = bReverse ? RI.m_iTotalLength - i - 1 : i;
		UT_uint32 iCW = RI.m_pWidths[k] > 0 ? RI.m_pWidths[k] : 0;
		iWidth += iCW;
	}

	return iWidth;
}


void GR_Graphics::measureRenderedCharWidths(GR_RenderInfo & ri) 
{
	UT_return_if_fail(ri.getType() == GRRI_XP);
	GR_XPRenderInfo & RI = (GR_XPRenderInfo &) ri;
	UT_return_if_fail(RI.m_pWidths);
	
	//bool bReverse = (RI.m_iVisDir == UT_BIDI_RTL);

	UT_sint32 i;

	for (i = 0; i < RI.m_iLength; i++)
	{
		if(i > 0 && *(RI.m_pChars + i) == UCS_LIGATURE_PLACEHOLDER)
		{
			RI.m_pWidths[i]   = RI.m_pWidths[i - 1]/2;
			UT_uint32 mod     = RI.m_pWidths[i-1]%2;
			RI.m_pWidths[i-1] = RI.m_pWidths[i] + mod;
		}
		else
		{

			measureString(RI.m_pChars + i, 0, 1,
					 static_cast<UT_GrowBufElement*>(RI.m_pWidths) + i);
		}
	}

	if(RI.isJustified())
	{
		justify(RI);
	}
	
	// make sure that we invalidate the static buffers if we own them
	if(RI.s_pOwner == &RI)
		RI.s_pOwner = NULL;
	
}

/*!
   prepareToRenderChars() does any preprocessing necessary immediately
   prior to the actual output on screen (which is done by
   renderChars()), and must be always called before renderChars().

   What this function does is entirely dependend of the specific
   shaping engine (and in some cases might not do anthing at all). For
   example, this function might refresh any temporary buffers,
   etc.

   The reason for dividing the actual drawing into two steps (prepare
   and render) is to limit the amount of processing in cases where the
   caller needs to make several calls to renderChars() with the same
   GR_RenderInfo. For example, fp_TextRun::_draw() draws the text in
   one, two or three segments depending whether there is a selection
   and where in the run it is. It will make one call to
   prepareToRenderChars() and then 1-3 calls to renderChars() chaning
   the background and text colour in between those calls.
*/
void GR_Graphics::prepareToRenderChars(GR_RenderInfo & ri)
{
	UT_return_if_fail(ri.getType() == GRRI_XP);
	GR_XPRenderInfo & RI = (GR_XPRenderInfo &)ri;
	RI.prepareToRenderChars();
}

/*!
   renderChars() outputs textual data represented by ri onto the device (screen,
   priter, etc.).

   The output starts at ri.m_iOffset, is ri.m_iLength
   long and the drawing starts at ri.m_xoff, ri.m_yoff
*/
void GR_Graphics::renderChars(GR_RenderInfo & ri)
{
	UT_return_if_fail(ri.getType() == GRRI_XP);
	GR_XPRenderInfo & RI = (GR_XPRenderInfo &)ri;

	drawChars(RI.s_pCharBuff,RI.m_iOffset,RI.m_iLength,RI.m_xoff,RI.m_yoff,RI.s_pAdvances);

	
}

/*!
    return true if linebreak at character c is permissible
*/
bool GR_Graphics::canBreak(GR_RenderInfo & ri, UT_sint32 &iNext, bool bAfter)
{
	UT_UCS4Char c[2];

	iNext = -1; // we do not bother with this
	UT_return_val_if_fail(ri.m_pText && ri.m_pText->getStatus() == UTIter_OK, false);
	
	*(ri.m_pText) += ri.m_iOffset;
	UT_return_val_if_fail(ri.m_pText->getStatus() == UTIter_OK, false);
	
	UT_return_val_if_fail(getApp(), false);


	if (bAfter) 
	{
		// Look up this character and the next.
		c[0] = ri.m_pText->getChar();
		++(*ri.m_pText);
		c[1] = ri.m_pText->getChar();
		// Check for end of document.
		if (c[1] == UT_IT_ERROR) 
			return false;

		return getApp()->getEncodingManager()->canBreakBetween(c);
	}
	else
	{
		// Look up this character and the one before.
		c[1] = ri.m_pText->getChar();
		--(*ri.m_pText);
		c[0] = ri.m_pText->getChar();
		// Check for beginning of document.
		if (c[0] == UT_IT_ERROR) 
			return true;

		return getApp()->getEncodingManager()->canBreakBetween(c);
	}
	// Control should never reach here.
	UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
}

/*!
   resetJustification() makes the data represented by ri unjustified
   and returns value by which the total width changed as a result such
   that OriginalWidth + ReturnValue = NewWidth (i.e., the return
   value should normally be negative).

   The parameter bPermanent indicates that the resetting is permanent
   and any buffers used to hold justification information can be
   remove, e.g., the paragraph alignment has changed from justified to
   left (in some circumstance the reset can be only temporary and we
   will be asked to subsequently recalculate the justification
   information; in such case it makes sense to keep the buffers in place)
   
*/
UT_sint32 GR_Graphics::resetJustification(GR_RenderInfo & ri, bool /* bPermanent*/)
{
	UT_return_val_if_fail(ri.getType() == GRRI_XP, 0);
	GR_XPRenderInfo & RI = (GR_XPRenderInfo &)ri;

	UT_return_val_if_fail(RI.m_pChars && RI.m_pWidths, 0);
	
	UT_sint32 iAccumDiff = 0;

	if(RI.isJustified())
	{
		UT_sint32 iSpaceWidthBefore = RI.m_iSpaceWidthBeforeJustification;

		if(RI.m_pWidths == NULL)
		{
			return 0;
		}

		for(UT_sint32 i = 0; i < RI.m_iLength; ++i)
		{
			if(RI.m_pChars[i] != UCS_SPACE)
				continue;

			if(RI.m_pWidths[i] != iSpaceWidthBefore)
			{
				iAccumDiff += iSpaceWidthBefore - RI.m_pWidths[i];
				RI.m_pWidths[i] = iSpaceWidthBefore;
			}
		}
		
		RI.m_iSpaceWidthBeforeJustification = 0xfffffff; // note one less 'f'
		RI.m_iJustificationPoints = 0;
		RI.m_iJustificationAmount = 0;

		if(RI.s_pOwner == &RI)
			RI.s_pOwner = NULL;

	}

	return iAccumDiff;
}

/*!
   countJustificationPoints() counts the number of points between
   which any extra justification width could be distributed (in Latin
   text these are typically spaces).spaces in the text;

   The function returns the count as negative value if
   the run contains only blank data (i.e., only spaces in Latin text).
*/
UT_sint32 GR_Graphics::countJustificationPoints(const GR_RenderInfo & ri) const
{
	UT_return_val_if_fail(ri.getType() == GRRI_XP, 0);
	GR_XPRenderInfo & RI = (GR_XPRenderInfo &)ri;

	UT_return_val_if_fail(RI.m_pChars, 0);

	UT_sint32 iCount = 0;
	bool bNonBlank = false;

	for(UT_sint32 i = (UT_sint32)RI.m_iLength-1; i >= 0; --i)
	{
		if(RI.m_pChars[i] != UCS_SPACE)
		{
			bNonBlank = true;
			continue;
		}
		
		// only count this space if this is not last run, or if we
		// have found something other than spaces
		if(!ri.m_bLastOnLine || bNonBlank)
			iCount++;
	}

	if(!bNonBlank)
	{
		return -iCount;
	}
	else
	{
		return iCount;
	}
}

/*!
   justify() distributes justification information into the text.

   The justification information consists of ri.m_iJustificationPoints and
   m_iJustificationAmount

   NB: This function must not modify the original values in
   ri.m_iJustificationAmount and ri.m_iJustificationPoints
*/
void GR_Graphics::justify(GR_RenderInfo & ri)
{
	UT_return_if_fail(ri.getType() == GRRI_XP);
	GR_XPRenderInfo & RI = (GR_XPRenderInfo &)ri;

	UT_return_if_fail(RI.m_pChars && RI.m_pWidths);

	// need to leave the original values alone ...
	UT_uint32 iPoints = ri.m_iJustificationPoints;
	UT_sint32 iAmount = ri.m_iJustificationAmount;
	
	
	if(!iAmount)
	{
		// this can happend near the start of the line (the line is
		// processed from back to front) due to rounding errors in
		// the  algorithm; we simply mark the run as one that does not
		// use justification

		// not needed, since we are always called after ::resetJustification()
		// resetJustification();
		return;
	}

	if(iPoints)
	{
		for(UT_sint32 i = 0; i < RI.m_iLength; ++i)
		{
			if(RI.m_pChars[i] != UCS_SPACE)
				continue;
			
			RI.m_iSpaceWidthBeforeJustification = RI.m_pWidths[i];
		
			UT_sint32 iThisAmount = iAmount / iPoints;

			RI.m_pWidths[i] += iThisAmount;

			xxx_UT_DEBUGMSG(("Space at loc %d new width %d given extra width %d \n",
							 i,pCharWidths[i],iThisAmount));
		
			iAmount -= iThisAmount;

			iPoints--;

			if(!iPoints)
				break;
		}

		if(RI.s_pOwner == &RI)
			RI.s_pOwner = NULL;
	}
}

UT_uint32 GR_Graphics::XYToPosition(const GR_RenderInfo & ri, UT_sint32 x, UT_sint32 y) const
{
	UT_return_val_if_fail(ri.getType() == GRRI_XP, 0);
	GR_XPRenderInfo & RI = (GR_XPRenderInfo &) ri;
	UT_return_val_if_fail(RI.m_pWidths, 0);

	UT_return_val_if_fail(UT_NOT_IMPLEMENTED,0 );
	return 0;
}

void GR_Graphics::positionToXY(const GR_RenderInfo & ri,
						 	  UT_sint32& x, UT_sint32& y,
							  UT_sint32& x2, UT_sint32& y2,
							  UT_sint32& height, bool& bDirection) const
{
	UT_return_if_fail(ri.getType() == GRRI_XP);
	GR_XPRenderInfo & RI = (GR_XPRenderInfo &) ri;
	UT_return_if_fail(RI.m_pWidths);
	
	UT_return_if_fail(UT_NOT_IMPLEMENTED);
}

UT_uint32 GR_Graphics::adjustCaretPosition(GR_RenderInfo & ri, bool bForward)
{
	return ri.m_iOffset;
}

void GR_Graphics::adjustDeletePosition(GR_RenderInfo & ri)
{
	return;
}

#endif // #ifndef ABI_GRAPHICS_PLUGIN

///////////////////////////////////////////////////////////////////////////////
//
//  IMPLEMNATION OF GR_GraphicsFactory
//
//  GR_GraphicsFactory allows parallel existence of differnt
//  implementations of GR_Graphics class, so that the graphics
//  class used by the application can be changed at runtime.
//
//  The factory is accessed via XAP_App functions.
//
//  Each derrived class needs to be registered with the factory using
//  the registerClass() function; new instances of the graphics class
//  are obtained by newGraphics()
//
//  
//  


/*!
   Registers the class allocator and descriptor functions with the
   factory.

   allocator is a static intermediary to the graphics constructor; it
   takes a parameter of type GR_AllocInfo*, which should point to any
   data the allocator needs to pass onto the constructor.

   descriptor is a static function that returns a string that
   describes the graphics class in human-readable manner (e.g., to be
   shown in dialogues).
   
   Returns true on success, false if requested id is already
   registered (except for IDs < GRID_LAST_DEFAULT, which will
   automatically be reassigned).

   This function is to be used by built-in classes; plugins
   should use registerPluginClass() instead.

   The default platform implementation of the graphics class should
   register itself twice, once with its predefined id and once as the
   default class.
*/
bool GR_GraphicsFactory::registerClass(GR_Allocator allocator, GR_Descriptor descriptor,
									   UT_uint32 iClassId)
{
	UT_return_val_if_fail(allocator && descriptor && iClassId > GRID_LAST_DEFAULT, false);
	
	UT_sint32 indx = m_vClassIds.findItem(iClassId);

	if(indx >= 0)
	{
		return false;
	}
	
	m_vAllocators.addItem((void*)allocator);
	m_vDescriptors.addItem((void*)descriptor);
	m_vClassIds.addItem((UT_sint32)iClassId);

	return true;
}


/*!
   As registerClass() but to be used by plugins; it automatically
   allocates an id for the class by which it will be identified.

   \return id > 0 on success, 0 on failure
*/
UT_uint32 GR_GraphicsFactory::registerPluginClass(GR_Allocator allocator, GR_Descriptor descriptor)
{
	UT_return_val_if_fail(allocator && descriptor, 0);

	static UT_uint32 iLastId = GRID_LAST_EXTENSION;
	iLastId++;

	while(iLastId < GRID_UNKNOWN && !registerClass(allocator,descriptor, iLastId))
		iLastId++;

	if(iLastId != GRID_UNKNOWN)
		return iLastId;

	return 0;
}


/*!
    Unregisteres class with the given id; this function is only to be
    used by plugins.

    Returns true on success.

    The caller must never try to unregister any other class that
    itself; the sole exception to this is GRID_DEFAULT

    The built-in graphics classes cannot be unregistered.
*/
bool GR_GraphicsFactory::unregisterClass(UT_uint32 iClassId)
{
	// cannot unregister built-in classes
	UT_return_val_if_fail(iClassId > GRID_LAST_BUILT_IN, false);

	// cannot unregister the default graphics class, hopefully the
	// rogue plugin will pay attention to the return value
	UT_return_val_if_fail(iClassId == m_iDefaultScreen || iClassId == m_iDefaultPrinter, false);
	
	UT_sint32 indx = m_vClassIds.findItem(iClassId);

	if(indx < 0)
		return false;

	m_vClassIds.deleteNthItem(indx);
	m_vAllocators.deleteNthItem(indx);
	m_vDescriptors.deleteNthItem(indx);

	return true;
}

/*!
   Creates an instance of the graphics class represented by iClassId,
   passing param to the class allocator.
*/
GR_Graphics * GR_GraphicsFactory::newGraphics(UT_uint32 iClassId, GR_AllocInfo &param) const
{
	if(iClassId == GRID_DEFAULT)
		iClassId = m_iDefaultScreen;

	if(iClassId == GRID_DEFAULT_PRINT)
		iClassId = m_iDefaultPrinter;
	
	UT_sint32 indx = m_vClassIds.findItem(iClassId);

	if(indx < 0)
		return NULL;
				
					
	GR_Allocator alloc = (GR_Allocator)m_vAllocators.getNthItem(indx);
				
	if(!alloc)
		return NULL;

	return alloc(param);
}

const char *  GR_GraphicsFactory::getClassDescription(UT_uint32 iClassId) const
{
	if(iClassId == GRID_DEFAULT)
		iClassId = m_iDefaultScreen;

	if(iClassId == GRID_DEFAULT_PRINT)
		iClassId = m_iDefaultPrinter;

	UT_sint32 indx = m_vClassIds.findItem(iClassId);

	if(indx < 0)
		return NULL;
					
	GR_Descriptor descr = (GR_Descriptor)m_vDescriptors.getNthItem(indx);
				
	if(!descr)
		return NULL;

	return descr();
				
}

bool GR_GraphicsFactory::isRegistered(UT_uint32 iClassId) const
{
	UT_sint32 indx = m_vClassIds.findItem(iClassId);

	if(indx < 0)
		return false;

	return true;
}
