/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Mozilla SVG project.
 *
 * The Initial Developer of the Original Code is
 * Crocodile Clips Ltd..
 * Portions created by the Initial Developer are Copyright (C) 2002
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Alex Fritze <alex.fritze@crocodile-clips.com> (original author)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsSVGTextFrame.h"
#include "nsILookAndFeel.h"
#include "nsTextFragment.h"
#include "nsSVGUtils.h"
#include "nsIDOMSVGLengthList.h"
#include "nsIDOMSVGLength.h"
#include "nsIDOMSVGRect.h"
#include "nsIDOMSVGPoint.h"
#include "nsSVGGlyphFrame.h"
#include "nsSVGTextPathFrame.h"
#include "nsSVGPathElement.h"
#include "nsSVGPoint.h"
#include "nsSVGRect.h"
#include "nsDOMError.h"
#include "gfxContext.h"
#include "gfxMatrix.h"
#include "gfxPlatform.h"
#include "gfxTextRunWordCache.h"

struct CharacterPosition {
  gfxPoint pos;
  gfxFloat angle;
  PRBool draw;
};
  
/**
 * This is a do-it-all helper class. It supports iterating through the
 * drawable characters of a string. For each character, it can set up
 * a graphics context with a transform appropriate for drawing the
 * character, or a transform appropriate for emitting geometry in the
 * text metrics coordinate system (which differs from the drawing
 * coordinate system by a scale factor of AppUnitPerCSSPixels). These
 * transforms include offsets and rotations of characters along paths, and
 * the mPosition of the nsSVGGlyphFrame.
 * 
 * This helper also creates the textrun as needed. It supports detecting
 * the special case when the entire textrun can be drawn or measured
 * as a unit, and setting the graphics context transform up for that. It
 * takes care of setting up the global transform if requested. It also
 * provides direct access to the character path position data for the
 * DOM APIs that need that.
 * 
 * If an error occurs, for example, a canvas TM is not available because
 * the element is in a <defs> section, then the CharacterIterator will
 * behave as if the frame has no drawable characters.
 *
 * XXX should make this iterate clusters instead
 * XXX needs RTL love
 * XXX might want to make AdvanceToCharacter constant time (e.g. by
 * caching advances and/or the CharacterPosition array across DOM
 * API calls) to ensure that calling Get*OfChar (etc) for each character
 * in the text is O(N)
 */
class CharacterIterator
{
public:
  /**
   * Sets up the iterator so that NextChar will return the first drawable
   * char.
   * @param aForceGlobalTransform passed on to EnsureTextRun (see below)
   */
  CharacterIterator(nsSVGGlyphFrame *aSource, PRBool aForceGlobalTransform);
  /**
   * This matrix will be applied to aContext in the SetupFor methods below,
   * before any glyph translation/rotation.
   */
  void SetInitialMatrix(gfxContext *aContext) {
    mInitialMatrix = aContext->CurrentMatrix();
    if (mInitialMatrix.IsSingular()) {
      mInError = PR_TRUE;
    }
  }
  /**
   * Try to set up aContext so we can draw the whole textrun at once.
   * This applies any global transform requested by SetInitialMatrix,
   * then applies the positioning of the text. Returns false if drawing
   * the whole textrun at once is impossible due to individual positioning
   * and/or rotation of glyphs.
   */
  PRBool SetupForDirectTextRunDrawing(gfxContext *aContext) {
    return SetupForDirectTextRun(aContext, mDrawScale);
  }
  /**
   * Try to set up aContext so we can measure the whole textrun at once.
   * This applies any global transform requested by SetInitialMatrix,
   * then applies the positioning of the text, then applies a scale
   * from appunits to device pixels so drawing in appunits works.
   * Returns false if drawing the whole textrun at once is impossible due
   * to individual positioning and/or rotation of glyphs.
   */
  PRBool SetupForDirectTextRunMetrics(gfxContext *aContext) {
    return SetupForDirectTextRun(aContext, mMetricsScale);
  }

  /**
   * Returns the index of the next char in the string that should be
   * drawn, or -1 if there is no such character.
   */
  PRInt32 NextChar();
  /**
   * Repeated calls NextChar until it returns aIndex (i.e. aIndex is the
   * current drawable character). Returns false if that never happens
   * (because aIndex is before or equal to the current character, or
   * out of bounds, or not drawable).
   */
  PRBool AdvanceToCharacter(PRInt32 aIndex);

  /**
   * Set up aContext for glyph drawing. This applies any global transform
   * requested by SetInitialMatrix, then applies any positioning and
   * rotation for the current character.
   */
  void SetupForDrawing(gfxContext *aContext) {
    return SetupFor(aContext, mDrawScale);
  }
  /**
   * Set up aContext for glyph measuring. This applies any global transform
   * requested by SetInitialMatrix, then applies any positioning and
   * rotation for the current character, then applies a scale from appunits
   * to device pixels so that drawing in appunits sizes works.
   */
  void SetupForMetrics(gfxContext *aContext) {
    return SetupFor(aContext, mMetricsScale);
  }
  /**
   * Get the raw position data for the current character.
   */
  CharacterPosition GetPositionData();

private:
  PRBool SetupForDirectTextRun(gfxContext *aContext, float aScale);
  void SetupFor(gfxContext *aContext, float aScale);

  nsSVGGlyphFrame *mSource;
  nsAutoTArray<CharacterPosition,80> mPositions;
  gfxMatrix mInitialMatrix;
  // Textrun advance width from start to mCurrentChar, in appunits
  gfxFloat mCurrentAdvance;
  PRInt32 mCurrentChar;
  float mDrawScale;
  float mMetricsScale;
  PRPackedBool mInError;
};

//----------------------------------------------------------------------
// Implementation

nsIFrame*
NS_NewSVGGlyphFrame(nsIPresShell* aPresShell, nsIContent* aContent, nsIFrame* parentFrame, nsStyleContext* aContext)
{
  NS_ASSERTION(parentFrame, "null parent");
  nsISVGTextContentMetrics *metrics;
  CallQueryInterface(parentFrame, &metrics);
  NS_ASSERTION(metrics, "trying to construct an SVGGlyphFrame for an invalid container");
  
  NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
               "trying to construct an SVGGlyphFrame for wrong content element");

  return new (aPresShell) nsSVGGlyphFrame(aContext);
}

//----------------------------------------------------------------------
// nsISupports methods

NS_INTERFACE_MAP_BEGIN(nsSVGGlyphFrame)
  NS_INTERFACE_MAP_ENTRY(nsISVGGlyphFragmentLeaf)
  NS_INTERFACE_MAP_ENTRY(nsISVGGlyphFragmentNode)
  NS_INTERFACE_MAP_ENTRY(nsISVGChildFrame)
NS_INTERFACE_MAP_END_INHERITING(nsSVGGlyphFrameBase)

//----------------------------------------------------------------------
// nsIFrame methods

NS_IMETHODIMP
nsSVGGlyphFrame::CharacterDataChanged(nsPresContext*  aPresContext,
                                      nsIContent*     aChild,
                                      PRBool          aAppend)
{
  ClearTextRun();
  NotifyGlyphMetricsChange();

  return NS_OK;
}

// Usable font size range in devpixels / user-units
#define CLAMP_MIN_SIZE 8
#define CLAMP_MAX_SIZE 200
#define PRECISE_SIZE   200

NS_IMETHODIMP
nsSVGGlyphFrame::DidSetStyleContext()
{
  nsSVGGlyphFrameBase::DidSetStyleContext();
  ClearTextRun();
  NotifyGlyphMetricsChange();

  return NS_OK;
}

NS_IMETHODIMP
nsSVGGlyphFrame::SetSelected(nsPresContext* aPresContext,
                             nsIDOMRange*    aRange,
                             PRBool          aSelected,
                             nsSpread        aSpread)
{
#if defined(DEBUG) && defined(SVG_DEBUG_SELECTION)
  printf("nsSVGGlyphFrame(%p)::SetSelected()\n", this);
#endif
//  return nsSVGGlyphFrameBase::SetSelected(aPresContext, aRange, aSelected, aSpread);

  // check whether style allows selection
  PRBool  selectable;
  IsSelectable(&selectable, nsnull);
  if (!selectable)
    return NS_OK;
  
  if ( aSelected ){
    mState |=  NS_FRAME_SELECTED_CONTENT;
  }
  else
    mState &= ~NS_FRAME_SELECTED_CONTENT;

  nsSVGUtils::UpdateGraphic(this);

  return NS_OK;
}

NS_IMETHODIMP
nsSVGGlyphFrame::GetSelected(PRBool *aSelected) const
{
  nsresult rv = nsSVGGlyphFrameBase::GetSelected(aSelected);
#if defined(DEBUG) && defined(SVG_DEBUG_SELECTION)
  printf("nsSVGGlyphFrame(%p)::GetSelected()=%d\n", this, *aSelected);
#endif
  return rv;
}

NS_IMETHODIMP
nsSVGGlyphFrame::IsSelectable(PRBool* aIsSelectable,
                              PRUint8* aSelectStyle) const
{
  nsresult rv = nsSVGGlyphFrameBase::IsSelectable(aIsSelectable, aSelectStyle);
#if defined(DEBUG) && defined(SVG_DEBUG_SELECTION)
  printf("nsSVGGlyphFrame(%p)::IsSelectable()=(%d,%d)\n", this, *aIsSelectable, aSelectStyle);
#endif
  return rv;
}

nsIAtom *
nsSVGGlyphFrame::GetType() const
{
  return nsGkAtoms::svgGlyphFrame;
}

//----------------------------------------------------------------------
// nsISVGChildFrame methods

NS_IMETHODIMP
nsSVGGlyphFrame::PaintSVG(nsSVGRenderState *aContext, nsRect *aDirtyRect)
{
  if (!GetStyleVisibility()->IsVisible())
    return NS_OK;

  gfxContext *gfx = aContext->GetGfxContext();
  PRUint16 renderMode = aContext->GetRenderMode();

  if (renderMode != nsSVGRenderState::NORMAL) {

    gfxMatrix matrix = gfx->CurrentMatrix();
    SetupGlobalTransform(gfx);

    CharacterIterator iter(this, PR_TRUE);
    iter.SetInitialMatrix(gfx);

    if (GetClipRule() == NS_STYLE_FILL_RULE_EVENODD)
      gfx->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD);
    else
      gfx->SetFillRule(gfxContext::FILL_RULE_WINDING);

    if (renderMode == nsSVGRenderState::CLIP_MASK) {
      gfx->SetAntialiasMode(gfxContext::MODE_ALIASED);
      gfx->SetColor(gfxRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      FillCharacters(&iter, gfx);
    } else {
      AddCharactersToPath(&iter, gfx);
    }

    gfx->SetMatrix(matrix);
    return NS_OK;
  }

  // We are adding patterns or gradients to the context. Save
  // it so we don't leak them into the next object we draw
  gfx->Save();
  SetupGlobalTransform(gfx);

  if (HasFill() && SetupCairoFill(gfx)) {
    gfxMatrix matrix = gfx->CurrentMatrix();
    CharacterIterator iter(this, PR_TRUE);
    iter.SetInitialMatrix(gfx);

    FillCharacters(&iter, gfx);
    gfx->SetMatrix(matrix);
  }

  if (HasStroke() && SetupCairoStroke(gfx)) {
    // SetupCairoStroke will clear mTextRun whenever
    // there is a pattern or gradient on the text
    CharacterIterator iter(this, PR_TRUE);
    iter.SetInitialMatrix(gfx);

    gfx->NewPath();
    AddCharactersToPath(&iter, gfx);
    gfx->Stroke();
    // We need to clear the context's path so state doesn't leak
    // out. See bug 337753.
    gfx->NewPath();
  }
  gfx->Restore();

  return NS_OK;
}

NS_IMETHODIMP
nsSVGGlyphFrame::GetFrameForPointSVG(float x, float y, nsIFrame** hit)
{
#ifdef DEBUG
  //printf("nsSVGGlyphFrame(%p)::GetFrameForPoint\n", this);
#endif
  // test for hit:
  *hit = nsnull;

  if (!mRect.Contains(nscoord(x), nscoord(y)))
    return NS_OK;

  PRBool events = PR_FALSE;
  switch (GetStyleSVG()->mPointerEvents) {
    case NS_STYLE_POINTER_EVENTS_NONE:
      break;
    case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED:
      if (GetStyleVisibility()->IsVisible() &&
          (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None ||
           GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None))
        events = PR_TRUE;
      break;
    case NS_STYLE_POINTER_EVENTS_VISIBLEFILL:
    case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE:
    case NS_STYLE_POINTER_EVENTS_VISIBLE:
      if (GetStyleVisibility()->IsVisible())
        events = PR_TRUE;
      break;
    case NS_STYLE_POINTER_EVENTS_PAINTED:
      if (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None ||
          GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None)
        events = PR_TRUE;
      break;
    case NS_STYLE_POINTER_EVENTS_FILL:
    case NS_STYLE_POINTER_EVENTS_STROKE:
    case NS_STYLE_POINTER_EVENTS_ALL:
      events = PR_TRUE;
      break;
    default:
      NS_ERROR("not reached");
      break;
  }

  if (!events)
    return NS_OK;

  PRBool isHit = ContainsPoint(x, y);
  if (isHit) 
    *hit = this;
  
  return NS_OK;
}

NS_IMETHODIMP_(nsRect)
nsSVGGlyphFrame::GetCoveredRegion()
{
  return mRect;
}

static gfxContext *
MakeTmpCtx() {
  return new gfxContext(nsSVGUtils::GetThebesComputationalSurface());
}

NS_IMETHODIMP
nsSVGGlyphFrame::UpdateCoveredRegion()
{
  nsRefPtr<gfxContext> tmpCtx = MakeTmpCtx();
  SetupGlobalTransform(tmpCtx);
  CharacterIterator iter(this, PR_TRUE);
  iter.SetInitialMatrix(tmpCtx);
  
  gfxRect extent;

  if (HasStroke()) {
    AddCharactersToPath(&iter, tmpCtx);
    SetupCairoStrokeGeometry(tmpCtx);
    extent = tmpCtx->UserToDevice(tmpCtx->GetUserStrokeExtent());
  } else if (HasFill()) {
    AddBoundingBoxesToPath(&iter, tmpCtx);
    tmpCtx->IdentityMatrix();
    extent = tmpCtx->GetUserPathExtent();
  } else {
    extent = gfxRect(0, 0, 0, 0);
  }

  mRect = nsSVGUtils::ToBoundingPixelRect(extent);
  return NS_OK;
}

NS_IMETHODIMP
nsSVGGlyphFrame::InitialUpdate()
{
  NS_ASSERTION(GetStateBits() & NS_FRAME_FIRST_REFLOW,
               "Yikes! We've been called already! Hopefully we weren't called "
               "before our nsSVGOuterSVGFrame's initial Reflow()!!!");

  NS_ASSERTION(!(mState & NS_FRAME_IN_REFLOW),
               "We don't actually participate in reflow");

  NotifyGlyphMetricsChange();

  // Do unset the various reflow bits, though.
  mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
              NS_FRAME_HAS_DIRTY_CHILDREN);
  
  return NS_OK;
}  

void
nsSVGGlyphFrame::NotifySVGChanged(PRUint32 aFlags)
{
  if (aFlags & TRANSFORM_CHANGED) {
    ClearTextRun();
  }
  if (!(aFlags & SUPPRESS_INVALIDATION)) {
    nsSVGUtils::UpdateGraphic(this);
  }
}

NS_IMETHODIMP
nsSVGGlyphFrame::NotifyRedrawSuspended()
{
  // XXX should we cache the fact that redraw is suspended?
  return NS_OK;
}

NS_IMETHODIMP
nsSVGGlyphFrame::NotifyRedrawUnsuspended()
{
  if (GetStateBits() & NS_STATE_SVG_DIRTY)
    nsSVGUtils::UpdateGraphic(this);

  return NS_OK;
}

void
nsSVGGlyphFrame::AddCharactersToPath(CharacterIterator *aIter,
                                     gfxContext *aContext)
{
  if (aIter->SetupForDirectTextRunDrawing(aContext)) {
    mTextRun->DrawToPath(aContext, gfxPoint(0, 0), 0,
                         mTextRun->GetLength(), nsnull, nsnull);
    return;
  }

  PRInt32 i;
  while ((i = aIter->NextChar()) >= 0) {
    aIter->SetupForDrawing(aContext);
    mTextRun->DrawToPath(aContext, gfxPoint(0, 0), i, 1, nsnull, nsnull);
  }
}

void
nsSVGGlyphFrame::AddBoundingBoxesToPath(CharacterIterator *aIter,
                                        gfxContext *aContext)
{
  if (aIter->SetupForDirectTextRunMetrics(aContext)) {
    gfxTextRun::Metrics metrics =
      mTextRun->MeasureText(0, mTextRun->GetLength(),
                            PR_FALSE, nsnull, nsnull);
    aContext->Rectangle(metrics.mBoundingBox);
    return;
  }

  PRInt32 i;
  while ((i = aIter->NextChar()) >= 0) {
    aIter->SetupForMetrics(aContext);
    gfxTextRun::Metrics metrics =
      mTextRun->MeasureText(i, 1, PR_FALSE, nsnull, nsnull);
    aContext->Rectangle(metrics.mBoundingBox);
  }
}

void
nsSVGGlyphFrame::FillCharacters(CharacterIterator *aIter,
                                gfxContext *aContext)
{
  if (aIter->SetupForDirectTextRunDrawing(aContext)) {
    mTextRun->Draw(aContext, gfxPoint(0, 0), 0,
                   mTextRun->GetLength(), nsnull, nsnull, nsnull);
    return;
  }

  PRInt32 i;
  while ((i = aIter->NextChar()) >= 0) {
    aIter->SetupForDrawing(aContext);
    mTextRun->Draw(aContext, gfxPoint(0, 0), i, 1, nsnull, nsnull, nsnull);
  }
}

NS_IMETHODIMP
nsSVGGlyphFrame::GetBBox(nsIDOMSVGRect **_retval)
{
  *_retval = nsnull;

  nsRefPtr<gfxContext> tmpCtx = MakeTmpCtx();
  SetupGlobalTransform(tmpCtx);
  CharacterIterator iter(this, PR_TRUE);
  iter.SetInitialMatrix(tmpCtx);
  AddCharactersToPath(&iter, tmpCtx);

  tmpCtx->IdentityMatrix();
  return NS_NewSVGRect(_retval, tmpCtx->GetUserPathExtent());
}

//----------------------------------------------------------------------
// nsSVGGeometryFrame methods:

/* readonly attribute nsIDOMSVGMatrix canvasTM; */
NS_IMETHODIMP
nsSVGGlyphFrame::GetCanvasTM(nsIDOMSVGMatrix * *aCTM)
{
  NS_ASSERTION(mParent, "null parent");
  
  nsSVGContainerFrame *containerFrame = static_cast<nsSVGContainerFrame*>
                                                   (mParent);
  nsCOMPtr<nsIDOMSVGMatrix> parentTM = containerFrame->GetCanvasTM();
  *aCTM = nsnull;
  parentTM.swap(*aCTM);
  return NS_OK;
}

//----------------------------------------------------------------------
// nsSVGGlyphFrame methods:

PRBool
nsSVGGlyphFrame::GetCharacterData(nsAString & aCharacterData)
{
  nsAutoString characterData;
  mContent->AppendTextTo(characterData);

  if (mWhitespaceHandling & COMPRESS_WHITESPACE) {
    PRBool trimLeadingWhitespace, trimTrailingWhitespace;
    trimLeadingWhitespace = ((mWhitespaceHandling & TRIM_LEADING_WHITESPACE) != 0);
    trimTrailingWhitespace = ((mWhitespaceHandling & TRIM_TRAILING_WHITESPACE) != 0);
    characterData.CompressWhitespace(trimLeadingWhitespace, 
                                     trimTrailingWhitespace);
  } else {
    nsAString::iterator start, end;
    characterData.BeginWriting(start);
    characterData.EndWriting(end);
    while (start != end) {
      if (NS_IsAsciiWhitespace(*start))
        *start = ' ';
      ++start;
    }
  }
  aCharacterData = characterData;

  return !characterData.IsEmpty();
}

PRBool
nsSVGGlyphFrame::GetCharacterPositions(nsTArray<CharacterPosition>* aCharacterPositions,
                                       float aMetricsScale)
{
  NS_ASSERTION(mTextRun->GetLength() > 0, "no text");

  nsSVGTextPathFrame *textPath = FindTextPathParent();

  /* we're an ordinary fragment - return */
  /* XXX: we might want to use this for individual x/y/dx/dy adjustment */
  if (!textPath)
    return PR_TRUE;

  nsRefPtr<gfxFlattenedPath> data = textPath->GetFlattenedPath();

  /* textPath frame, but invalid target */
  if (!data)
    return PR_FALSE;

  gfxFloat length = data->GetLength();
  PRUint32 strLength = mTextRun->GetLength();

  if (!aCharacterPositions->SetLength(strLength))
    return PR_FALSE;

  CharacterPosition *cp = aCharacterPositions->Elements();

  for (PRUint32 k = 0; k < strLength; k++)
    cp[k].draw = PR_FALSE;

  gfxFloat x = mPosition.x;
  for (PRUint32 i = 0; i < strLength; i++) {
    gfxFloat halfAdvance =
      mTextRun->GetAdvanceWidth(i, 1, nsnull)*aMetricsScale / 2.0;

    /* have we run off the end of the path? */
    if (x + halfAdvance > length)
      break;

    /* check that we've advanced to the start of the path */
    if (x + halfAdvance >= 0.0) {
      cp[i].draw = PR_TRUE;

      // add y (normal)
      // add rotation
      // move point back along tangent
      gfxPoint pt = data->FindPoint(gfxPoint(x + halfAdvance, mPosition.y),
                                    &(cp[i].angle));
      cp[i].pos =
        pt - gfxPoint(cos(cp[i].angle), sin(cp[i].angle)) * halfAdvance;
    }
    x += 2 * halfAdvance;
  }
  
  return PR_TRUE;
}

//----------------------------------------------------------------------

// Utilities for converting from indices in the uncompressed content
// element strings to compressed frame string and back:
static int
CompressIndex(int index, const nsTextFragment*fragment)
{
  int ci=0;
  if (fragment->Is2b()) {
    const PRUnichar *data=fragment->Get2b();
    while(*data && index) {
      if (XP_IS_SPACE_W(*data)){
        do {
          ++data;
          --index;
        }while(XP_IS_SPACE_W(*data) && index);
      }
      else {
        ++data;
        --index;
      }
      ++ci;
    }
  }
  else {
    const char *data=fragment->Get1b();
    while(*data && index) {
      if (XP_IS_SPACE_W(*data)){
        do {
          ++data;
          --index;
        }while(XP_IS_SPACE_W(*data) && index);
      }
      else {
        ++data;
        --index;
      }
      ++ci;
    }
  }
    
  return ci;
}

static int
UncompressIndex(int index, PRBool bRightAffinity, const nsTextFragment*fragment)
{
  // XXX
  return index;
}

nsresult
nsSVGGlyphFrame::GetHighlight(PRUint32 *charnum, PRUint32 *nchars,
                              nscolor *foreground, nscolor *background)
{
  *foreground = NS_RGB(255,255,255);
  *background = NS_RGB(0,0,0); 
  *charnum=0;
  *nchars=0;

  PRBool hasHighlight =
    (mState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT;

  if (!hasHighlight) {
    NS_ERROR("nsSVGGlyphFrame::GetHighlight() called by renderer when there is no highlight");
    return NS_ERROR_FAILURE;
  }

  nsPresContext *presContext = PresContext();

  // The selection ranges are relative to the uncompressed text in
  // the content element. We'll need the text fragment:
  const nsTextFragment *fragment = mContent->GetText();
  NS_ASSERTION(fragment, "no text");
  
  // get the selection details 
  SelectionDetails *details = nsnull;
  {
    nsCOMPtr<nsFrameSelection> frameSelection;
    {
      nsCOMPtr<nsISelectionController> controller;
      GetSelectionController(presContext, getter_AddRefs(controller));
      
      if (!controller) {
        NS_ERROR("no selection controller");
        return NS_ERROR_FAILURE;
      }
      frameSelection = do_QueryInterface(controller);
    }
    if (!frameSelection) {
      frameSelection = presContext->PresShell()->FrameSelection();
    }
    if (!frameSelection) {
      NS_ERROR("no frameselection interface");
      return NS_ERROR_FAILURE;
    }

    details = frameSelection->LookUpSelection(
      mContent, 0, fragment->GetLength(), PR_FALSE
      );
  }

#if defined(DEBUG) && defined(SVG_DEBUG_SELECTION)
  {
    SelectionDetails *dp = details;
    printf("nsSVGGlyphFrame(%p)::GetHighlight() [\n", this);
    while (dp) {
      printf("selection detail: %d(%d)->%d(%d) type %d\n",
             dp->mStart, CompressIndex(dp->mStart, fragment),
             dp->mEnd, CompressIndex(dp->mEnd, fragment),
             dp->mType);
      dp = dp->mNext;
    }
    printf("]\n");
      
  }
#endif
  
  if (details) {
    NS_ASSERTION(details->mNext==nsnull, "can't do multiple selection ranges");

    *charnum=CompressIndex(details->mStart, fragment);
    *nchars=CompressIndex(details->mEnd, fragment)-*charnum;  
    
    nsILookAndFeel *look = presContext->LookAndFeel();

    look->GetColor(nsILookAndFeel::eColor_TextSelectBackground, *background);
    look->GetColor(nsILookAndFeel::eColor_TextSelectForeground, *foreground);

    SelectionDetails *dp = details;
    while ((dp=details->mNext) != nsnull) {
      delete details;
      details = dp;
    }
    delete details;
  }
  
  return NS_OK;
}


//----------------------------------------------------------------------
// nsISVGGlyphFragmentLeaf interface:

NS_IMETHODIMP_(void)
nsSVGGlyphFrame::SetGlyphPosition(float x, float y)
{
  mPosition.MoveTo(x, y);
  nsSVGUtils::UpdateGraphic(this);
}

NS_IMETHODIMP
nsSVGGlyphFrame::GetStartPositionOfChar(PRUint32 charnum,
                                        nsIDOMSVGPoint **_retval)
{
  *_retval = nsnull;

  CharacterIterator iter(this, PR_FALSE);
  if (!iter.AdvanceToCharacter(charnum))
    return NS_ERROR_DOM_INDEX_SIZE_ERR;

  return NS_NewSVGPoint(_retval, iter.GetPositionData().pos);
}

NS_IMETHODIMP
nsSVGGlyphFrame::GetEndPositionOfChar(PRUint32 charnum,
                                      nsIDOMSVGPoint **_retval)
{
  *_retval = nsnull;

  CharacterIterator iter(this, PR_FALSE);
  if (!iter.AdvanceToCharacter(charnum))
    return NS_ERROR_DOM_INDEX_SIZE_ERR;

  nsRefPtr<gfxContext> tmpCtx = MakeTmpCtx();
  iter.SetupForMetrics(tmpCtx);
  tmpCtx->MoveTo(gfxPoint(mTextRun->GetAdvanceWidth(charnum, 1, nsnull), 0));
  tmpCtx->IdentityMatrix();
  return NS_NewSVGPoint(_retval, tmpCtx->CurrentPoint());
}

NS_IMETHODIMP
nsSVGGlyphFrame::GetExtentOfChar(PRUint32 charnum, nsIDOMSVGRect **_retval)
{
  *_retval = nsnull;

  CharacterIterator iter(this, PR_FALSE);
  if (!iter.AdvanceToCharacter(charnum))
    return NS_ERROR_DOM_INDEX_SIZE_ERR;

  gfxTextRun::Metrics metrics =
    mTextRun->MeasureText(charnum, 1, PR_FALSE, nsnull, nsnull);

  nsRefPtr<gfxContext> tmpCtx = MakeTmpCtx();
  iter.SetupForMetrics(tmpCtx);
  tmpCtx->Rectangle(metrics.mBoundingBox);
  tmpCtx->IdentityMatrix();
  return NS_NewSVGRect(_retval, tmpCtx->GetUserPathExtent());
}

NS_IMETHODIMP
nsSVGGlyphFrame::GetRotationOfChar(PRUint32 charnum, float *_retval)
{
  CharacterIterator iter(this, PR_FALSE);
  if (!iter.AdvanceToCharacter(charnum))
    return NS_ERROR_DOM_INDEX_SIZE_ERR;

  CharacterPosition pos = iter.GetPositionData();
  if (!pos.draw)
    return NS_ERROR_DOM_INDEX_SIZE_ERR;

  const gfxFloat radPerDeg = M_PI/180.0;
  *_retval = float(pos.angle / radPerDeg);
  return NS_OK;
}

NS_IMETHODIMP_(float)
nsSVGGlyphFrame::GetBaselineOffset(PRUint16 baselineIdentifier,
                                   PRBool aForceGlobalTransform)
{
  float drawScale, metricsScale;

  if (!EnsureTextRun(&drawScale, &metricsScale, aForceGlobalTransform))
    return 0.0f;

  gfxTextRun::Metrics metrics =
    mTextRun->MeasureText(0, mTextRun->GetLength(),
                          PR_FALSE, nsnull, nsnull);

  gfxFloat baselineAppUnits;
  switch (baselineIdentifier) {
  case BASELINE_HANGING:
    // not really right, but the best we can do with the information provided
    // FALLTHROUGH
  case BASELINE_TEXT_BEFORE_EDGE:
    baselineAppUnits = -metrics.mAscent;
    break;
  case BASELINE_TEXT_AFTER_EDGE:
    baselineAppUnits = metrics.mDescent;
    break;
  case BASELINE_CENTRAL:
  case BASELINE_MIDDLE:
    baselineAppUnits = -(metrics.mAscent - metrics.mDescent) / 2.0f;
    break;
  case BASELINE_ALPHABETIC:
  default:
    baselineAppUnits = 0.0;
    break;
  }
  return float(baselineAppUnits)*metricsScale;
}

NS_IMETHODIMP_(float)
nsSVGGlyphFrame::GetAdvance(PRBool aForceGlobalTransform)
{
  float drawScale, metricsScale;
  if (!EnsureTextRun(&drawScale, &metricsScale, aForceGlobalTransform))
    return 0.0f;

  gfxFloat advanceAppUnits =
    mTextRun->GetAdvanceWidth(0, mTextRun->GetLength(), nsnull);
  return float(advanceAppUnits)*metricsScale;
}

NS_IMETHODIMP_(nsSVGTextPathFrame*) 
nsSVGGlyphFrame::FindTextPathParent()
{
  /* check if we're the child of a textPath */
  for (nsIFrame *frame = GetParent();
       frame != nsnull;
       frame = frame->GetParent()) {
    nsIAtom* type = frame->GetType();
    if (type == nsGkAtoms::svgTextPathFrame) {
      return static_cast<nsSVGTextPathFrame*>(frame);
    } else if (type == nsGkAtoms::svgTextFrame)
      return nsnull;
  }
  return nsnull;
}

NS_IMETHODIMP_(PRBool)
nsSVGGlyphFrame::IsStartOfChunk()
{
  // this fragment is a chunk if it has a corresponding absolute
  // position adjustment in an ancestors' x or y array. (At the moment
  // we don't map the full arrays, but only the first elements.)

  return PR_FALSE;
}

NS_IMETHODIMP_(void)
nsSVGGlyphFrame::GetAdjustedPosition(/* inout */ float &x, /* inout */ float &y)
{
}

NS_IMETHODIMP_(already_AddRefed<nsIDOMSVGLengthList>)
nsSVGGlyphFrame::GetX()
{
  nsSVGTextContainerFrame *containerFrame;
  containerFrame = static_cast<nsSVGTextContainerFrame *>(mParent);
  if (containerFrame)
    return containerFrame->GetX();
  return nsnull;
}

NS_IMETHODIMP_(already_AddRefed<nsIDOMSVGLengthList>)
nsSVGGlyphFrame::GetY()
{
  nsSVGTextContainerFrame *containerFrame;
  containerFrame = static_cast<nsSVGTextContainerFrame *>(mParent);
  if (containerFrame)
    return containerFrame->GetY();
  return nsnull;
}

NS_IMETHODIMP_(already_AddRefed<nsIDOMSVGLengthList>)
nsSVGGlyphFrame::GetDx()
{
  nsSVGTextContainerFrame *containerFrame;
  containerFrame = static_cast<nsSVGTextContainerFrame *>(mParent);
  if (containerFrame)
    return containerFrame->GetDx();
  return nsnull;
}

NS_IMETHODIMP_(already_AddRefed<nsIDOMSVGLengthList>)
nsSVGGlyphFrame::GetDy()
{
  nsSVGTextContainerFrame *containerFrame;
  containerFrame = static_cast<nsSVGTextContainerFrame *>(mParent);
  if (containerFrame)
    return containerFrame->GetDy();
  return nsnull;
}

NS_IMETHODIMP_(PRUint16)
nsSVGGlyphFrame::GetTextAnchor()
{
  return GetStyleSVG()->mTextAnchor;
}

NS_IMETHODIMP_(PRBool)
nsSVGGlyphFrame::IsAbsolutelyPositioned()
{
  nsIFrame *lastFrame = this;

  for (nsIFrame *frame = GetParent();
       frame != nsnull;
       lastFrame = frame, frame = frame->GetParent()) {

    /* need to be the first child if we are absolutely positioned */
    if (!frame ||
        frame->GetFirstChild(nsnull) != lastFrame)
      break;

    // textPath is always absolutely positioned for our purposes
    if (frame->GetType() == nsGkAtoms::svgTextPathFrame)
      return PR_TRUE;
        
    if (frame &&
        (frame->GetContent()->HasAttr(kNameSpaceID_None, nsGkAtoms::x) ||
         frame->GetContent()->HasAttr(kNameSpaceID_None, nsGkAtoms::y)))
        return PR_TRUE;

    if (frame->GetType() == nsGkAtoms::svgTextFrame)
      break;
  }

  return PR_FALSE;
}


//----------------------------------------------------------------------
// nsISVGGlyphFragmentNode interface:

NS_IMETHODIMP_(PRUint32)
nsSVGGlyphFrame::GetNumberOfChars()
{
  if (mWhitespaceHandling == PRESERVE_WHITESPACE)
    return mContent->TextLength();

  nsAutoString text;
  GetCharacterData(text);
  return text.Length();
}

NS_IMETHODIMP_(float)
nsSVGGlyphFrame::GetComputedTextLength()
{
  return GetAdvance(PR_FALSE);
}

NS_IMETHODIMP_(float)
nsSVGGlyphFrame::GetSubStringLength(PRUint32 charnum, PRUint32 fragmentChars)
{
  float drawScale, metricsScale;
  if (!EnsureTextRun(&drawScale, &metricsScale, PR_FALSE))
    return 0.0f;

  gfxFloat advanceAppUnits =
    mTextRun->GetAdvanceWidth(charnum, fragmentChars, nsnull);
  return float(advanceAppUnits)*metricsScale;
}

NS_IMETHODIMP_(PRInt32)
nsSVGGlyphFrame::GetCharNumAtPosition(nsIDOMSVGPoint *point)
{
  float xPos, yPos;
  point->GetX(&xPos);
  point->GetY(&yPos);

  nsRefPtr<gfxContext> tmpCtx = MakeTmpCtx();
  CharacterIterator iter(this, PR_FALSE);

  PRInt32 i;
  PRInt32 last = -1;
  gfxPoint pt(xPos, yPos);
  while ((i = iter.NextChar()) >= 0) {
    gfxTextRun::Metrics metrics =
      mTextRun->MeasureText(i, 1, PR_FALSE, nsnull, nsnull);
    iter.SetupForMetrics(tmpCtx);
    tmpCtx->NewPath();
    tmpCtx->Rectangle(metrics.mBoundingBox);
    tmpCtx->IdentityMatrix();
    if (tmpCtx->PointInFill(pt)) {
      // Can't return now. If there's glyph overlap, the last character
      // to be rendered wins.
      last = i;
    }
  }

  return last;
}

NS_IMETHODIMP_(nsISVGGlyphFragmentLeaf *)
nsSVGGlyphFrame::GetFirstGlyphFragment()
{
  return this;
}

NS_IMETHODIMP_(nsISVGGlyphFragmentLeaf *)
nsSVGGlyphFrame::GetNextGlyphFragment()
{
  nsIFrame* sibling = mNextSibling;
  while (sibling) {
    nsISVGGlyphFragmentNode *node = nsnull;
    CallQueryInterface(sibling, &node);
    if (node)
      return node->GetFirstGlyphFragment();
    sibling = sibling->GetNextSibling();
  }

  // no more siblings. go back up the tree.
  
  NS_ASSERTION(mParent, "null parent");
  nsISVGGlyphFragmentNode *node = nsnull;
  CallQueryInterface(mParent, &node);
  return node ? node->GetNextGlyphFragment() : nsnull;
}

NS_IMETHODIMP_(void)
nsSVGGlyphFrame::SetWhitespaceHandling(PRUint8 aWhitespaceHandling)
{
  mWhitespaceHandling = aWhitespaceHandling;
}

//----------------------------------------------------------------------
//

void
nsSVGGlyphFrame::NotifyGlyphMetricsChange()
{
  nsSVGTextContainerFrame *containerFrame =
    static_cast<nsSVGTextContainerFrame *>(mParent);
  if (containerFrame)
    containerFrame->NotifyGlyphMetricsChange();
}

PRBool
nsSVGGlyphFrame::ContainsPoint(float x, float y)
{
  nsRefPtr<gfxContext> tmpCtx = MakeTmpCtx();
  SetupGlobalTransform(tmpCtx);
  CharacterIterator iter(this, PR_TRUE);
  iter.SetInitialMatrix(tmpCtx);
  
  PRInt32 i;
  while ((i = iter.NextChar()) >= 0) {
    gfxTextRun::Metrics metrics =
      mTextRun->MeasureText(i, 1, PR_FALSE, nsnull, nsnull);
    iter.SetupForMetrics(tmpCtx);
    tmpCtx->Rectangle(metrics.mBoundingBox);
  }

  tmpCtx->IdentityMatrix();
  return tmpCtx->PointInFill(gfxPoint(x, y));
}

PRBool
nsSVGGlyphFrame::GetGlobalTransform(gfxMatrix *aMatrix)
{
  nsCOMPtr<nsIDOMSVGMatrix> ctm;
  GetCanvasTM(getter_AddRefs(ctm));
  if (!ctm)
    return PR_FALSE;

  *aMatrix = nsSVGUtils::ConvertSVGMatrixToThebes(ctm);
  return !aMatrix->IsSingular();
}

void
nsSVGGlyphFrame::SetupGlobalTransform(gfxContext *aContext)
{
  gfxMatrix matrix;
  GetGlobalTransform(&matrix);
  aContext->Multiply(matrix);
}

void
nsSVGGlyphFrame::ClearTextRun()
{
  if (!mTextRun)
    return;
  gfxTextRunWordCache::RemoveTextRun(mTextRun);
  delete mTextRun;
  mTextRun = nsnull;
}

PRBool
nsSVGGlyphFrame::EnsureTextRun(float *aDrawScale, float *aMetricsScale,
                               PRBool aForceGlobalTransform)
{
  // Compute the size at which the text should render (excluding the CTM)
  const nsStyleFont* fontData = GetStyleFont();
  // Since SVG has its own scaling, we really don't want
  // fonts in SVG to respond to the browser's "TextZoom"
  // (Ctrl++,Ctrl+-)
  nsPresContext *presContext = PresContext();
  float textZoom = presContext->TextZoom();
  double size =
    presContext->AppUnitsToFloatCSSPixels(fontData->mSize) / textZoom;

  double textRunSize;
  if (mTextRun) {
    textRunSize = mTextRun->GetFontGroup()->GetStyle()->size;
  } else {
    nsAutoString text;
    if (!GetCharacterData(text))
      return PR_FALSE;

    gfxMatrix m;
    if (aForceGlobalTransform ||
        !(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) {
      if (!GetGlobalTransform(&m))
        return PR_FALSE;
    }

    // The context scale is the ratio of the length of the transformed
    // diagonal vector (1,1) to the length of the untransformed diagonal
    // (which is sqrt(2)).
    gfxPoint p = m.Transform(gfxPoint(1, 1)) - m.Transform(gfxPoint(0, 0));
    double contextScale = sqrt((p.x*p.x + p.y*p.y)/2);

    nsCAutoString langGroup;
    nsIAtom *langGroupAtom = presContext->GetLangGroup();
    if (langGroupAtom) {
      const char* lg;
      langGroupAtom->GetUTF8String(&lg);
      langGroup.Assign(lg);
    }

    if (GetStyleSVG()->mTextRendering ==
        NS_STYLE_TEXT_RENDERING_GEOMETRICPRECISION) {
      textRunSize = PRECISE_SIZE;
    } else {
      textRunSize = size*contextScale;
      textRunSize = PR_MAX(textRunSize, CLAMP_MIN_SIZE);
      textRunSize = PR_MIN(textRunSize, CLAMP_MAX_SIZE);
    }

    const nsFont& font = fontData->mFont;
    gfxFontStyle fontStyle(font.style, font.weight, textRunSize, langGroup,
                           font.sizeAdjust, font.systemFont,
                           font.familyNameQuirks);

    nsRefPtr<gfxFontGroup> fontGroup =
      gfxPlatform::GetPlatform()->CreateFontGroup(font.name, &fontStyle);

    PRUint32 flags = gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX |
      nsLayoutUtils::GetTextRunFlagsForStyle(GetStyleContext(), GetStyleText(), GetStyleFont());

    // XXX We should use a better surface here! But then we'd have to
    // change things so we can ensure we always have the "right" sort of
    // surface available, by creating the textrun only at the right times
    nsRefPtr<gfxContext> tmpCtx = MakeTmpCtx();
    tmpCtx->SetMatrix(m);

    // Use only the word cache here. We don't want to cache the textrun
    // globally because we'll never hit in that cache, since we create
    // a new fontgroup every time. Even if we cached fontgroups, we
    // might render at very many different sizes (e.g. during zoom
    // animation) and caching a textrun for each such size would be bad.
    gfxTextRunFactory::Parameters params = {
        tmpCtx, nsnull, nsnull, nsnull, 0, GetTextRunUnitsFactor()
    };
    mTextRun = gfxTextRunWordCache::MakeTextRun(text.get(), text.Length(),
      fontGroup, &params, flags);
    if (!mTextRun)
      return PR_FALSE;
  }

  *aDrawScale = float(size/textRunSize);
  *aMetricsScale = (*aDrawScale)/GetTextRunUnitsFactor();
  return PR_TRUE;
}

//----------------------------------------------------------------------
// helper class

CharacterIterator::CharacterIterator(nsSVGGlyphFrame *aSource,
        PRBool aForceGlobalTransform)
  : mSource(aSource), mCurrentAdvance(0), mCurrentChar(-1),
    mInError(PR_FALSE)
{
  if (!aSource->EnsureTextRun(&mDrawScale, &mMetricsScale,
                              aForceGlobalTransform) ||
      !aSource->GetCharacterPositions(&mPositions, mMetricsScale)) {
    mInError = PR_TRUE;
  }
}

PRBool
CharacterIterator::SetupForDirectTextRun(gfxContext *aContext, float aScale)
{
  if (!mPositions.IsEmpty() || mInError)
    return PR_FALSE;
  aContext->SetMatrix(mInitialMatrix);
  aContext->Translate(mSource->mPosition);
  aContext->Scale(aScale, aScale);
  return PR_TRUE;
}

PRInt32
CharacterIterator::NextChar()
{
  if (mInError)
    return -1;

  while (PR_TRUE) {
    if (mCurrentChar >= 0 &&
        (mPositions.IsEmpty() || mPositions[mCurrentChar].draw)) {
      mCurrentAdvance +=
        mSource->mTextRun->GetAdvanceWidth(mCurrentChar, 1, nsnull);
    }
    ++mCurrentChar;

    if (mCurrentChar >= PRInt32(mSource->mTextRun->GetLength()))
      return -1;

    if (mPositions.IsEmpty() || mPositions[mCurrentChar].draw)
      return mCurrentChar;
  }
}

PRBool
CharacterIterator::AdvanceToCharacter(PRInt32 aIndex)
{
  while (NextChar() != -1) {
    if (mCurrentChar == aIndex)
      return PR_TRUE;
  }
  return PR_FALSE;
}

void
CharacterIterator::SetupFor(gfxContext *aContext, float aScale)
{
  NS_ASSERTION(!mInError, "We should not have reached here");

  aContext->SetMatrix(mInitialMatrix);
  if (mPositions.IsEmpty()) {
    aContext->Translate(mSource->mPosition);
    aContext->Scale(aScale, aScale);
    aContext->Translate(gfxPoint(mCurrentAdvance, 0));
  } else {
    aContext->Translate(mPositions[mCurrentChar].pos);
    aContext->Rotate(mPositions[mCurrentChar].angle);
    aContext->Scale(aScale, aScale);
  }
}

CharacterPosition
CharacterIterator::GetPositionData()
{
  if (!mPositions.IsEmpty())
    return mPositions[mCurrentChar];

  gfxFloat advance = mCurrentAdvance/nsSVGGlyphFrame::GetTextRunUnitsFactor();
  CharacterPosition cp =
    { mSource->mPosition + gfxPoint(advance, 0), 0, PR_TRUE };
  return cp;
}
