/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
// vim:cindent:ts=2:et:sw=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 mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Mats Palmgren <mats.palmgren@bredband.net>
 *   Takeshi Ichimaru <ayakawa.m@gmail.com>
 *   Masayuki Nakano <masayuki@d-toybox.com>
 *
 * 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 ***** */

/* utility functions for drawing borders and backgrounds */

#include "nsStyleConsts.h"
#include "nsPresContext.h"
#include "nsIImage.h"
#include "nsIFrame.h"
#include "nsPoint.h"
#include "nsRect.h"
#include "nsIViewManager.h"
#include "nsIPresShell.h"
#include "nsFrameManager.h"
#include "nsStyleContext.h"
#include "nsGkAtoms.h"
#include "nsTransform2D.h"
#include "nsIDeviceContext.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIScrollableFrame.h"
#include "imgIRequest.h"
#include "imgIContainer.h"
#include "gfxIImageFrame.h"
#include "nsCSSRendering.h"
#include "nsCSSColorUtils.h"
#include "nsITheme.h"
#include "nsThemeConstants.h"
#include "nsIServiceManager.h"
#include "nsIHTMLDocument.h"
#include "nsLayoutUtils.h"
#include "nsINameSpaceManager.h"
#include "nsBlockFrame.h"

#include "gfxContext.h"

#define BORDER_FULL    0        //entire side
#define BORDER_INSIDE  1        //inside half
#define BORDER_OUTSIDE 2        //outside half

//thickness of dashed line relative to dotted line
#define DOT_LENGTH  1           //square
#define DASH_LENGTH 3           //3 times longer than dot

//some shorthand for side bits
#define SIDE_BIT_TOP (1 << NS_SIDE_TOP)
#define SIDE_BIT_RIGHT (1 << NS_SIDE_RIGHT)
#define SIDE_BIT_BOTTOM (1 << NS_SIDE_BOTTOM)
#define SIDE_BIT_LEFT (1 << NS_SIDE_LEFT)
#define SIDE_BITS_ALL (SIDE_BIT_TOP|SIDE_BIT_RIGHT|SIDE_BIT_BOTTOM|SIDE_BIT_LEFT)


/** The following classes are used by CSSRendering for the rounded rect implementation */
#define MAXPATHSIZE 12
#define MAXPOLYPATHSIZE 1000

enum ePathTypes{
  eOutside =0,
  eInside,
  eCalc,
  eCalcRev
};

// To avoid storing this data on nsInlineFrame (bloat) and to avoid
// recalculating this for each frame in a continuation (perf), hold
// a cache of various coordinate information that we need in order
// to paint inline backgrounds.
struct InlineBackgroundData
{
  InlineBackgroundData()
      : mFrame(nsnull), mBlockFrame(nsnull)
  {
  }

  ~InlineBackgroundData()
  {
  }

  void Reset()
  {
    mBoundingBox.SetRect(0,0,0,0);
    mContinuationPoint = mLineContinuationPoint = mUnbrokenWidth = 0;
    mFrame = mBlockFrame = nsnull;
  }

  nsRect GetContinuousRect(nsIFrame* aFrame)
  {
    SetFrame(aFrame);

    nscoord x;
    if (mBidiEnabled) {
      x = mLineContinuationPoint;

      // Scan continuations on the same line as aFrame and accumulate the widths
      // of frames that are to the left (if this is an LTR block) or right 
      // (if it's RTL) of the current one.
      PRBool isRtlBlock = (mBlockFrame->GetStyleVisibility()->mDirection ==
                           NS_STYLE_DIRECTION_RTL);      
      nscoord curOffset = aFrame->GetOffsetTo(mBlockFrame).x;

      nsIFrame* inlineFrame = aFrame->GetPrevContinuation();
      // If the continuation is fluid we know inlineFrame is not on the same line.
      // If it's not fluid, we need to test furhter to be sure.
      while (inlineFrame && !inlineFrame->GetNextInFlow() &&
             AreOnSameLine(aFrame, inlineFrame)) {
        nscoord frameXOffset = inlineFrame->GetOffsetTo(mBlockFrame).x;
        if(isRtlBlock == (frameXOffset >= curOffset)) {
          x += inlineFrame->GetSize().width;
        }
        inlineFrame = inlineFrame->GetPrevContinuation();
      }

      inlineFrame = aFrame->GetNextContinuation();
      while (inlineFrame && !inlineFrame->GetPrevInFlow() &&
             AreOnSameLine(aFrame, inlineFrame)) {
        nscoord frameXOffset = inlineFrame->GetOffsetTo(mBlockFrame).x;
        if(isRtlBlock == (frameXOffset >= curOffset)) {
          x += inlineFrame->GetSize().width;
        }
        inlineFrame = inlineFrame->GetNextContinuation();
      }
      if (isRtlBlock) {
        // aFrame itself is also to the right of its left edge, so add its width.
        x += aFrame->GetSize().width;
        // x is now the distance from the left edge of aFrame to the right edge
        // of the unbroken content. Change it to indicate the distance from the
        // left edge of the unbroken content to the left edge of aFrame.
        x = mUnbrokenWidth - x;
      }
    } else {
      x = mContinuationPoint;
    }

    // Assume background-origin: border and return a rect with offsets
    // relative to (0,0).  If we have a different background-origin,
    // then our rect should be deflated appropriately by our caller.
    return nsRect(-x, 0, mUnbrokenWidth, mFrame->GetSize().height);
  }

  nsRect GetBoundingRect(nsIFrame* aFrame)
  {
    SetFrame(aFrame);

    // Move the offsets relative to (0,0) which puts the bounding box into
    // our coordinate system rather than our parent's.  We do this by
    // moving it the back distance from us to the bounding box.
    // This also assumes background-origin: border, so our caller will
    // need to deflate us if needed.
    nsRect boundingBox(mBoundingBox);
    nsPoint point = mFrame->GetPosition();
    boundingBox.MoveBy(-point.x, -point.y);

    return boundingBox;
  }

protected:
  nsIFrame*     mFrame;
  nscoord       mContinuationPoint;
  nscoord       mUnbrokenWidth;
  nsRect        mBoundingBox;

  PRBool        mBidiEnabled;
  nsBlockFrame* mBlockFrame;
  nscoord       mLineContinuationPoint;
  
  void SetFrame(nsIFrame* aFrame)
  {
    NS_PRECONDITION(aFrame, "Need a frame");

    nsIFrame *prevContinuation = aFrame->GetPrevContinuation();

    if (!prevContinuation || mFrame != prevContinuation) {
      // Ok, we've got the wrong frame.  We have to start from scratch.
      Reset();
      Init(aFrame);
      return;
    }

    // Get our last frame's size and add its width to our continuation
    // point before we cache the new frame.
    mContinuationPoint += mFrame->GetSize().width;

    // If this a new line, update mLineContinuationPoint.
    if (mBidiEnabled &&
        (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) {
       mLineContinuationPoint = mContinuationPoint;
    }
    
    mFrame = aFrame;
  }

  void Init(nsIFrame* aFrame)
  {    
    // Start with the previous flow frame as our continuation point
    // is the total of the widths of the previous frames.
    nsIFrame* inlineFrame = aFrame->GetPrevContinuation();

    while (inlineFrame) {
      nsRect rect = inlineFrame->GetRect();
      mContinuationPoint += rect.width;
      mUnbrokenWidth += rect.width;
      mBoundingBox.UnionRect(mBoundingBox, rect);
      inlineFrame = inlineFrame->GetPrevContinuation();
    }

    // Next add this frame and subsequent frames to the bounding box and
    // unbroken width.
    inlineFrame = aFrame;
    while (inlineFrame) {
      nsRect rect = inlineFrame->GetRect();
      mUnbrokenWidth += rect.width;
      mBoundingBox.UnionRect(mBoundingBox, rect);
      inlineFrame = inlineFrame->GetNextContinuation();
    }

    mFrame = aFrame;

    mBidiEnabled = aFrame->PresContext()->BidiEnabled();
    if (mBidiEnabled) {
      // Find the containing block frame
      nsIFrame* frame = aFrame;
      nsresult rv = NS_ERROR_FAILURE;
      while (frame &&
             frame->IsFrameOfType(nsIFrame::eLineParticipant) &&
             NS_FAILED(rv)) {
        frame = frame->GetParent();
        rv = frame->QueryInterface(kBlockFrameCID, (void**)&mBlockFrame);
      }
      if (NS_FAILED(rv) || !mBlockFrame) {
        NS_NOTREACHED("Cannot find containing block.");
        // If we can't find a block, pretend bidi isn't enabled.
        mBidiEnabled = PR_FALSE;
        mBlockFrame = nsnull;
      }

      mLineContinuationPoint = mContinuationPoint;
    }
  }
  
  PRBool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) {
    // Assumes that aFrame1 and aFrame2 are both decsendants of mBlockFrame.
    PRBool isValid1, isValid2;
    nsBlockInFlowLineIterator it1(mBlockFrame, aFrame1, &isValid1);
    nsBlockInFlowLineIterator it2(mBlockFrame, aFrame2, &isValid2);
    return isValid1 && isValid2 && it1.GetLine() == it2.GetLine();
  }
};

static InlineBackgroundData* gInlineBGData = nsnull;

// FillRect or InvertRect depending on the renderingaInvert parameter
static void FillOrInvertRect(nsIRenderingContext& aRC,nscoord aX, nscoord aY, nscoord aWidth, nscoord aHeight, PRBool aInvert);
static void FillOrInvertRect(nsIRenderingContext& aRC,const nsRect& aRect, PRBool aInvert);

// Initialize any static variables used by nsCSSRendering.
nsresult nsCSSRendering::Init()
{  
  NS_ASSERTION(!gInlineBGData, "Init called twice");
  gInlineBGData = new InlineBackgroundData();
  if (!gInlineBGData)
    return NS_ERROR_OUT_OF_MEMORY;

  return NS_OK;
}

// Clean up any global variables used by nsCSSRendering.
void nsCSSRendering::Shutdown()
{
  delete gInlineBGData;
  gInlineBGData = nsnull;
}

// Draw a line, skipping that portion which crosses aGap. aGap defines a rectangle gap
// This services fieldset legends and only works for coords defining horizontal lines.
void nsCSSRendering::DrawLine (nsIRenderingContext& aContext, 
                               nscoord aX1, nscoord aY1, nscoord aX2, nscoord aY2,
                               nsRect* aGap)
{
  if (nsnull == aGap) {
    aContext.DrawLine(aX1, aY1, aX2, aY2);
  } else {
    nscoord x1 = (aX1 < aX2) ? aX1 : aX2;
    nscoord x2 = (aX1 < aX2) ? aX2 : aX1;
    nsPoint gapUpperRight(aGap->x + aGap->width, aGap->y);
    nsPoint gapLowerRight(aGap->x + aGap->width, aGap->y + aGap->height);
    if ((aGap->y <= aY1) && (gapLowerRight.y >= aY2)) {
      if ((aGap->x > x1) && (aGap->x < x2)) {
        aContext.DrawLine(x1, aY1, aGap->x, aY1);
      } 
      if ((gapLowerRight.x > x1) && (gapLowerRight.x < x2)) {
        aContext.DrawLine(gapUpperRight.x, aY2, x2, aY2);
      } 
    } else {
      aContext.DrawLine(aX1, aY1, aX2, aY2);
    }
  }
}

// Fill a polygon, skipping that portion which crosses aGap. aGap defines a rectangle gap
// This services fieldset legends and only works for points defining a horizontal rectangle 
void nsCSSRendering::FillPolygon (nsIRenderingContext& aContext, 
                                  const nsPoint aPoints[],
                                  PRInt32 aNumPoints,
                                  nsRect* aGap)
{

  if (nsnull == aGap) {
    aContext.FillPolygon(aPoints, aNumPoints);
  } else if (4 == aNumPoints) {
    nsPoint gapUpperRight(aGap->x + aGap->width, aGap->y);
    nsPoint gapLowerRight(aGap->x + aGap->width, aGap->y + aGap->height);

    // sort the 4 points by x
    nsPoint points[4];
    for (PRInt32 pX = 0; pX < 4; pX++) {
      points[pX] = aPoints[pX];
    }
    for (PRInt32 i = 0; i < 3; i++) {
      for (PRInt32 j = i+1; j < 4; j++) { 
        if (points[j].x < points[i].x) {
          nsPoint swap = points[i];
          points[i] = points[j];
          points[j] = swap;
        }
      }
    }

    nsPoint upperLeft  = (points[0].y <= points[1].y) ? points[0] : points[1];
    nsPoint lowerLeft  = (points[0].y <= points[1].y) ? points[1] : points[0];
    nsPoint upperRight = (points[2].y <= points[3].y) ? points[2] : points[3];
    nsPoint lowerRight = (points[2].y <= points[3].y) ? points[3] : points[2];


    if ((aGap->y <= upperLeft.y) && (gapLowerRight.y >= lowerRight.y)) {
      if ((aGap->x > upperLeft.x) && (aGap->x < upperRight.x)) {
        nsPoint leftRect[4];
        leftRect[0] = upperLeft;
        leftRect[1] = nsPoint(aGap->x, upperLeft.y);
        leftRect[2] = nsPoint(aGap->x, lowerLeft.y);
        leftRect[3] = lowerLeft;
        aContext.FillPolygon(leftRect, 4);
      } 
      if ((gapUpperRight.x > upperLeft.x) && (gapUpperRight.x < upperRight.x)) {
        nsPoint rightRect[4];
        rightRect[0] = nsPoint(gapUpperRight.x, upperRight.y);
        rightRect[1] = upperRight;
        rightRect[2] = lowerRight;
        rightRect[3] = nsPoint(gapLowerRight.x, lowerRight.y);
        aContext.FillPolygon(rightRect, 4);
      } 
    } else {
      aContext.FillPolygon(aPoints, aNumPoints);
    }      
  }
}

/**
 * Make a bevel color
 */
nscolor nsCSSRendering::MakeBevelColor(PRIntn whichSide, PRUint8 style,
                                       nscolor aBackgroundColor,
                                       nscolor aBorderColor)
{

  nscolor colors[2];
  nscolor theColor;

  // Given a background color and a border color
  // calculate the color used for the shading
  NS_GetSpecial3DColors(colors, aBackgroundColor, aBorderColor);
 
  if ((style == NS_STYLE_BORDER_STYLE_OUTSET) ||
      (style == NS_STYLE_BORDER_STYLE_RIDGE)) {
    // Flip colors for these two border styles
    switch (whichSide) {
    case NS_SIDE_BOTTOM: whichSide = NS_SIDE_TOP;    break;
    case NS_SIDE_RIGHT:  whichSide = NS_SIDE_LEFT;   break;
    case NS_SIDE_TOP:    whichSide = NS_SIDE_BOTTOM; break;
    case NS_SIDE_LEFT:   whichSide = NS_SIDE_RIGHT;  break;
    }
  }

  switch (whichSide) {
  case NS_SIDE_BOTTOM:
    theColor = colors[1];
    break;
  case NS_SIDE_RIGHT:
    theColor = colors[1];
    break;
  case NS_SIDE_TOP:
    theColor = colors[0];
    break;
  case NS_SIDE_LEFT:
  default:
    theColor = colors[0];
    break;
  }
  return theColor;
}

// Maximum poly points in any of the polygons we generate below
#define MAX_POLY_POINTS 4

#define ACTUAL_THICKNESS(outside, inside, frac, tpp) \
  (NSToCoordRound(((outside) - (inside)) * (frac) / (tpp)) * (tpp))


/**
 * Draw a dotted/dashed sides of a box
 */
//XXX dashes which span more than two edges are not handled properly MMP
void nsCSSRendering::DrawDashedSides(PRIntn startSide,
                                     nsIRenderingContext& aContext,
                   /* XXX unused */  const nsRect& aDirtyRect,
                                     const PRUint8 borderStyles[],  
                                     const nscolor borderColors[],  
                                     const nsRect& borderOutside,
                                     const nsRect& borderInside,
                                     PRIntn aSkipSides,
                   /* XXX unused */  nsRect* aGap)
{
PRIntn  dashLength;
nsRect  dashRect, firstRect, currRect;
PRBool  bSolid = PR_TRUE;
float   over = 0.0f;
PRUint8 style = borderStyles[startSide];  
PRBool  skippedSide = PR_FALSE;

  for (PRIntn whichSide = startSide; whichSide < 4; whichSide++) {
    PRUint8 prevStyle = style;
    style = borderStyles[whichSide];  
    if ((1<<whichSide) & aSkipSides) {
      // Skipped side
      skippedSide = PR_TRUE;
      continue;
    }
    if ((style == NS_STYLE_BORDER_STYLE_DASHED) ||
        (style == NS_STYLE_BORDER_STYLE_DOTTED))
    {
      if ((style != prevStyle) || skippedSide) {
        //style discontinuity
        over = 0.0f;
        bSolid = PR_TRUE;
      }

      // XXX units for dash & dot?
      if (style == NS_STYLE_BORDER_STYLE_DASHED) {
        dashLength = DASH_LENGTH;
      } else {
        dashLength = DOT_LENGTH;
      }

      aContext.SetColor(borderColors[whichSide]);  
      switch (whichSide) {
      case NS_SIDE_LEFT:
        //XXX need to properly handle wrap around from last edge to first edge
        //(this is the first edge) MMP
        dashRect.width = borderInside.x - borderOutside.x;
        dashRect.height = nscoord(dashRect.width * dashLength);
        dashRect.x = borderOutside.x;
        dashRect.y = borderInside.YMost() - dashRect.height;

        if (over > 0.0f) {
          firstRect.x = dashRect.x;
          firstRect.width = dashRect.width;
          firstRect.height = nscoord(dashRect.height * over);
          firstRect.y = dashRect.y + (dashRect.height - firstRect.height);
          over = 0.0f;
          currRect = firstRect;
        } else {
          currRect = dashRect;
        }

        while (currRect.YMost() > borderInside.y) {
          //clip if necessary
          if (currRect.y < borderInside.y) {
            over = float(borderInside.y - dashRect.y) /
              float(dashRect.height);
            currRect.height = currRect.height - (borderInside.y - currRect.y);
            currRect.y = borderInside.y;
          }

          //draw if necessary
          if (bSolid) {
            aContext.FillRect(currRect);
          }

          //setup for next iteration
          if (over == 0.0f) {
            bSolid = PRBool(!bSolid);
          }
          dashRect.y = dashRect.y - currRect.height;
          currRect = dashRect;
        }
        break;

      case NS_SIDE_TOP:
        //if we are continuing a solid rect, fill in the corner first
        if (bSolid) {
          aContext.FillRect(borderOutside.x, borderOutside.y,
                            borderInside.x - borderOutside.x,
                            borderInside.y - borderOutside.y);
        }

        dashRect.height = borderInside.y - borderOutside.y;
        dashRect.width = dashRect.height * dashLength;
        dashRect.x = borderInside.x;
        dashRect.y = borderOutside.y;

        if (over > 0.0f) {
          firstRect.x = dashRect.x;
          firstRect.y = dashRect.y;
          firstRect.width = nscoord(dashRect.width * over);
          firstRect.height = dashRect.height;
          over = 0.0f;
          currRect = firstRect;
        } else {
          currRect = dashRect;
        }

        while (currRect.x < borderInside.XMost()) {
          //clip if necessary
          if (currRect.XMost() > borderInside.XMost()) {
            over = float(dashRect.XMost() - borderInside.XMost()) /
              float(dashRect.width);
            currRect.width = currRect.width -
              (currRect.XMost() - borderInside.XMost());
          }

          //draw if necessary
          if (bSolid) {
            aContext.FillRect(currRect);
          }

          //setup for next iteration
          if (over == 0.0f) {
            bSolid = PRBool(!bSolid);
          }
          dashRect.x = dashRect.x + currRect.width;
          currRect = dashRect;
        }
        break;

      case NS_SIDE_RIGHT:
        //if we are continuing a solid rect, fill in the corner first
        if (bSolid) {
          aContext.FillRect(borderInside.XMost(), borderOutside.y,
                            borderOutside.XMost() - borderInside.XMost(),
                            borderInside.y - borderOutside.y);
        }

        dashRect.width = borderOutside.XMost() - borderInside.XMost();
        dashRect.height = nscoord(dashRect.width * dashLength);
        dashRect.x = borderInside.XMost();
        dashRect.y = borderInside.y;

        if (over > 0.0f) {
          firstRect.x = dashRect.x;
          firstRect.y = dashRect.y;
          firstRect.width = dashRect.width;
          firstRect.height = nscoord(dashRect.height * over);
          over = 0.0f;
          currRect = firstRect;
        } else {
          currRect = dashRect;
        }

        while (currRect.y < borderInside.YMost()) {
          //clip if necessary
          if (currRect.YMost() > borderInside.YMost()) {
            over = float(dashRect.YMost() - borderInside.YMost()) /
              float(dashRect.height);
            currRect.height = currRect.height -
              (currRect.YMost() - borderInside.YMost());
          }

          //draw if necessary
          if (bSolid) {
            aContext.FillRect(currRect);
          }

          //setup for next iteration
          if (over == 0.0f) {
            bSolid = PRBool(!bSolid);
          }
          dashRect.y = dashRect.y + currRect.height;
          currRect = dashRect;
        }
        break;

      case NS_SIDE_BOTTOM:
        //if we are continuing a solid rect, fill in the corner first
        if (bSolid) {
          aContext.FillRect(borderInside.XMost(), borderInside.YMost(),
                            borderOutside.XMost() - borderInside.XMost(),
                            borderOutside.YMost() - borderInside.YMost());
        }

        dashRect.height = borderOutside.YMost() - borderInside.YMost();
        dashRect.width = nscoord(dashRect.height * dashLength);
        dashRect.x = borderInside.XMost() - dashRect.width;
        dashRect.y = borderInside.YMost();

        if (over > 0.0f) {
          firstRect.y = dashRect.y;
          firstRect.width = nscoord(dashRect.width * over);
          firstRect.height = dashRect.height;
          firstRect.x = dashRect.x + (dashRect.width - firstRect.width);
          over = 0.0f;
          currRect = firstRect;
        } else {
          currRect = dashRect;
        }

        while (currRect.XMost() > borderInside.x) {
          //clip if necessary
          if (currRect.x < borderInside.x) {
            over = float(borderInside.x - dashRect.x) / float(dashRect.width);
            currRect.width = currRect.width - (borderInside.x - currRect.x);
            currRect.x = borderInside.x;
          }

          //draw if necessary
          if (bSolid) {
            aContext.FillRect(currRect);
          }

          //setup for next iteration
          if (over == 0.0f) {
            bSolid = PRBool(!bSolid);
          }
          dashRect.x = dashRect.x - currRect.width;
          currRect = dashRect;
        }
        break;
      }
    }
    skippedSide = PR_FALSE;
  }
}

/** ---------------------------------------------------
 *  See documentation in nsCSSRendering.h
 *  @update 10/22/99 dwc
 */
void nsCSSRendering::DrawDashedSides(PRIntn startSide,
                                     nsIRenderingContext& aContext,
                                     const nsRect& aDirtyRect,
                                     const nsStyleColor* aColorStyle,
                                     const nsStyleBorder* aBorderStyle,  
                                     const nsStyleOutline* aOutlineStyle,  
                                     PRBool aDoOutline,
                                     const nsRect& borderOutside,
                                     const nsRect& borderInside,
                                     PRIntn aSkipSides,
                   /* XXX unused */  nsRect* aGap)
{

PRIntn  dashLength;
nsRect  dashRect, currRect;
nscoord temp, temp1, adjust;
PRBool  bSolid = PR_TRUE;
float   over = 0.0f;
PRBool  skippedSide = PR_FALSE;

  NS_ASSERTION(aColorStyle &&
               ((aDoOutline && aOutlineStyle) || (!aDoOutline && aBorderStyle)),
               "null params not allowed");
  PRUint8 style = aDoOutline
                  ? aOutlineStyle->GetOutlineStyle()
                  : aBorderStyle->GetBorderStyle(startSide);  

  // find the x and y width
  nscoord xwidth = aDirtyRect.XMost();
  nscoord ywidth = aDirtyRect.YMost();

  for (PRIntn whichSide = startSide; whichSide < 4; whichSide++) {
    PRUint8 prevStyle = style;
    style = aDoOutline
              ? aOutlineStyle->GetOutlineStyle()
              : aBorderStyle->GetBorderStyle(whichSide);  
    if ((1<<whichSide) & aSkipSides) {
      // Skipped side
      skippedSide = PR_TRUE;
      continue;
    }
    if ((style == NS_STYLE_BORDER_STYLE_DASHED) ||
        (style == NS_STYLE_BORDER_STYLE_DOTTED))
    {
      if ((style != prevStyle) || skippedSide) {
        //style discontinuity
        over = 0.0f;
        bSolid = PR_TRUE;
      }

      if (style == NS_STYLE_BORDER_STYLE_DASHED) {
        dashLength = DASH_LENGTH;
      } else {
        dashLength = DOT_LENGTH;
      }

      // default to current color in case color cannot be resolved
      // (because invert is not supported on cur platform)
      nscolor sideColor(aColorStyle->mColor);

      PRBool  isInvert = PR_FALSE;
      if (aDoOutline) {
        if (!aOutlineStyle->GetOutlineInitialColor()) {
          aOutlineStyle->GetOutlineColor(sideColor);
        }
#ifdef GFX_HAS_INVERT
        else {
          isInvert = PR_TRUE;
        }
#endif
      } else {
        PRBool transparent; 
        PRBool foreground;
        aBorderStyle->GetBorderColor(whichSide, sideColor, transparent, foreground);
        if (foreground)
          sideColor = aColorStyle->mColor;
        if (transparent)
          continue; // side is transparent
      }
      aContext.SetColor(sideColor);  

      switch (whichSide) {
      case NS_SIDE_RIGHT:
      case NS_SIDE_LEFT:
        bSolid = PR_FALSE;
        
        // This is our dot or dash..
        if(whichSide==NS_SIDE_LEFT){ 
          dashRect.width = borderInside.x - borderOutside.x;
        } else {
          dashRect.width = borderOutside.XMost() - borderInside.XMost();
        }
        if( dashRect.width >0 ) {
          dashRect.height = dashRect.width * dashLength;
          dashRect.y = borderOutside.y;

          if(whichSide == NS_SIDE_RIGHT){
            dashRect.x = borderInside.XMost();
          } else {
            dashRect.x = borderOutside.x;
          }

          temp = borderOutside.height;
          temp1 = temp/dashRect.height;

          currRect = dashRect;

          if((temp1%2)==0){
            adjust = (dashRect.height-(temp%dashRect.height))/2; // adjust back
            // draw in the left and right
            FillOrInvertRect(aContext,  dashRect.x, borderOutside.y,dashRect.width, dashRect.height-adjust,isInvert);
            FillOrInvertRect(aContext,dashRect.x,(borderOutside.YMost()-(dashRect.height-adjust)),dashRect.width, dashRect.height-adjust,isInvert);
            currRect.y += (dashRect.height-adjust);
            temp-= (dashRect.height-adjust);
          } else {
            adjust = (temp%dashRect.width)/2;                   // adjust a tad longer
            // draw in the left and right
            FillOrInvertRect(aContext, dashRect.x, borderOutside.y,dashRect.width, dashRect.height+adjust,isInvert);
            FillOrInvertRect(aContext, dashRect.x,(borderOutside.YMost()-(dashRect.height+adjust)),dashRect.width, dashRect.height+adjust,isInvert);
            currRect.y += (dashRect.height+adjust);
            temp-= (dashRect.height+adjust);
          }
        
          temp += borderOutside.y;
          if( temp > ywidth)
            temp = ywidth;

          // get the currRect's x into the view before we start
          if( currRect.y < aDirtyRect.y){
            temp1 = NSToCoordFloor((float)((aDirtyRect.y-currRect.y)/dashRect.height));
            currRect.y += temp1*dashRect.height;
            if((temp1%2)==1){
              bSolid = PR_TRUE;
            }
          }

          while(currRect.y<temp) {
            //draw if necessary
            if (bSolid) {
              FillOrInvertRect(aContext, currRect,isInvert);
            }

            bSolid = PRBool(!bSolid);
            currRect.y += dashRect.height;
          }
        }
        break;

      case NS_SIDE_BOTTOM:
      case NS_SIDE_TOP:
        bSolid = PR_FALSE;
        
        // This is our dot or dash..

        if(whichSide==NS_SIDE_TOP){ 
          dashRect.height = borderInside.y - borderOutside.y;
        } else {
          dashRect.height = borderOutside.YMost() - borderInside.YMost();
        }
        if( dashRect.height >0 ) {
          dashRect.width = dashRect.height * dashLength;
          dashRect.x = borderOutside.x;

          if(whichSide == NS_SIDE_BOTTOM){
            dashRect.y = borderInside.YMost();
          } else {
            dashRect.y = borderOutside.y;
          }

          temp = borderOutside.width;
          temp1 = temp/dashRect.width;

          currRect = dashRect;

          if((temp1%2)==0){
            adjust = (dashRect.width-(temp%dashRect.width))/2;     // even, adjust back
            // draw in the left and right
            FillOrInvertRect(aContext, borderOutside.x,dashRect.y,dashRect.width-adjust,dashRect.height,isInvert);
            FillOrInvertRect(aContext, (borderOutside.XMost()-(dashRect.width-adjust)),dashRect.y,dashRect.width-adjust,dashRect.height,isInvert);
            currRect.x += (dashRect.width-adjust);
            temp-= (dashRect.width-adjust);
          } else {
            adjust = (temp%dashRect.width)/2;
            // draw in the left and right
            FillOrInvertRect(aContext, borderOutside.x,dashRect.y,dashRect.width+adjust,dashRect.height,isInvert);
            FillOrInvertRect(aContext, (borderOutside.XMost()-(dashRect.width+adjust)),dashRect.y,dashRect.width+adjust,dashRect.height,isInvert);
            currRect.x += (dashRect.width+adjust);
            temp-= (dashRect.width+adjust);
          }
       
          temp += borderOutside.x;
          if( temp > xwidth)
            temp = xwidth;

          // get the currRect's x into the view before we start
          if( currRect.x < aDirtyRect.x){
            temp1 = NSToCoordFloor((float)((aDirtyRect.x-currRect.x)/dashRect.width));
            currRect.x += temp1*dashRect.width;
            if((temp1%2)==1){
              bSolid = PR_TRUE;
            }
          }

          while(currRect.x<temp) {
            //draw if necessary
            if (bSolid) {
              FillOrInvertRect(aContext, currRect,isInvert);
            }

            bSolid = PRBool(!bSolid);
            currRect.x += dashRect.width;
          }
        }
      break;
      }
    }
    skippedSide = PR_FALSE;
  }
}

nscolor
nsCSSRendering::TransformColor(nscolor  aMapColor,PRBool aNoBackGround)
{
PRUint16  hue,sat,value;
nscolor   newcolor;

  newcolor = aMapColor;
  if (PR_TRUE == aNoBackGround){
    // convert the RBG to HSV so we can get the lightness (which is the v)
    NS_RGB2HSV(newcolor,hue,sat,value);
    // The goal here is to send white to black while letting colored
    // stuff stay colored... So we adopt the following approach.
    // Something with sat = 0 should end up with value = 0.  Something
    // with a high sat can end up with a high value and it's ok.... At
    // the same time, we don't want to make things lighter.  Do
    // something simple, since it seems to work.
    if (value > sat) {
      value = sat;
      // convert this color back into the RGB color space.
      NS_HSV2RGB(newcolor,hue,sat,value);
    }
  }
  return newcolor;
}

//----------------------------------------------------------------------
// Thebes Border Rendering Code Start

#undef DEBUG_NEW_BORDERS

#ifdef DEBUG_NEW_BORDERS
#include <stdarg.h>

static inline void S(const gfxPoint& p) {
  fprintf (stderr, "[%f,%f]", p.x, p.y);
}

static inline void S(const gfxSize& s) {
  fprintf (stderr, "[%f %f]", s.width, s.height);
}

static inline void S(const gfxRect& r) {
  fprintf (stderr, "[%f %f %f %f]", r.pos.x, r.pos.y, r.size.width, r.size.height);
}

static inline void S(const gfxFloat f) {
  fprintf (stderr, "%f", f);
}

static inline void S(const char *s) {
  fprintf (stderr, "%s", s);
}

static inline void SN(const char *s = nsnull) {
  if (s)
    fprintf (stderr, "%s", s);
  fprintf (stderr, "\n");
  fflush (stderr);
}

static inline void SF(const char *fmt, ...) {
  va_list vl;
  va_start(vl, fmt);
  vfprintf (stderr, fmt, vl);
  va_end(vl);
}

static inline void SX(gfxContext *ctx) {
  gfxPoint p = ctx->CurrentPoint();
  fprintf (stderr, "p: %f %f\n", p.x, p.y);
  return;
  ctx->MoveTo(p + gfxPoint(-2, -2)); ctx->LineTo(p + gfxPoint(2, 2));
  ctx->MoveTo(p + gfxPoint(-2, 2)); ctx->LineTo(p + gfxPoint(2, -2));
  ctx->MoveTo(p);
}


#else
static inline void S(const gfxPoint& p) {}
static inline void S(const gfxSize& s) {}
static inline void S(const gfxRect& r) {}
static inline void S(const gfxFloat f) {}
static inline void S(const char *s) {}
static inline void SN(const char *s = nsnull) {}
static inline void SF(const char *fmt, ...) {}
static inline void SX(gfxContext *ctx) {}
#endif

// the static order in which we paint sides
static const PRUint8 gBorderSideOrder[] = { NS_SIDE_TOP, NS_SIDE_RIGHT, NS_SIDE_BOTTOM, NS_SIDE_LEFT };

// little helper function to check if the array of 4 floats given are
// equal to the given value
static PRBool
CheckFourFloatsEqual(const gfxFloat *vals, gfxFloat k)
{
  if (vals[0] == k &&
      vals[1] == k &&
      vals[2] == k &&
      vals[3] == k)
    return PR_TRUE;

  return PR_FALSE;
}

// another helper function to convert a nsRect to a gfxRect
static gfxRect
RectToGfxRect(const nsRect& rect, nscoord twipsPerPixel)
{
  return gfxRect(gfxFloat(rect.x) / twipsPerPixel,
                 gfxFloat(rect.y) / twipsPerPixel,
                 gfxFloat(rect.width) / twipsPerPixel,
                 gfxFloat(rect.height) / twipsPerPixel);
}


/*
 * Figure out whether we need to draw using separate side rendering or
 * not.
 *
 * The only case where we can draw the border in one pass if, for all sides:
 *  - the same style is used, and it is SOLID, DOUBLE, DASHED, or DOTTED
 *  - the same color is used
 *
 * We can draw the border in two passes if, for all sides:
 *  - the same style is used, and it is INSET, OUTSET, GROOVE, or RIDGE
 *  - the same color is used
 * 
 * Otherwise, we have do all 4 sides separately.  Generally this only
 * happens if we have different colors on the different sides.
 */
static PRUint8
NumBorderPasses (PRUint8 *borderStyles,
                 nscolor *borderColors,
                 nsBorderColors **compositeColors)
{
  PRUint8 numBorderPasses = 1;
  PRUint8 firstSideStyle = borderStyles[0];
  nscolor firstSideColor = borderColors[0];

  for (int i = 0; i < 4; i++) {
    PRUint8 borderRenderStyle = borderStyles[i];

    // split into 4 if:
    // - the styles don't match
    // - the colors don't match
    // - there are any compositeColors
    if (borderRenderStyle != firstSideStyle ||
        borderColors[i] != firstSideColor ||
        compositeColors[i])
      return 4;

    switch (borderRenderStyle) {
      case NS_STYLE_BORDER_STYLE_INSET:
      case NS_STYLE_BORDER_STYLE_OUTSET:
      case NS_STYLE_BORDER_STYLE_GROOVE:
      case NS_STYLE_BORDER_STYLE_RIDGE:
        numBorderPasses = 2;
        break;

      case NS_STYLE_BORDER_STYLE_SOLID:
      case NS_STYLE_BORDER_STYLE_DOUBLE:
      case NS_STYLE_BORDER_STYLE_DASHED:
      case NS_STYLE_BORDER_STYLE_DOTTED:
        // we can do this as 1, if everything else is ok
        break;

      default:
        return 4;
    }
  }

  // everything's transparent
  if (firstSideColor == 0x0)
    return 0;

  return numBorderPasses;
}

#define C_TL 0
#define C_TR 1
#define C_BR 2
#define C_BL 3

#ifndef NS_PI
#define NS_PI 3.14159265358979323846
#endif

/* Return the dimensions of the corners of the border area, taking
 * into account any border radius.  The width and height of each
 * corner (in order of TL, TR, BR, BL) is returned in oDims, which
 * should be a 4-element array of gfxSize.
 */

// How much of the actual corner size to call the "corner" for the
// dimensions.  Must be >= 1.0; anything over 1.0 will give more of a
// corner in dotted/dashed rendering cases.  It's not clear whether >=
// 1.0 looks better.
#define CORNER_FACTOR 1.0

static void
GetBorderCornerDimensions(const gfxRect& oRect,
                          const gfxRect& iRect,
                          const gfxFloat *radii,
                          gfxSize *oDims)
{
  gfxFloat topWidth = iRect.pos.y - oRect.pos.y;
  gfxFloat leftWidth = iRect.pos.x - oRect.pos.x;
  gfxFloat rightWidth = oRect.size.width - iRect.size.width - leftWidth;
  gfxFloat bottomWidth = oRect.size.height - iRect.size.height - topWidth;

  if (radii) {
    leftWidth = PR_MAX(leftWidth, PR_MAX(radii[C_TL], radii[C_BL]));
    topWidth = PR_MAX(topWidth, PR_MAX(radii[C_TL], radii[C_TR]));
    rightWidth = PR_MAX(rightWidth, PR_MAX(radii[C_TR], radii[C_BR]));
    bottomWidth = PR_MAX(bottomWidth, PR_MAX(radii[C_BR], radii[C_BL]));
  }

  oDims[C_TL] = gfxSize(leftWidth, topWidth);
  oDims[C_TR] = gfxSize(rightWidth, topWidth);
  oDims[C_BL] = gfxSize(leftWidth, bottomWidth);
  oDims[C_BR] = gfxSize(rightWidth, bottomWidth);
}

/* Set up a path for rendering just the corners of the path.  Executed
 * by computing the corner dimensions, and then drawing rectangles for
 * each corner.
 * 
 * Because this function is used mainly for dashed rendering, the
 * sides that don't have a dotted/dashed styles are also included.
 */

static void
DoCornerClipSubPath(gfxContext *ctx,
                    const gfxRect& oRect,
                    const gfxRect& iRect,
                    const gfxFloat *radii,
                    PRIntn dashedSides = 0xff)
{
  gfxSize dims[4];

  GetBorderCornerDimensions(oRect, iRect, radii, dims);

  gfxRect tl(oRect.pos.x,
             oRect.pos.y,
             dims[C_TL].width,
             dims[C_TL].height);

  gfxRect tr(oRect.pos.x + oRect.size.width - dims[C_TR].width,
             oRect.pos.y,
             dims[C_TR].width,
             dims[C_TR].height);

  gfxRect br(oRect.pos.x + oRect.size.width - dims[C_BR].width,
             oRect.pos.y + oRect.size.height - dims[C_BR].height,
             dims[C_BR].width,
             dims[C_BR].height);

  gfxRect bl(oRect.pos.x,
             oRect.pos.y + oRect.size.height - dims[C_BL].height,
             dims[C_BL].width,
             dims[C_BL].height);

  ctx->Rectangle(tl);
  ctx->Rectangle(tr);
  ctx->Rectangle(br);
  ctx->Rectangle(bl);

  // Now if any of the sides are not dashed, include that full side.
  if (!(dashedSides & SIDE_BIT_TOP)) {
    ctx->Rectangle(gfxRect(tl.pos.x,
                           tl.pos.y,
                           oRect.size.width,
                           dims[C_TL].height));
  }

  if (!(dashedSides & SIDE_BIT_RIGHT)) {
    ctx->Rectangle(gfxRect(tr.pos.x,
                           tr.pos.y,
                           dims[C_TR].width,
                           oRect.size.height));
  }

  if (!(dashedSides & SIDE_BIT_BOTTOM)) {
    ctx->Rectangle(gfxRect(oRect.pos.x,
                           br.pos.y,
                           oRect.size.width,
                           dims[C_BR].height));
  }

  if (!(dashedSides & SIDE_BIT_LEFT)) {
    ctx->Rectangle(gfxRect(oRect.pos.x,
                           oRect.pos.y,
                           dims[C_BL].width,
                           oRect.size.height));
  }
}

// Draw a path for a rounded rectangle with the corners rounded by the
// given radii, with the path going clockwise.
static void
DoRoundedRectCWSubPath(gfxContext *ctx,
                       const gfxRect& sRect,
                       const gfxFloat *radii)
{
  ctx->Translate(sRect.pos);

  ctx->MoveTo(gfxPoint(sRect.size.width - radii[C_TR], 0.0));
  SX(ctx);
            
  if (radii[C_TR]) {
    ctx->Arc(gfxPoint(sRect.size.width - radii[C_TR], radii[C_TR]),
             radii[C_TR],
             3.0 * NS_PI / 2.0,
             0.0);
    SX(ctx);
  }

  ctx->LineTo(gfxPoint(sRect.size.width, sRect.size.height - radii[C_BR]));
  SX(ctx);

  if (radii[C_BR]) {
    ctx->Arc(gfxPoint(sRect.size.width - radii[C_BR], sRect.size.height - radii[C_BR]),
             radii[C_BR],
             0.0,
             NS_PI / 2.0);
    SX(ctx);
  }

  ctx->LineTo(gfxPoint(radii[C_BL], sRect.size.height));
  SX(ctx);
      
  if (radii[C_BL]) {
    ctx->Arc(gfxPoint(radii[C_BL], sRect.size.height - radii[C_BL]),
             radii[C_BL],
             NS_PI / 2.0,
             NS_PI);
    SX(ctx);
  }

  ctx->LineTo(gfxPoint(0.0, radii[C_TL]));
  SX(ctx);

  if (radii[C_TL]) {
    ctx->Arc(gfxPoint(radii[C_TL], radii[C_TL]),
             radii[C_TL],
             NS_PI,
             3.0 * NS_PI / 2.0);
    SX(ctx);
  }

  ctx->ClosePath();

  ctx->Translate(-sRect.pos);
}

// Draw a path for a rounded rectangle with the corners rounded by the
// given radii, with the path going counterclockwise.
static void
DoRoundedRectCCWSubPath(gfxContext *ctx,
                        const gfxRect& sRect,
                        const gfxFloat *radii)
{
  ctx->Translate(sRect.pos);

  ctx->MoveTo(gfxPoint(radii[C_TL], 0.0));

  if (radii[C_TL]) {
    ctx->NegativeArc(gfxPoint(radii[C_TL], radii[C_TL]),
                     radii[C_TL],
                     3.0 * NS_PI / 2.0,
                     NS_PI);
    SX(ctx);
  }

  ctx->LineTo(gfxPoint(0.0, sRect.size.height - radii[C_BL]));

  if (radii[C_BL]) {
    ctx->NegativeArc(gfxPoint(radii[C_BL], sRect.size.height - radii[C_BL]),
                     radii[C_BL],
                     NS_PI,
                     NS_PI / 2.0);
    SX(ctx);
  }

  ctx->LineTo(gfxPoint(sRect.size.width - radii[C_BR], sRect.size.height));

  if (radii[C_BR]) {
    ctx->NegativeArc(gfxPoint(sRect.size.width - radii[C_BR], sRect.size.height - radii[C_BR]),
                     radii[C_BR],
                     NS_PI / 2.0,
                     0.0);
    SX(ctx);
  }

  ctx->LineTo(gfxPoint(sRect.size.width, radii[C_TR]));

  if (radii[C_TR]) {
    ctx->NegativeArc(gfxPoint(sRect.size.width - radii[C_TR], radii[C_TR]),
                     radii[C_TR],
                     0.0,
                     3.0 * NS_PI / 2.0);
    SX(ctx);
  }

  ctx->ClosePath();

  ctx->Translate(-sRect.pos);
}

// Calculate the inner radii from the outer and the border sizes.
static void
CalculateInnerRadii(const gfxFloat *radii,
                    const gfxFloat *borderSizes,
                    gfxFloat *innerRadii)
{
  innerRadii[C_TL] = PR_MAX(0.0, radii[C_TL] - PR_MAX(borderSizes[NS_SIDE_TOP], borderSizes[NS_SIDE_LEFT]));
  innerRadii[C_TR] = PR_MAX(0.0, radii[C_TR] - PR_MAX(borderSizes[NS_SIDE_TOP], borderSizes[NS_SIDE_RIGHT]));
  innerRadii[C_BR] = PR_MAX(0.0, radii[C_BR] - PR_MAX(borderSizes[NS_SIDE_BOTTOM], borderSizes[NS_SIDE_RIGHT]));
  innerRadii[C_BL] = PR_MAX(0.0, radii[C_BL] - PR_MAX(borderSizes[NS_SIDE_BOTTOM], borderSizes[NS_SIDE_LEFT]));
}

// Draw the entire border path.  Intended to be filled with the
// (default) WINDING rule.
static void
DoAllSidesBorderPath(gfxContext *ctx,
                     const gfxRect &oRect,
                     const gfxRect &iRect,
                     const gfxFloat *radii,
                     const gfxFloat *borderSizes)
{
  gfxFloat innerRadii[4];
  CalculateInnerRadii(radii, borderSizes, innerRadii);

  ctx->NewPath();

  // do the outer border
  DoRoundedRectCWSubPath(ctx, oRect, radii);

  // then do the inner border
  DoRoundedRectCCWSubPath(ctx, iRect, innerRadii);
}

// Draw the top left piece of the border path.  Intended to be filled
// with the (default) WINDING rule.
static void
DoTopLeftSidesBorderPath(gfxContext *ctx,
                         const gfxRect &oRect,
                         const gfxRect &iRect,
                         const gfxFloat *radii,
                         const gfxFloat *borderSizes)
{
  gfxFloat innerRadii[4];
  CalculateInnerRadii(radii, borderSizes, innerRadii);

  ctx->NewPath();

  // start drawing counterclockwise on the outside,
  // in the first left-side straightway
  ctx->MoveTo(oRect.BottomLeft() + gfxPoint(0.0, - radii[C_BL]));

  if (radii[C_BL]) {
    ctx->NegativeArc(oRect.BottomLeft() + gfxPoint(radii[C_BL], - radii[C_BL]),
                     radii[C_BL],
                     NS_PI,
                     NS_PI * 3.0 / 4.0);
  }

  // flip here; start drawing clockwise; line between arc endpoints will
  // be filled in by cairo

  if (innerRadii[C_BL]) {
    ctx->Arc(iRect.BottomLeft() + gfxPoint(innerRadii[C_BL], - innerRadii[C_BL]),
             innerRadii[C_BL],
             NS_PI * 3.0 / 4.0,
             NS_PI);
  } else {
    ctx->LineTo(iRect.BottomLeft());
  }

  ctx->LineTo(iRect.TopLeft() + gfxPoint(0.0, innerRadii[C_TL]));

  if (innerRadii[C_TL]) {
    ctx->Arc(iRect.TopLeft() + gfxPoint(innerRadii[C_TL], innerRadii[C_TL]),
             innerRadii[C_TL],
             NS_PI,
             NS_PI * 3.0 / 2.0);
  }

  ctx->LineTo(iRect.TopRight() + gfxPoint(- innerRadii[C_TR], 0.0));

  if (innerRadii[C_TR]) {
    ctx->Arc(iRect.TopRight() + gfxPoint( - innerRadii[C_TR], innerRadii[C_TR]),
             innerRadii[C_TR],
             NS_PI * 6.0 / 4.0,
             NS_PI * 7.0 / 4.0);
  }

  // now go back

  if (radii[C_TR]) {
    ctx->NegativeArc(oRect.TopRight() + gfxPoint(- radii[C_TR], radii[C_TR]),
                     radii[C_TR],
                     NS_PI * 7.0 / 4.0,
                     NS_PI * 6.0 / 4.0);

  } else {
    ctx->LineTo(oRect.TopRight());
  }

  ctx->LineTo(oRect.TopLeft() + gfxPoint(radii[C_TL], 0.0));

  if (radii[C_TL]) {
    ctx->NegativeArc(oRect.TopLeft() + gfxPoint(radii[C_TL], radii[C_TL]),
                     radii[C_TL],
                     NS_PI * 3.0 / 2.0,
                     NS_PI);
  }

  ctx->ClosePath();
}

// Draw the bottom right piece of the border path.  Intended to be
// filled with the (default) WINDING rule.
static void
DoBottomRightSidesBorderPath(gfxContext *ctx,
                             const gfxRect &oRect,
                             const gfxRect &iRect,
                             const gfxFloat *radii,
                             const gfxFloat *borderSizes)
{
  gfxFloat innerRadii[4];
  CalculateInnerRadii(radii, borderSizes, innerRadii);

  ctx->NewPath();

  // start drawing counterclockwise on the outside,
  // in the first right-side straightway
  ctx->MoveTo(oRect.TopRight() + gfxPoint(0.0, radii[C_TR]));

  if (radii[C_TR]) {
    ctx->NegativeArc(oRect.TopRight() + gfxPoint(- radii[C_TR], radii[C_TR]),
                     radii[C_TR],
                     0.0,
                     NS_PI * 7.0 / 4.0);
  }

  // flip

  if (innerRadii[C_TR]) {
    ctx->Arc(iRect.TopRight() + gfxPoint(- innerRadii[C_TR], innerRadii[C_TR]),
             innerRadii[C_TR],
             NS_PI * 7.0 / 4.0,
             0.0);
  } else {
    ctx->LineTo(iRect.TopRight());
  }

  ctx->LineTo(iRect.BottomRight() + gfxPoint(0.0, - innerRadii[C_BR]));

  if (innerRadii[C_BR]) {
    ctx->Arc(iRect.BottomRight() + gfxPoint(- innerRadii[C_BR], - innerRadii[C_BR]),
             innerRadii[C_BR],
             0.0,
             NS_PI / 2.0);
  }

  ctx->LineTo(iRect.BottomLeft() + gfxPoint(innerRadii[C_BL], 0.0));

  if (innerRadii[C_BL]) {
    ctx->Arc(iRect.BottomLeft() + gfxPoint(innerRadii[C_BL], - innerRadii[C_BL]),
             innerRadii[C_BL],
             NS_PI / 2.0,
             NS_PI * 3.0 / 4.0);
  }

  // and flip

  if (radii[C_BL]) {
    ctx->NegativeArc(oRect.BottomLeft() + gfxPoint(radii[C_BL], - radii[C_BL]),
                     radii[C_BL],
                     NS_PI * 3.0 / 4.0,
                     NS_PI / 2.0);
  } else {
    ctx->LineTo(oRect.BottomLeft());
  }

  ctx->LineTo(oRect.BottomRight() + gfxPoint(- radii[C_BR], 0.0));

  if (radii[C_BR]) {
    ctx->NegativeArc(oRect.BottomRight() + gfxPoint(- radii[C_BR], - radii[C_BR]),
                     radii[C_BR],
                     NS_PI / 2.0,
                     0.0);
  }

  ctx->ClosePath();
}

// Given a set of sides to fill and a color, do so in the fastest way.
//
// Stroke tends to be faster for smaller borders because it doesn't go
// through the tessellator, which has initialization overhead.  If
// we're rendering all sides, we can use stroke at any thickness; we
// also do TL/BR pairs at 1px thickness using stroke.
//
// If we can't stroke, then if it's a TL/BR pair, we use the specific
// TL/BR paths.  Otherwise, we do the full path and fill.
//
// Calling code is expected to only set up a clip as necessary; no
// clip is needed if we can render the entire border in 1 or 2 passes.
static void
FillFastBorderPath(gfxContext *ctx,
                   const gfxRect &oRect,
                   const gfxRect &iRect,
                   const gfxFloat *radii,
                   const gfxFloat *borderSizes,
                   PRIntn sides,
                   const gfxRGBA& color)
{
  ctx->SetColor(color);

  if (CheckFourFloatsEqual(radii, 0.0) &&
      CheckFourFloatsEqual(borderSizes, borderSizes[0]))
  {
    if (sides == SIDE_BITS_ALL) {
      ctx->NewPath();

      gfxRect r(oRect);
      r.Inset(borderSizes[0] / 2.0);
      ctx->Rectangle(r);
      ctx->SetLineWidth(borderSizes[0]);
      ctx->Stroke();

      return;
    }

    if (sides == (SIDE_BIT_TOP | SIDE_BIT_LEFT) &&
               borderSizes[0] == 1.0 &&
               color.a == 1.0)
    {
      ctx->SetLineWidth(1.0);

      ctx->NewPath();
      ctx->MoveTo(oRect.BottomLeft() + gfxSize(0.5, 0.0));
      ctx->LineTo(oRect.TopLeft() + gfxSize(0.5, 0.5));
      ctx->LineTo(oRect.TopRight() + gfxSize(0.0, 0.5));
      ctx->Stroke();
      return;
    }

    if (sides == (SIDE_BIT_BOTTOM | SIDE_BIT_RIGHT) &&
               borderSizes[0] == 1.0 &&
               color.a == 1.0)
    {
      ctx->SetLineWidth(1.0);

      ctx->NewPath();
      ctx->MoveTo(oRect.BottomLeft() + gfxSize(0.0, -0.5));
      ctx->LineTo(oRect.BottomRight() + gfxSize(-0.5, -0.5));
      ctx->LineTo(oRect.TopRight() + gfxSize(-0.5, 0.0));
      ctx->Stroke();
      return;
    }
  }

  // we weren't able to render using stroke; do paths and fill.
  if (sides == (SIDE_BIT_TOP | SIDE_BIT_LEFT)) {
    DoTopLeftSidesBorderPath(ctx, oRect, iRect, radii, borderSizes);
  } else if (sides == (SIDE_BIT_BOTTOM | SIDE_BIT_RIGHT)) {
    DoBottomRightSidesBorderPath(ctx, oRect, iRect, radii, borderSizes);
  } else {
    DoAllSidesBorderPath(ctx, oRect, iRect, radii, borderSizes);
  }

  ctx->Fill();
}

// Create a clip path for the wedge that this side of
// the border should take up.  This is only called
// when we're drawing separate border sides, so we know
// that ADD compositing is taking place.
//
// This code needs to make sure that the individual pieces
// don't ever (mathematically) overlap; the pixel overlap
// is taken care of by the ADD compositing.
//
// The side border type and the adjacent border types are
// examined and one of the different types of clipping (listed
// below) is selected.

typedef enum {
  // clip to the trapezoid formed by the corners of the
  // inner and outer rectangles for the given side
  SIDE_CLIP_TRAPEZOID,

  // clip to the trapezoid formed by the outer rectangle
  // corners and the center of the region, making sure
  // that diagonal lines all go directly from the outside
  // corner to the inside corner, but that they then continue on
  // to the middle.
  //
  // This is needed for correctly clipping rounded borders,
  // which might extend past the SIDE_CLIP_TRAPEZOID trap.
  SIDE_CLIP_TRAPEZOID_FULL,

  // clip to the rectangle formed by the given side; a specific
  // overlap algorithm is used; see the function for details.
  // this is currently used for dashing.
  SIDE_CLIP_RECTANGLE
} SideClipType;

static void
DoSideClipSubPath(gfxContext *ctx,
                  const gfxRect& iRect,
                  const gfxRect& oRect,
                  PRUint8 whichSide,
                  const PRUint8 *borderStyles,
                  const gfxFloat *borderRadii)
{
  // the clip proceeds clockwise from the top left corner;
  // so "start" in each case is the start of the region from that side.
  //
  // the final path will be formed like:
  // s0 ------- e0
  // |         /
  // s1 ----- e1
  //
  // that is, the second point will always be on the inside

  gfxPoint start[2];
  gfxPoint end[2];

  PRUint8 style = borderStyles[whichSide];
  PRUint8 startAdjacentStyle = borderStyles[((whichSide - 1) + 4) % 4];
  PRUint8 endAdjacentStyle = borderStyles[(whichSide + 1) % 4];

  PRBool isDashed =
    (style == NS_STYLE_BORDER_STYLE_DASHED || style == NS_STYLE_BORDER_STYLE_DOTTED);
  PRBool startIsDashed =
    (startAdjacentStyle == NS_STYLE_BORDER_STYLE_DASHED || startAdjacentStyle == NS_STYLE_BORDER_STYLE_DOTTED);
  PRBool endIsDashed =
    (endAdjacentStyle == NS_STYLE_BORDER_STYLE_DASHED || endAdjacentStyle == NS_STYLE_BORDER_STYLE_DOTTED);

  PRBool startHasRadius = PR_FALSE;
  PRBool endHasRadius = PR_FALSE;

  SideClipType startType = SIDE_CLIP_TRAPEZOID;
  SideClipType endType = SIDE_CLIP_TRAPEZOID;

  if (borderRadii) {
    startHasRadius = borderRadii[whichSide] != 0.0;
    endHasRadius = borderRadii[(whichSide+1) % 4] != 0.0;
  }

  if (startHasRadius) {
    startType = SIDE_CLIP_TRAPEZOID_FULL;
  } else if (startIsDashed && isDashed) {
    startType = SIDE_CLIP_RECTANGLE;
  }

  if (endHasRadius) {
    endType = SIDE_CLIP_TRAPEZOID_FULL;
  } else if (endIsDashed && isDashed) {
    endType = SIDE_CLIP_RECTANGLE;
  }

  if (startType == SIDE_CLIP_TRAPEZOID ||
      startType == SIDE_CLIP_TRAPEZOID_FULL)
  {
    switch (whichSide) {
      case NS_SIDE_TOP:
        start[0] = oRect.TopLeft();
        start[1] = iRect.TopLeft();
        break;

      case NS_SIDE_RIGHT:
        start[0] = oRect.TopRight();
        start[1] = iRect.TopRight();
        break;

      case NS_SIDE_BOTTOM:
        start[0] = oRect.BottomRight();
        start[1] = iRect.BottomRight();
        break;

      case NS_SIDE_LEFT:
        start[0] = oRect.BottomLeft();
        start[1] = iRect.BottomLeft();
        break;
    }

    if (startType == SIDE_CLIP_TRAPEZOID_FULL) {
      gfxFloat mx = iRect.pos.x + iRect.size.width / 2.0;
      gfxFloat my = iRect.pos.y + iRect.size.height / 2.0;

      gfxPoint ps, pc;

      ps = start[1] - start[0];
      if (ps.x == 0.0 && ps.y == 0.0) {
        // do nothing; pc == start[1]
      } else if (ps.x == 0.0) {
        start[1] = start[0] + gfxSize(ps.y, ps.y);
      } else if (ps.y == 0.0) {
        start[1] = start[0] + gfxSize(ps.x, ps.x);
      } else {
        gfxFloat k = PR_MIN((mx - start[0].x) / ps.x,
                            (my - start[0].y) / ps.y);
        start[1] = start[0] + ps * k;
      }
    }
  } else if (startType == SIDE_CLIP_RECTANGLE) {
    switch (whichSide) {
      case NS_SIDE_TOP:
        start[0] = oRect.TopLeft();
        start[1] = gfxPoint(start[0].x, iRect.TopLeft().y);
        break;

      case NS_SIDE_RIGHT:
        start[0] = oRect.TopRight();
        start[1] = gfxPoint(iRect.TopRight().x, start[0].y);
        break;

      case NS_SIDE_BOTTOM:
        start[0] = oRect.BottomRight();
        start[1] = gfxPoint(start[0].x, iRect.BottomRight().y);
        break;

      case NS_SIDE_LEFT:
        start[0] = oRect.BottomLeft();
        start[1] = gfxPoint(iRect.BottomLeft().x, start[0].y);
        break;
    }
  }

  if (endType == SIDE_CLIP_TRAPEZOID ||
      endType == SIDE_CLIP_TRAPEZOID_FULL)
  {
    switch (whichSide) {
      case NS_SIDE_TOP:
        end[0] = oRect.TopRight();
        end[1] = iRect.TopRight();
        break;

      case NS_SIDE_RIGHT:
        end[0] = oRect.BottomRight();
        end[1] = iRect.BottomRight();
        break;

      case NS_SIDE_BOTTOM:
        end[0] = oRect.BottomLeft();
        end[1] = iRect.BottomLeft();
        break;

      case NS_SIDE_LEFT:
        end[0] = oRect.TopLeft();
        end[1] = iRect.TopLeft();
        break;
    }

    if (endType == SIDE_CLIP_TRAPEZOID_FULL) {
      gfxFloat mx = iRect.pos.x + iRect.size.width / 2.0;
      gfxFloat my = iRect.pos.y + iRect.size.height / 2.0;

      gfxPoint ps, pc;

      ps = end[1] - end[0];
      if (ps.x == 0.0 && ps.y == 0.0) {
        // do nothing; pc == end[1]
      } else if (ps.x == 0.0) {
        end[1] = end[0] + gfxSize(ps.y, ps.y);
      } else if (ps.y == 0.0) {
        end[1] = end[0] + gfxSize(ps.x, ps.x);
      } else {
        gfxFloat k = PR_MIN((mx - end[0].x) / ps.x,
                            (my - end[0].y) / ps.y);
        end[1] = end[0] + ps * k;
      }
    }
  } else if (endType == SIDE_CLIP_RECTANGLE) {
    switch (whichSide) {
      case NS_SIDE_TOP:
        end[0] = gfxPoint(iRect.TopRight().x, oRect.TopRight().y);
        end[1] = iRect.TopRight();
        break;

      case NS_SIDE_RIGHT:
        end[0] = gfxPoint(oRect.BottomRight().x, iRect.BottomRight().y);
        end[1] = iRect.BottomRight();
        break;

      case NS_SIDE_BOTTOM:
        end[0] = gfxPoint(iRect.BottomLeft().x, oRect.BottomLeft().y);
        end[1] = iRect.BottomLeft();
        break;

      case NS_SIDE_LEFT:
        end[0] = gfxPoint(oRect.TopLeft().x, iRect.TopLeft().y);
        end[1] = iRect.TopLeft();
        break;
    }
  }

  ctx->MoveTo(start[0]);
  ctx->LineTo(end[0]);
  ctx->LineTo(end[1]);
  ctx->LineTo(start[1]);
  ctx->ClosePath();
}

typedef enum {
  BorderColorStyleNone,
  BorderColorStyleSolid,
  BorderColorStyleLight,
  BorderColorStyleDark
} BorderColorStyle;

static void
MakeBorderColor(gfxRGBA& color, const gfxRGBA& backgroundColor, BorderColorStyle bpat)
{
  nscolor colors[2];

  switch (bpat) {
    case BorderColorStyleNone:
      color.r = 0.0;
      color.g = 0.0;
      color.b = 0.0;
      color.a = 0.0;
      break;

    case BorderColorStyleSolid:
      break;

    case BorderColorStyleLight:
      NS_GetSpecial3DColors(colors, backgroundColor.Packed(), color.Packed());
      color.r = NS_GET_R(colors[1]) / 255.0;
      color.g = NS_GET_G(colors[1]) / 255.0;
      color.b = NS_GET_B(colors[1]) / 255.0;
      color.a = 1.0;
      break;

    case BorderColorStyleDark:
      NS_GetSpecial3DColors(colors, backgroundColor.Packed(), color.Packed());
      color.r = NS_GET_R(colors[0]) / 255.0;
      color.g = NS_GET_G(colors[0]) / 255.0;
      color.b = NS_GET_B(colors[0]) / 255.0;
      color.a = 1.0;
      break;
  }
}

// Given a line index (an index starting from the outside of the
// border going inwards) and an array of line styles, calculate the
// color that that stripe of the border should be rendered in.
static void
ComputeColorForLine(PRUint32 lineIndex,
                    const BorderColorStyle* borderColorStyle,
                    PRUint32 borderColorStyleCount,
                    const nsBorderColors* borderColors,
                    PRUint32 borderColorCount,
                    nscolor borderColor,
                    nscolor backgroundColor,
                    gfxRGBA& outColor)
{
  NS_ASSERTION(lineIndex < borderColorStyleCount, "Invalid lineIndex given");

  if (borderColors) {
    if (lineIndex >= borderColorCount) {
      //outColor = gfxRGBA(borderColor);
      //return;

      // use the last color
      lineIndex = borderColorCount - 1;
    }

    while (lineIndex--)
      borderColors = borderColors->mNext;

    if (borderColors->mTransparent)
      outColor.r = outColor.g = outColor.b = outColor.a = 0.0;
    else
      outColor = gfxRGBA(borderColors->mColor);

    return;
  }

  outColor = gfxRGBA(borderColor);

  MakeBorderColor(outColor, gfxRGBA(backgroundColor), borderColorStyle[lineIndex]);
}

/**
 ** This function assumes that it can twiddle with the gfx state, and
 ** expects to be called between a Save/Restore pair.
 **/

static void
DrawBorderSides(gfxContext *ctx,           // The content to render to
                const gfxFloat *borderWidths,    // The widths of the border sides; top-right-bottom-left
                PRIntn sides,              // The specific sides we're actually rendering (bits)
                PRUint8 borderRenderStyle, // The style the border is to be rendered in
                const gfxRect& oRect,            // The outside rectangle that encompasses the entire border
                const gfxRect& iRect,            // The inner rectangle of the border
                nscolor borderRenderColor, // The base color the border is to be rendered in
                const nsBorderColors *compositeColors, // Composite colors, nsnull if none
                nscolor bgColor,           // The background color; used for computing the actual color for some styles
                nscoord twipsPerPixel,     // The current twips-per-pixel ratio
                const gfxFloat *borderRadii)     // The border radii; TL, TR, BR, BL -- nsnull if none
{
  gfxFloat radii[4];
  gfxFloat *radiiPtr = nsnull;

  PRUint32 borderColorStyleCount = 0;
  BorderColorStyle borderColorStyleTopLeft[3], borderColorStyleBottomRight[3];
  BorderColorStyle *borderColorStyle = nsnull;
  PRUint32 compositeColorCount = 0;

  if (borderRadii) {
    // make a copy, because we munge this during this function
    for (int i = 0; i < 4; i++)
      radii[i] = borderRadii[i];

    radiiPtr = &radii[0];
  }

  // if we're not doing compositeColors, we can calculate the borderColorStyle based
  // on the specified style.  The borderColorStyle array goes from the outer to the inner
  // style.

  if (!compositeColors) {
    // if the border width is 1, we need to change the borderRenderStyle a bit to make sure
    // that we get the right colors -- e.g. 'ridge' with a 1px border needs to look like
    // solid, not like 'outset'.
    if (CheckFourFloatsEqual(borderWidths, 1.0)) {
      if (borderRenderStyle == NS_STYLE_BORDER_STYLE_RIDGE ||
          borderRenderStyle == NS_STYLE_BORDER_STYLE_GROOVE ||
          borderRenderStyle == NS_STYLE_BORDER_STYLE_DOUBLE)
        borderRenderStyle = NS_STYLE_BORDER_STYLE_SOLID;
    }

    switch (borderRenderStyle) {
      case NS_STYLE_BORDER_STYLE_SOLID:
      case NS_STYLE_BORDER_STYLE_DASHED:
      case NS_STYLE_BORDER_STYLE_DOTTED:
        borderColorStyleTopLeft[0] = BorderColorStyleSolid;

        borderColorStyleBottomRight[0] = BorderColorStyleSolid;

        borderColorStyleCount = 1;
        break;

      case NS_STYLE_BORDER_STYLE_GROOVE:
        borderColorStyleTopLeft[0] = BorderColorStyleDark;
        borderColorStyleTopLeft[1] = BorderColorStyleLight;

        borderColorStyleBottomRight[0] = BorderColorStyleLight;
        borderColorStyleBottomRight[1] = BorderColorStyleDark;

        borderColorStyleCount = 2;
        break;

      case NS_STYLE_BORDER_STYLE_RIDGE:
        borderColorStyleTopLeft[0] = BorderColorStyleLight;
        borderColorStyleTopLeft[1] = BorderColorStyleDark;

        borderColorStyleBottomRight[0] = BorderColorStyleDark;
        borderColorStyleBottomRight[1] = BorderColorStyleLight;

        borderColorStyleCount = 2;
        break;

      case NS_STYLE_BORDER_STYLE_DOUBLE:
        borderColorStyleTopLeft[0] = BorderColorStyleSolid;
        borderColorStyleTopLeft[1] = BorderColorStyleNone;
        borderColorStyleTopLeft[2] = BorderColorStyleSolid;

        borderColorStyleBottomRight[0] = BorderColorStyleSolid;
        borderColorStyleBottomRight[1] = BorderColorStyleNone;
        borderColorStyleBottomRight[2] = BorderColorStyleSolid;

        borderColorStyleCount = 3;
        break;

      case NS_STYLE_BORDER_STYLE_INSET:
        borderColorStyleTopLeft[0] = BorderColorStyleDark;
        borderColorStyleBottomRight[0] = BorderColorStyleLight;

        borderColorStyleCount = 1;
        break;

      case NS_STYLE_BORDER_STYLE_OUTSET:
        borderColorStyleTopLeft[0] = BorderColorStyleLight;
        borderColorStyleBottomRight[0] = BorderColorStyleDark;

        borderColorStyleCount = 1;
        break;

      default:
        NS_NOTREACHED("Unhandled border style!!");
        break;
    }

    // The caller should never give us anything with a mix
    // of TL/BR if the border style would require a
    // TL/BR split.
    if (sides & (SIDE_BIT_BOTTOM | SIDE_BIT_RIGHT))
      borderColorStyle = borderColorStyleBottomRight;
    else
      borderColorStyle = borderColorStyleTopLeft;
  } else {
    // composite colors; we need to calculate borderColorStyle differently --
    // all borders are rendered as "solid", but we might need an arbitrary number
    // of them.
    PRUint32 maxBorderWidth = 0;
    for (int i = 0; i < 4; i++)
      maxBorderWidth = PR_MAX(maxBorderWidth, PRUint32(borderWidths[i]));
    
    borderColorStyle = new BorderColorStyle[maxBorderWidth];
    borderColorStyleCount = maxBorderWidth;

    const nsBorderColors *tmp = compositeColors;
    do {
      compositeColorCount++;
      tmp = tmp->mNext;
    } while (tmp);

    for (unsigned int i = 0; i < borderColorStyleCount; i++) {
      borderColorStyle[i] = BorderColorStyleSolid;
    }
  }

  SF("borderWidths: %f %f %f %f ", borderWidths[0], borderWidths[1], borderWidths[2], borderWidths[3]), SN(), SF(" borderColorStyleCount: %d\n", borderColorStyleCount);
  if (radiiPtr) {
    SF(" radii: %f %f %f %f\n", radiiPtr[0], radiiPtr[1], radiiPtr[2], radiiPtr[3]);
  }

  // -moz-border-colors is a hack; if we have it for a border, then
  // it's always drawn solid, and each color is given 1px.  The last
  // color is used for the remainder of the border's size.
  //
  // Otherwise, we distribute the border across the available space.

  if (compositeColorCount == 0) {
    if (borderColorStyleCount == 1) {
      gfxRGBA color;
      ComputeColorForLine(0,
                          borderColorStyle, borderColorStyleCount,
                          nsnull, 0,
                          borderRenderColor, bgColor, color);


      SF("borderColorStyle: %d color: %f %f %f %f\n", borderColorStyle[0], color.r, color.g, color.b, color.a);

      FillFastBorderPath(ctx, oRect, iRect, radiiPtr, borderWidths, sides, color);
    } else if (borderColorStyleCount == 2) {
      // with 2 color styles, any extra pixel goes to the outside

      gfxFloat outerBorderWidths[4], innerBorderWidths[4];
      for (int i = 0; i < 4; i++) {
        outerBorderWidths[i] = PRInt32(borderWidths[i]) / 2 + PRInt32(borderWidths[i]) % 2;
        innerBorderWidths[i] = PRInt32(borderWidths[i]) / 2;
      }

      gfxRGBA color;
      gfxRect soRect, siRect;

      // draw outer rect
      if (borderColorStyle[1] != BorderColorStyleNone) {
        ComputeColorForLine(0,
                            borderColorStyle, borderColorStyleCount,
                            nsnull, 0,
                            borderRenderColor, bgColor, color);

        soRect = oRect;
        siRect = iRect;

        siRect.Outset(innerBorderWidths);

        FillFastBorderPath(ctx, soRect, siRect, radiiPtr, outerBorderWidths, sides, color);
      }

      if (radiiPtr)
        CalculateInnerRadii(radiiPtr, outerBorderWidths, radiiPtr);

      // draw inner rect
      if (borderColorStyle[0] != BorderColorStyleNone) {
        ComputeColorForLine(1,
                            borderColorStyle, borderColorStyleCount,
                            nsnull, 0,
                            borderRenderColor, bgColor, color);

        soRect = oRect;
        siRect = iRect;

        soRect.Inset(outerBorderWidths);

        FillFastBorderPath(ctx, soRect, siRect, radiiPtr, innerBorderWidths, sides, color);
      }

    } else if (borderColorStyleCount == 3) {
      // with 3 color styles, any extra pixel (or lack of extra pixel)
      // goes to the middle

      gfxFloat outerBorderWidths[4], middleBorderWidths[4], innerBorderWidths[4];

      for (int i = 0; i < 4; i++) {
        if (borderWidths[i] == 1.0) {
          outerBorderWidths[i] = 1.0;
          middleBorderWidths[i] = innerBorderWidths[i] = 0.0;
        } else {
          PRInt32 rest = PRInt32(borderWidths[i]) % 3;
          outerBorderWidths[i] = innerBorderWidths[i] = middleBorderWidths[i] = (PRInt32(borderWidths[i]) - rest) / 3;

          if (rest == 1) {
            middleBorderWidths[i] += 1.0;
          } else if (rest == 2) {
            outerBorderWidths[i] += 1.0;
            innerBorderWidths[i] += 1.0;
          }
        }
      }

      gfxRGBA color;
      gfxRect soRect, siRect;

      // draw outer rect
      if (borderColorStyle[2] != BorderColorStyleNone) {
        ComputeColorForLine(0,
                            borderColorStyle, borderColorStyleCount,
                            nsnull, 0,
                            borderRenderColor, bgColor, color);

        soRect = oRect;
        siRect = iRect;

        siRect.Outset(innerBorderWidths);
        siRect.Outset(middleBorderWidths);

        FillFastBorderPath(ctx, soRect, siRect, radiiPtr, outerBorderWidths, sides, color);
      }

      if (radiiPtr)
        CalculateInnerRadii(radiiPtr, outerBorderWidths, radiiPtr);

      // draw middle rect
      if (borderColorStyle[1] != BorderColorStyleNone) {
        ComputeColorForLine(1,
                            borderColorStyle, borderColorStyleCount,
                            nsnull, 0,
                            borderRenderColor, bgColor, color);

        soRect = oRect;
        siRect = iRect;

        soRect.Inset(outerBorderWidths);
        siRect.Outset(innerBorderWidths);

        FillFastBorderPath(ctx, soRect, siRect, radiiPtr, middleBorderWidths, sides, color);
      }

      if (radiiPtr)
        CalculateInnerRadii(radiiPtr, middleBorderWidths, radiiPtr);

      // draw inner rect
      if (borderColorStyle[0] != BorderColorStyleNone) {
        ComputeColorForLine(2,
                            borderColorStyle, borderColorStyleCount,
                            nsnull, 0,
                            borderRenderColor, bgColor, color);

        soRect = oRect;
        siRect = iRect;

        soRect.Inset(outerBorderWidths);
        soRect.Inset(middleBorderWidths);

        FillFastBorderPath(ctx, soRect, siRect, radiiPtr, innerBorderWidths, sides, color);
      }
    } else {
      // The only way to get to here is by having a
      // borderColorStyleCount < 1 or > 3; this should never happen,
      // since -moz-border-colors doesn't get handled here.
      NS_ERROR("Non-border-colors case with borderColorStyleCount < 1 or > 3; what happened?");
    }
  } else {
    // the generic composite colors path; each border is 1px in size
    gfxRect soRect = oRect;
    gfxRect siRect;
    gfxFloat maxBorderWidth = 0;
    for (int i = 0; i < 4; i++)
      maxBorderWidth = PR_MAX(maxBorderWidth, borderWidths[i]);

    // distribute the border sizes evenly as we draw lines; we end up
    // drawing borders that are potentially less than 1px in width
    // if some of the sides are bigger than the others, but we have
    // consistent colors all the way around.
    gfxFloat fakeBorderSizes[4];
    for (int i = 0; i < 4; i++)
      fakeBorderSizes[i] = borderWidths[i] / maxBorderWidth;

    for (PRUint32 i = 0; i < PRUint32(maxBorderWidth); i++) {
      gfxRGBA lineColor;
      siRect = soRect;
      siRect.Inset(fakeBorderSizes);

      ComputeColorForLine(i,
                          borderColorStyle, borderColorStyleCount,
                          compositeColors, compositeColorCount,
                          borderRenderColor, bgColor, lineColor);

      FillFastBorderPath(ctx, soRect, siRect, radiiPtr, fakeBorderSizes, sides, lineColor);

      soRect.Inset(fakeBorderSizes);

      if (radiiPtr)
        CalculateInnerRadii(radiiPtr, fakeBorderSizes, radiiPtr);
    }
  }

  if (compositeColors) {
    delete [] borderColorStyle;
  }

  ctx->SetFillRule(gfxContext::FILL_RULE_WINDING);

#if 0
  ctx->SetOperator(gfxContext::OPERATOR_OVER);
  // debug; draw a line on the outside and inside edge
  // of the border.
  ctx->SetLineWidth(1.0);
  ctx->SetDash(nsnull, 0, 0.0);
  ctx->SetColor(gfxRGBA(1.0, 0.0, 0.0, 1.0));
  ctx->NewPath();
  ctx->Rectangle(oRect);
  ctx->Stroke();
  ctx->NewPath();
  ctx->Rectangle(iRect);
  ctx->Stroke();
#endif
}

/*
 * Compute the float-pixel radii that should be used for drawing
 * this border/outline, given the various input bits.
 *
 * If a side is skipped via skipSides, its corners are forced to 0,
 * otherwise the resulting radius is the smaller of the specified
 * radius and half of each adjacent side's length.
 */
static void
ComputePixelRadii(const nscoord *aTwipsRadii,
                  const nsRect& outerRect,
                  const nsMargin& borderMargin,
                  PRIntn skipSides,
                  nscoord twipsPerPixel,
                  gfxFloat *oBorderRadii)
{
  nscoord twipsRadii[4] = { aTwipsRadii[0], aTwipsRadii[1], aTwipsRadii[2], aTwipsRadii[3] };
  nsMargin border(borderMargin);

  if (skipSides & SIDE_BIT_TOP) {
    border.top = 0;
    twipsRadii[C_TL] = 0;
    twipsRadii[C_TR] = 0;
  }

  if (skipSides & SIDE_BIT_RIGHT) {
    border.right = 0;
    twipsRadii[C_TR] = 0;
    twipsRadii[C_BR] = 0;
  }

  if (skipSides & SIDE_BIT_BOTTOM) {
    border.bottom = 0;
    twipsRadii[C_BR] = 0;
    twipsRadii[C_BL] = 0;
  }

  if (skipSides & SIDE_BIT_LEFT) {
    border.left = 0;
    twipsRadii[C_BL] = 0;
    twipsRadii[C_TL] = 0;
  }

  nsRect innerRect(outerRect);
  innerRect.Deflate(border);

  // make sure the corner radii don't get too big
  nsMargin maxRadiusSize(innerRect.width/2 + border.left,
                         innerRect.height/2 + border.top,
                         innerRect.width/2 + border.right,
                         innerRect.height/2 + border.bottom);

  oBorderRadii[C_TL] = gfxFloat(PR_MIN(twipsRadii[C_TL], PR_MIN(maxRadiusSize.top, maxRadiusSize.left))) / twipsPerPixel;
  oBorderRadii[C_TR] = gfxFloat(PR_MIN(twipsRadii[C_TR], PR_MIN(maxRadiusSize.top, maxRadiusSize.right))) / twipsPerPixel;
  oBorderRadii[C_BL] = gfxFloat(PR_MIN(twipsRadii[C_BL], PR_MIN(maxRadiusSize.bottom, maxRadiusSize.left))) / twipsPerPixel;
  oBorderRadii[C_BR] = gfxFloat(PR_MIN(twipsRadii[C_BR], PR_MIN(maxRadiusSize.bottom, maxRadiusSize.right))) / twipsPerPixel;
}

static void
DrawDashedSide(gfxContext *ctx,
               PRUint8 side,
               const gfxRect& iRect,
               const gfxRect& oRect,
               PRUint8 style,
               gfxFloat borderWidth,
               nscolor borderColor,
               gfxSize *cornerDimensions)
{
  gfxFloat dashWidth;
  gfxFloat dash[2];

  if (borderWidth == 0.0)
    return;

  if (style == NS_STYLE_BORDER_STYLE_DASHED) {
    dashWidth = gfxFloat(borderWidth * DOT_LENGTH * DASH_LENGTH);

    dash[0] = dashWidth;
    dash[1] = dashWidth;

    ctx->SetLineCap(gfxContext::LINE_CAP_BUTT);
  } else if (style == NS_STYLE_BORDER_STYLE_DOTTED) {
    dashWidth = gfxFloat(borderWidth * DOT_LENGTH);

    if (borderWidth > 2.0) {
      dash[0] = 0.0;
      dash[1] = dashWidth * 2.0;

      ctx->SetLineCap(gfxContext::LINE_CAP_ROUND);
    } else {
      dash[0] = dashWidth;
      dash[1] = dashWidth;
    }
  } else {
    SF("DrawDashedSide: style: %d!!\n", style);
    NS_ERROR("DrawDashedSide called with style other than DASHED or DOTTED; someone's not playing nice");
    return;
  }

  SF("dash: %f %f\n", dash[0], dash[1]);

  ctx->SetDash(dash, 2, 0.0);

  // Get the line drawn
  gfxPoint start, end;
  gfxFloat length;
  if (side == NS_SIDE_TOP) {
    start = gfxPoint(oRect.pos.x + cornerDimensions[C_TL].width,
                     (oRect.pos.y + iRect.pos.y) / 2.0);
    end = gfxPoint(oRect.pos.x + oRect.size.width - cornerDimensions[C_TR].width,
                   (oRect.pos.y + iRect.pos.y) / 2.0);
    length = end.x - start.x;
  } else if (side == NS_SIDE_RIGHT) {
    start = gfxPoint(oRect.pos.x + oRect.size.width - borderWidth / 2.0,
                     oRect.pos.y + cornerDimensions[C_TR].height);
    end = gfxPoint(oRect.pos.x + oRect.size.width - borderWidth / 2.0,
                   oRect.pos.y + oRect.size.height - cornerDimensions[C_BR].height);
    length = end.y - start.y;
  } else if (side == NS_SIDE_BOTTOM) {
    start = gfxPoint(oRect.pos.x + oRect.size.width - cornerDimensions[C_BR].width,
                     oRect.pos.y + oRect.size.height - borderWidth / 2.0);
    end = gfxPoint(oRect.pos.x + cornerDimensions[C_BL].width,
                   oRect.pos.y + oRect.size.height - borderWidth / 2.0);
    length = start.x - end.x;
  } else if (side == NS_SIDE_LEFT) {
    start = gfxPoint(oRect.pos.x + borderWidth / 2.0,
                     oRect.pos.y + oRect.size.height - cornerDimensions[C_BL].height);
    end = gfxPoint(oRect.pos.x + borderWidth / 2.0,
                   oRect.pos.y + cornerDimensions[C_TR].height);
    length = start.y - end.y;
  }

  ctx->NewPath();
  ctx->MoveTo(start);
  ctx->LineTo(end);
  ctx->SetLineWidth(borderWidth);
  ctx->SetColor(gfxRGBA(borderColor));
  //ctx->SetColor(gfxRGBA(1.0, 0.0, 0.0, 1.0));
  ctx->Stroke();
}

static void
DrawBorders(gfxContext *ctx,
            gfxRect& oRect,
            gfxRect& iRect,
            PRUint8 *borderStyles,
            gfxFloat *borderWidths,
            gfxFloat *borderRadii,
            nscolor *borderColors,
            nsBorderColors **compositeColors,
            PRIntn skipSides,
            nscolor backgroundColor,
            nscoord twipsPerPixel,
            nsRect *aGap = nsnull)
{
  // Examine the border style to figure out if we can draw it in one
  // go or not.
  PRUint8 numRenderPasses = NumBorderPasses (borderStyles, borderColors, compositeColors);
  if (numRenderPasses == 0) {
    // all the colors are transparent; nothing to do.
    return;
  }

  // round oRect and iRect; they're already an integer
  // number of pixels apart and should stay that way after
  // rounding.
  oRect.Round();
  iRect.Round();

  S(" oRect: "), S(oRect), SN();
  S(" iRect: "), S(iRect), SN();
  SF(" borderColors: 0x%08x 0x%08x 0x%08x 0x%08x\n", borderColors[0], borderColors[1], borderColors[2], borderColors[3]);

  // if conditioning the outside rect failed, then bail -- the outside
  // rect is supposed to enclose the entire border
  oRect.Condition();
  if (oRect.IsEmpty())
    return;

  iRect.Condition();

  // do we have any sides that are dotted/dashed?
  PRIntn dashedSides = 0;
  for (int i = 0; i < 4; i++) {
    PRUint8 style = borderStyles[i];
    if (style == NS_STYLE_BORDER_STYLE_DASHED ||
        style == NS_STYLE_BORDER_STYLE_DOTTED)
    {
      dashedSides |= (1 << i);
    }

    // just bail out entirely if RULES_MARKER
    // got through (see bug 379419).
    if (style & NS_STYLE_BORDER_STYLE_RULES_MARKER)
      return;
  }

  SF(" dashedSides: 0x%02x\n", dashedSides);

  // Clamp the CTM to be pixel-aligned; we do this only
  // for translation-only matrices now, but we could do it
  // if the matrix has just a scale as well.  We should not
  // do it if there's a rotation.
  gfxMatrix mat = ctx->CurrentMatrix();
  if (!mat.HasNonTranslation()) {
    mat.x0 = floor(mat.x0 + 0.5);
    mat.y0 = floor(mat.y0 + 0.5);
    ctx->SetMatrix(mat);
  }

  // if we're going to do separate sides, we need to do it as
  // a temporary surface group
  PRBool canAvoidGroup = PR_TRUE;
  if (numRenderPasses > 1) {
    // clip to oRect to define the size of the temporary surface
    ctx->NewPath();
    ctx->Rectangle(oRect);

    if (aGap) {
      gfxRect gapRect(RectToGfxRect(*aGap, twipsPerPixel));

      // draw the rectangle backwards, so that we get it
      // clipped out via the winding rule
      ctx->MoveTo(gapRect.pos);
      ctx->LineTo(gapRect.pos + gfxSize(0.0, gapRect.size.height));
      ctx->LineTo(gapRect.pos + gapRect.size);
      ctx->LineTo(gapRect.pos + gfxSize(gapRect.size.width, 0.0));
      ctx->ClosePath();
    }

    ctx->Clip();

    // OPTIMIZATION
    // Starting a compositing group is more work than necessary;
    // can avoid doing it if:
    // a) all the colors involved have to be solid (NS_GET_A(c) == 0xff)
    // b) no border radius is involved (the curves have antialiasing)
    // c) no dashed/dotted borders are involved
    // d) no DOUBLE style is involved (the middle part of DOUBLE needs
    //    to have the the background color show through; [we could
    //    handle this by just clearing out the parts that will be drawn])

    if (dashedSides != 0) {
      canAvoidGroup = PR_FALSE;
    } else {
      for (int i = 0; i < 4; i++) {
        if (borderRadii[i] != 0.0) {
          canAvoidGroup = PR_FALSE;
          break;
        }

        PRUint8 style = borderStyles[i];
        if (style == NS_STYLE_BORDER_STYLE_DASHED ||
            style == NS_STYLE_BORDER_STYLE_DOTTED ||
            style == NS_STYLE_BORDER_STYLE_DOUBLE)
        {
          canAvoidGroup = PR_FALSE;
          break;
        }

        if (compositeColors[i]) {
          nsBorderColors* colors = compositeColors[i];
          do {
            if (NS_GET_A(colors->mColor) != 0xff) {
              canAvoidGroup = PR_FALSE;
              break;
            }

            colors = colors->mNext;
          } while (colors);
        } else {
          if (NS_GET_A(borderColors[i]) != 0xff) {
            canAvoidGroup = PR_FALSE;
            break;
          }
        }
      }
    }

    if (canAvoidGroup) {
      // clear the area underneath where the border's to be
      // rendered, so we can avoid using a compositing group
      // but still have the ADD operator work correctly

      // OPTIMIZATION
      // avoid doing a group or using OPERATOR_ADD for the common
      // case of a TL/BR border style in 1px size.
      if (numRenderPasses == 2 &&
          CheckFourFloatsEqual(borderWidths, 1.0) &&
          NS_GET_A(borderColors[0]) == 0xff)
      {
        // OVER is faster than SOURCE in a lot of cases, and they'll
        // behave the same since the color has no transparency.
        // We don't need to clear anything out in this case, either.
        ctx->SetOperator(gfxContext::OPERATOR_OVER);
      } else {
        // clear out the area so that we can use ADD without drawing
        ctx->SetOperator(gfxContext::OPERATOR_CLEAR);
        FillFastBorderPath(ctx, oRect, iRect, borderRadii, borderWidths, SIDE_BITS_ALL, gfxRGBA(0.0,0.0,0.0,0.0));
        ctx->SetOperator(gfxContext::OPERATOR_ADD);
      }
    } else {
      // start a compositing group
      ctx->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA);
      ctx->SetOperator(gfxContext::OPERATOR_ADD);
    }

    SF("canAvoidGroup: %d\n", canAvoidGroup);
  } else if (aGap) {
    gfxRect gapRect(RectToGfxRect(*aGap, twipsPerPixel));

    // draw the rectangle backwards, so that we get it
    // clipped out via the winding rule
    ctx->MoveTo(gapRect.pos);
    ctx->LineTo(gapRect.pos + gfxSize(0.0, gapRect.size.height));
    ctx->LineTo(gapRect.pos + gapRect.size);
    ctx->LineTo(gapRect.pos + gfxSize(gapRect.size.width, 0.0));
    ctx->ClosePath();
    ctx->Clip();
  }

  // if we have dashed sides, clip to the corners so that we can draw the
  // dashed bits later.
  if (dashedSides) {
    ctx->Save();

    ctx->NewPath();
    DoCornerClipSubPath(ctx, oRect, iRect, borderRadii, dashedSides);

#if 0
    ctx->SetColor(gfxRGBA(1.0, 0.0, 1.0, 1.0));
    ctx->SetLineWidth(2.);
    ctx->Stroke();
#endif

    ctx->Clip();
  }

  // Render with either 1, 2, or 4 passes, depending on how
  // many are needed to get the job done.
  for (int i = 0; i < numRenderPasses; i++) {
    PRIntn sideBits;
    PRUint8 side;

    if (numRenderPasses == 4) {
      side = gBorderSideOrder[i];
      sideBits = 1 << side;

      // skip this side if it's, well, skipped
      if (skipSides & (1 << side))
        continue;

      ctx->Save();
      ctx->NewPath();

      DoSideClipSubPath(ctx, iRect, oRect, side, borderStyles, borderRadii);

      ctx->Clip();
    } else if (numRenderPasses == 2) {
      if (i == 0) {
        side = NS_SIDE_TOP;
        sideBits = SIDE_BIT_TOP | SIDE_BIT_LEFT;
      } else {
        side = NS_SIDE_BOTTOM;
        sideBits = SIDE_BIT_BOTTOM | SIDE_BIT_RIGHT;
      }
    } else {
        side = NS_SIDE_TOP;
        sideBits = SIDE_BITS_ALL;
    }

    const PRUint8 style = borderStyles[side];
    if (style != NS_STYLE_BORDER_STYLE_NONE &&
        style != NS_STYLE_BORDER_STYLE_HIDDEN) {
      // Draw the whole border.  If we're not drawing multiple passes,
      // then sides are identical and no clip was set -- this will draw
      // the entire border.  Otherwise, this will still draw the entire
      // border in the style of this side, but it will be clipped by the
      // above code.  We do this to get the joins looking correct.

      DrawBorderSides(ctx,
                      borderWidths,
                      sideBits,
                      style,
                      oRect, iRect,
                      borderColors[side],
                      compositeColors[side],
                      backgroundColor,
                      twipsPerPixel,
                      borderRadii);
      SN("----------------");
    }

    if (numRenderPasses > 2)
      ctx->Restore();
  }

  // now fill in any dotted/dashed borders
  if (dashedSides != 0) {
    // get rid of the corner clip we set earlier
    ctx->Restore();

    gfxSize dims[4];
    GetBorderCornerDimensions(oRect, iRect, borderRadii, dims);

    for (int i = 0; i < 4; i++) {
      PRUint8 side = gBorderSideOrder[i];
      if (NS_GET_A(borderColors[side]) != 0x00 && dashedSides & (1 << side)) {
        // side is dotted/dashed.
        DrawDashedSide (ctx, side,
                        iRect, oRect,
                        borderStyles[side],
                        borderWidths[side],
                        borderColors[side],
                        dims);
      }
    }
  }

  if (!canAvoidGroup) {
    ctx->PopGroupToSource();
    ctx->Paint();
  }
}

void
nsCSSRendering::PaintBorder(nsPresContext* aPresContext,
                            nsIRenderingContext& aRenderingContext,
                            nsIFrame* aForFrame,
                            const nsRect& aDirtyRect,
                            const nsRect& aBorderArea,
                            const nsStyleBorder& aBorderStyle,
                            nsStyleContext* aStyleContext,
                            PRIntn aSkipSides,
                            nsRect* aGap,
                            nscoord aHardBorderSize,
                            PRBool  aShouldIgnoreRounded)
{
  nsMargin            border;
  nsStyleCoord        bordStyleRadius[4];
  PRInt32             twipsRadii[4];
  float               percent;
  nsCompatibility     compatMode = aPresContext->CompatibilityMode();

  SN("++ PaintBorder");

  // Check to see if we have an appearance defined.  If so, we let the theme
  // renderer draw the border.  DO not get the data from aForFrame, since the passed in style context
  // may be different!  Always use |aStyleContext|!
  const nsStyleDisplay* displayData = aStyleContext->GetStyleDisplay();
  if (displayData->mAppearance) {
    nsITheme *theme = aPresContext->GetTheme();
    if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame, displayData->mAppearance))
      return; // Let the theme handle it.
  }

  // Get our style context's color struct.
  const nsStyleColor* ourColor = aStyleContext->GetStyleColor();

  // in NavQuirks mode we want to use the parent's context as a starting point
  // for determining the background color
  const nsStyleBackground* bgColor = nsCSSRendering::FindNonTransparentBackground
    (aStyleContext, compatMode == eCompatibility_NavQuirks ? PR_TRUE : PR_FALSE);

  if (aHardBorderSize > 0) {
    border.SizeTo(aHardBorderSize, aHardBorderSize, aHardBorderSize, aHardBorderSize);
  } else {
    border = aBorderStyle.GetBorder();
  }

  if ((0 == border.left) && (0 == border.right) &&
      (0 == border.top) && (0 == border.bottom)) {
    // Empty border area
    return;
  }

  // get the radius for our border
  bordStyleRadius[0] = aBorderStyle.mBorderRadius.GetTop();    //topleft
  bordStyleRadius[1] = aBorderStyle.mBorderRadius.GetRight();  //topright
  bordStyleRadius[2] = aBorderStyle.mBorderRadius.GetBottom(); //bottomright
  bordStyleRadius[3] = aBorderStyle.mBorderRadius.GetLeft();   //bottomleft

  // convert percentage values
  for(int i = 0; i < 4; i++) {
    twipsRadii[i] = 0;

    switch (bordStyleRadius[i].GetUnit()) {
      case eStyleUnit_Percent:
        percent = bordStyleRadius[i].GetPercentValue();
        twipsRadii[i] = (nscoord)(percent * aForFrame->GetSize().width);
        break;

      case eStyleUnit_Coord:
        twipsRadii[i] = bordStyleRadius[i].GetCoordValue();
        break;

      default:
        break;
    }
  }

  // Turn off rendering for all of the zero sized sides
  if (aSkipSides & SIDE_BIT_TOP) border.top = 0;
  if (aSkipSides & SIDE_BIT_RIGHT) border.right = 0;
  if (aSkipSides & SIDE_BIT_BOTTOM) border.bottom = 0;
  if (aSkipSides & SIDE_BIT_LEFT) border.left = 0;

  // get the inside and outside parts of the border
  nsRect outerRect(aBorderArea), innerRect(aBorderArea);
  innerRect.Deflate(border);

  SF(" innerRect: %d %d %d %d\n", innerRect.x, innerRect.y, innerRect.width, innerRect.height);
  SF(" outerRect: %d %d %d %d\n", outerRect.x, outerRect.y, outerRect.width, outerRect.height);

  // if the border size is more than the appropriate dimension of the area,
  // then... do what?
  // XXX what is this?  according to bug 62245, this check might not be
  // needed any more
  if (border.left + border.right > aBorderArea.width) {
    innerRect.x = outerRect.x;
    innerRect.width = outerRect.width;
  }
  if (border.top + border.bottom > aBorderArea.height) {
    innerRect.y = outerRect.y;
    innerRect.height = outerRect.height;
  }

  // we can assume that we're already clipped to aDirtyRect -- I think? (!?)

  // Get our conversion values
  nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);

  // convert outer and inner rects
  gfxRect oRect(RectToGfxRect(outerRect, twipsPerPixel));
  gfxRect iRect(RectToGfxRect(innerRect, twipsPerPixel));

  // convert the border widths
  gfxFloat borderWidths[4] = { border.top / twipsPerPixel,
                               border.right / twipsPerPixel,
                               border.bottom / twipsPerPixel,
                               border.left / twipsPerPixel };

  // convert the radii
  gfxFloat borderRadii[4];
  ComputePixelRadii(twipsRadii, outerRect, border, aSkipSides, twipsPerPixel, borderRadii);

  PRUint8 borderStyles[4];
  nscolor borderColors[4];
  nsBorderColors *compositeColors[4];

  // pull out styles, colors, composite colors
  for (int i = 0; i < 4; i++) {
    PRBool transparent, foreground;
    borderStyles[i] = aBorderStyle.GetBorderStyle(i);
    aBorderStyle.GetBorderColor(i, borderColors[i], transparent, foreground);
    aBorderStyle.GetCompositeColors(i, &compositeColors[i]);

    if (transparent)
      borderColors[i] = 0x0;
    else if (foreground)
      borderColors[i] = ourColor->mColor;
  }

  SF(" borderStyles: %d %d %d %d\n", borderStyles[0], borderStyles[1], borderStyles[2], borderStyles[3]);

  // start drawing
  nsRefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();

  ctx->Save();

#if 0
  // this will draw a transparent red backround underneath the area between iRect and oRect
  ctx->Save();
  ctx->Rectangle(iRect);
  ctx->Clip();
  ctx->NewPath();

  ctx->Rectangle(oRect);
  ctx->SetColor(gfxRGBA(1.0, 0.0, 0.0, 0.5));
  ctx->Fill();
  ctx->Restore();
#endif

  SF ("borderRadii: %f %f %f %f\n", borderRadii[0], borderRadii[1], borderRadii[2], borderRadii[3]);

  DrawBorders(ctx,
              oRect,
              iRect,
              borderStyles,
              borderWidths,
              borderRadii,
              borderColors,
              compositeColors,
              aSkipSides,
              bgColor->mBackgroundColor,
              twipsPerPixel,
              aGap);

  ctx->Restore();

  SN();
}

void
nsCSSRendering::PaintOutline(nsPresContext* aPresContext,
                             nsIRenderingContext& aRenderingContext,
                             nsIFrame* aForFrame,
                             const nsRect& aDirtyRect,
                             const nsRect& aBorderArea,
                             const nsStyleBorder& aBorderStyle,
                             const nsStyleOutline& aOutlineStyle,
                             nsStyleContext* aStyleContext,
                             nsRect* aGap)
{
  nsStyleCoord        bordStyleRadius[4];
  PRInt32             twipsRadii[4];

  // Get our style context's color struct.
  const nsStyleColor* ourColor = aStyleContext->GetStyleColor();

  nscoord width, offset;
  float percent;

  aOutlineStyle.GetOutlineWidth(width);

  if (width == 0) {
    // Empty outline
    return;
  }

  const nsStyleBackground* bgColor = nsCSSRendering::FindNonTransparentBackground
    (aStyleContext, PR_FALSE);

  // get the radius for our outline
  bordStyleRadius[0] = aOutlineStyle.mOutlineRadius.GetTop();    //topleft
  bordStyleRadius[1] = aOutlineStyle.mOutlineRadius.GetRight();  //topright
  bordStyleRadius[2] = aOutlineStyle.mOutlineRadius.GetBottom(); //bottomright
  bordStyleRadius[3] = aOutlineStyle.mOutlineRadius.GetLeft();   //bottomleft

  // convert percentage values
  for (int i = 0; i < 4; i++) {
    twipsRadii[i] = 0;

    switch (bordStyleRadius[i].GetUnit()) {
      case eStyleUnit_Percent:
        percent = bordStyleRadius[i].GetPercentValue();
        twipsRadii[i] = (nscoord)(percent * aBorderArea.width);
        break;

      case eStyleUnit_Coord:
        twipsRadii[i] = bordStyleRadius[i].GetCoordValue();
        break;

      default:
        break;
    }
  }

  nsRect overflowArea = aForFrame->GetOverflowRect();

  // get the offset for our outline
  aOutlineStyle.GetOutlineOffset(offset);
  nsRect outerRect(overflowArea + aBorderArea.TopLeft());
  nsRect innerRect(outerRect);
  if (width + offset >= 0) {
    // the overflow area is exactly the outside edge of the outline
    innerRect.Deflate(width, width);
  } else {
    // the overflow area is exactly the rectangle containing the frame and its
    // children; we can compute the outline directly
    innerRect.Deflate(-offset, -offset);
    if (innerRect.width < 0 || innerRect.height < 0) {
      return; // Protect against negative outline sizes
    }
    outerRect = innerRect;
    outerRect.Inflate(width, width);
  }

  // If the dirty rect is completely inside the border area (e.g., only the
  // content is being painted), then we can skip out now
  // XXX this isn't exactly true for rounded borders, where the inside curves may
  // encroach into the content area.  A safer calculation would be to
  // shorten insideRect by the radius one each side before performing this test.
  if (innerRect.Contains(aDirtyRect)) {
    return;
  }

  // Get our conversion values
  nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);

  // get the inner and outer rectangles
  gfxRect oRect(RectToGfxRect(outerRect, twipsPerPixel));
  gfxRect iRect(RectToGfxRect(innerRect, twipsPerPixel));

  // convert the radii
  nsMargin outlineMargin(width, width, width, width);
  gfxFloat outlineRadii[4];
  ComputePixelRadii(twipsRadii, outerRect, outlineMargin, 0, twipsPerPixel, outlineRadii);

  PRUint8 outlineStyle = aOutlineStyle.GetOutlineStyle();
  PRUint8 outlineStyles[4] = { outlineStyle,
                               outlineStyle,
                               outlineStyle,
                               outlineStyle };

  nscolor outlineColor;
  // PR_FALSE means use the initial color; PR_TRUE means a color was
  // set.
  if (!aOutlineStyle.GetOutlineColor(outlineColor))
    outlineColor = ourColor->mColor;
  nscolor outlineColors[4] = { outlineColor,
                               outlineColor,
                               outlineColor,
                               outlineColor };

  nsBorderColors *outlineCompositeColors[4] = { nsnull };

  // convert the border widths
  gfxFloat outlineWidths[4] = { width / twipsPerPixel,
                                width / twipsPerPixel,
                                width / twipsPerPixel,
                                width / twipsPerPixel };

  // start drawing
  nsRefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();

  ctx->Save();

  DrawBorders(ctx,
              oRect,
              iRect,
              outlineStyles,
              outlineWidths,
              outlineRadii,
              outlineColors,
              outlineCompositeColors,
              0,
              bgColor->mBackgroundColor,
              twipsPerPixel,
              aGap);

  ctx->Restore();

  SN();
}

// Thebes Border Rendering Code End
//----------------------------------------------------------------------


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

// Returns the anchor point to use for the background image. The
// anchor point is the (x, y) location where the first tile should
// be placed
//
// For repeated tiling, the anchor values are normalized wrt to the upper-left
// edge of the bounds, and are always in the range:
// -(aTileWidth - 1) <= anchor.x <= 0
// -(aTileHeight - 1) <= anchor.y <= 0
//
// i.e., they are either 0 or a negative number whose absolute value is
// less than the tile size in that dimension
//
// aOriginBounds is the box to which the tiling position should be relative
// aClipBounds is the box in which the tiling will actually be done
// They should correspond to 'background-origin' and 'background-clip',
// except when painting on the canvas, in which case the origin bounds
// should be the bounds of the root element's frame and the clip bounds
// should be the bounds of the canvas frame.
static void
ComputeBackgroundAnchorPoint(const nsStyleBackground& aColor,
                             const nsRect& aOriginBounds,
                             const nsRect& aClipBounds,
                             nscoord aTileWidth, nscoord aTileHeight,
                             nsPoint& aResult)
{
  nscoord x;
  if (NS_STYLE_BG_X_POSITION_LENGTH & aColor.mBackgroundFlags) {
    x = aColor.mBackgroundXPosition.mCoord;
  }
  else if (NS_STYLE_BG_X_POSITION_PERCENT & aColor.mBackgroundFlags) {
    PRFloat64 percent = PRFloat64(aColor.mBackgroundXPosition.mFloat);
    nscoord tilePos = nscoord(percent * PRFloat64(aTileWidth));
    nscoord boxPos = nscoord(percent * PRFloat64(aOriginBounds.width));
    x = boxPos - tilePos;
  }
  else {
    x = 0;
  }
  x += aOriginBounds.x - aClipBounds.x;
  if (NS_STYLE_BG_REPEAT_X & aColor.mBackgroundRepeat) {
    // When we are tiling in the x direction the loop will run from
    // the left edge of the box to the right edge of the box. We need
    // to adjust the starting coordinate to lie within the band being
    // rendered.
    if (x < 0) {
      x = -x;
      if (x < 0) {
        // Some joker gave us max-negative-integer.
        x = 0;
      }
      x %= aTileWidth;
      x = -x;
    }
    else if (x != 0) {
      x %= aTileWidth;
      if (x > 0) {
        x = x - aTileWidth;
      }
    }

    NS_POSTCONDITION((x >= -(aTileWidth - 1)) && (x <= 0), "bad computed anchor value");
  }
  aResult.x = x;

  nscoord y;
  if (NS_STYLE_BG_Y_POSITION_LENGTH & aColor.mBackgroundFlags) {
    y = aColor.mBackgroundYPosition.mCoord;
  }
  else if (NS_STYLE_BG_Y_POSITION_PERCENT & aColor.mBackgroundFlags){
    PRFloat64 percent = PRFloat64(aColor.mBackgroundYPosition.mFloat);
    nscoord tilePos = nscoord(percent * PRFloat64(aTileHeight));
    nscoord boxPos = nscoord(percent * PRFloat64(aOriginBounds.height));
    y = boxPos - tilePos;
  }
  else {
    y = 0;
  }
  y += aOriginBounds.y - aClipBounds.y;
  if (NS_STYLE_BG_REPEAT_Y & aColor.mBackgroundRepeat) {
    // When we are tiling in the y direction the loop will run from
    // the top edge of the box to the bottom edge of the box. We need
    // to adjust the starting coordinate to lie within the band being
    // rendered.
    if (y < 0) {
      y = -y;
      if (y < 0) {
        // Some joker gave us max-negative-integer.
        y = 0;
      }
      y %= aTileHeight;
      y = -y;
    }
    else if (y != 0) {
      y %= aTileHeight;
      if (y > 0) {
        y = y - aTileHeight;
      }
    }
    
    NS_POSTCONDITION((y >= -(aTileHeight - 1)) && (y <= 0), "bad computed anchor value");
  }
  aResult.y = y;
}

const nsStyleBackground*
nsCSSRendering::FindNonTransparentBackground(nsStyleContext* aContext,
                                             PRBool aStartAtParent /*= PR_FALSE*/)
{
  NS_ASSERTION(aContext, "Cannot find NonTransparentBackground in a null context" );
  
  const nsStyleBackground* result = nsnull;
  nsStyleContext* context = nsnull;
  if (aStartAtParent) {
    context = aContext->GetParent();
  }
  if (!context) {
    context = aContext;
  }
  
  while (context) {
    result = context->GetStyleBackground();
    if (0 == (result->mBackgroundFlags & NS_STYLE_BG_COLOR_TRANSPARENT))
      break;

    context = context->GetParent();
  }
  return result;
}


/**
 * |FindBackground| finds the correct style data to use to paint the
 * background.  It is responsible for handling the following two
 * statements in section 14.2 of CSS2:
 *
 *   The background of the box generated by the root element covers the
 *   entire canvas.
 *
 *   For HTML documents, however, we recommend that authors specify the
 *   background for the BODY element rather than the HTML element. User
 *   agents should observe the following precedence rules to fill in the
 *   background: if the value of the 'background' property for the HTML
 *   element is different from 'transparent' then use it, else use the
 *   value of the 'background' property for the BODY element. If the
 *   resulting value is 'transparent', the rendering is undefined.
 *
 * Thus, in our implementation, it is responsible for ensuring that:
 *  + we paint the correct background on the |nsCanvasFrame|,
 *    |nsRootBoxFrame|, or |nsPageFrame|,
 *  + we don't paint the background on the root element, and
 *  + we don't paint the background on the BODY element in *some* cases,
 *    and for SGML-based HTML documents only.
 *
 * |FindBackground| returns true if a background should be painted, and
 * the resulting style context to use for the background information
 * will be filled in to |aBackground|.  It fills in a boolean indicating
 * whether the frame is the canvas frame to allow PaintBackground to
 * ensure that it always paints something non-transparent for the
 * canvas.
 */

// Returns nsnull if aFrame is not a canvas frame.
// Otherwise, it returns the frame we should look for the background on.
// This is normally aFrame but if aFrame is the viewport, we need to
// look for the background starting at the scroll root (which shares
// style context with the document root) or the document root itself.
// We need to treat the viewport as canvas because, even though
// it does not actually paint a background, we need to get the right
// background style so we correctly detect transparent documents.
inline nsIFrame*
IsCanvasFrame(nsIFrame *aFrame)
{
  nsIAtom* frameType = aFrame->GetType();
  if (frameType == nsGkAtoms::canvasFrame ||
      frameType == nsGkAtoms::rootFrame ||
      frameType == nsGkAtoms::pageFrame ||
      frameType == nsGkAtoms::pageContentFrame) {
    return aFrame;
  } else if (frameType == nsGkAtoms::viewportFrame) {
    nsIFrame* firstChild = aFrame->GetFirstChild(nsnull);
    if (firstChild) {
      return firstChild;
    }
  }
  
  return nsnull;
}

inline PRBool
FindCanvasBackground(nsIFrame* aForFrame,
                     const nsStyleBackground** aBackground)
{
  // XXXldb What if the root element is positioned, etc.?  (We don't
  // allow that yet, do we?)
  nsIFrame *firstChild = aForFrame->GetFirstChild(nsnull);
  if (firstChild) {
    const nsStyleBackground* result = firstChild->GetStyleBackground();
    nsIFrame* topFrame = aForFrame;

    if (firstChild->GetType() == nsGkAtoms::pageContentFrame) {
      topFrame = firstChild->GetFirstChild(nsnull);
      NS_ASSERTION(topFrame,
                   "nsPageContentFrame is missing a normal flow child");
      if (!topFrame) {
        return PR_FALSE;
      }
      NS_ASSERTION(topFrame->GetContent(),
                   "nsPageContentFrame child without content");
      result = topFrame->GetStyleBackground();
    }

    // Check if we need to do propagation from BODY rather than HTML.
    if (result->IsTransparent()) {
      nsIContent* content = topFrame->GetContent();
      if (content) {
        // Use |GetOwnerDoc| so it works during destruction.
        nsIDocument* document = content->GetOwnerDoc();
        nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document);
        if (htmlDoc) {
          nsIContent* bodyContent = htmlDoc->GetBodyContentExternal();
          // We need to null check the body node (bug 118829) since
          // there are cases, thanks to the fix for bug 5569, where we
          // will reflow a document with no body.  In particular, if a
          // SCRIPT element in the head blocks the parser and then has a
          // SCRIPT that does "document.location.href = 'foo'", then
          // nsParser::Terminate will call |DidBuildModel| methods
          // through to the content sink, which will call |StartLayout|
          // and thus |InitialReflow| on the pres shell.  See bug 119351
          // for the ugly details.
          if (bodyContent) {
            nsIFrame *bodyFrame = aForFrame->PresContext()->GetPresShell()->
              GetPrimaryFrameFor(bodyContent);
            if (bodyFrame)
              result = bodyFrame->GetStyleBackground();
          }
        }
      }
    }

    *aBackground = result;
  } else {
    // This should always give transparent, so we'll fill it in with the
    // default color if needed.  This seems to happen a bit while a page is
    // being loaded.
    *aBackground = aForFrame->GetStyleBackground();
  }
  
  return PR_TRUE;
}

inline PRBool
FindElementBackground(nsIFrame* aForFrame,
                      const nsStyleBackground** aBackground)
{
  nsIFrame *parentFrame = aForFrame->GetParent();
  // XXXldb We shouldn't have to null-check |parentFrame| here.
  if (parentFrame && IsCanvasFrame(parentFrame) == parentFrame) {
    // Check that we're really the root (rather than in another child list).
    nsIFrame *childFrame = parentFrame->GetFirstChild(nsnull);
    if (childFrame == aForFrame)
      return PR_FALSE; // Background was already drawn for the canvas.
  }

  *aBackground = aForFrame->GetStyleBackground();

  // Return true unless the frame is for a BODY element whose background
  // was propagated to the viewport.

  if (aForFrame->GetStyleContext()->GetPseudoType())
    return PR_TRUE; // A pseudo-element frame.

  nsIContent* content = aForFrame->GetContent();
  if (!content || !content->IsNodeOfType(nsINode::eHTML))
    return PR_TRUE;  // not frame for an HTML element

  if (!parentFrame)
    return PR_TRUE; // no parent to look at

  if (content->Tag() != nsGkAtoms::body)
    return PR_TRUE; // not frame for <BODY> element

  // We should only look at the <html> background if we're in an HTML document
  nsIDocument* document = content->GetOwnerDoc();
  nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document);
  if (!htmlDoc)
    return PR_TRUE;

  nsIContent* bodyContent = htmlDoc->GetBodyContentExternal();
  if (bodyContent != content)
    return PR_TRUE; // this wasn't the background that was propagated

  const nsStyleBackground* htmlBG = parentFrame->GetStyleBackground();
  return !htmlBG->IsTransparent();
}

PRBool
nsCSSRendering::FindBackground(nsPresContext* aPresContext,
                               nsIFrame* aForFrame,
                               const nsStyleBackground** aBackground,
                               PRBool* aIsCanvas)
{
  nsIFrame* canvasFrame = IsCanvasFrame(aForFrame);
  *aIsCanvas = canvasFrame != nsnull;
  return canvasFrame
      ? FindCanvasBackground(canvasFrame, aBackground)
      : FindElementBackground(aForFrame, aBackground);
}

void
nsCSSRendering::DidPaint()
{
  gInlineBGData->Reset();
}

void
nsCSSRendering::PaintBackground(nsPresContext* aPresContext,
                                nsIRenderingContext& aRenderingContext,
                                nsIFrame* aForFrame,
                                const nsRect& aDirtyRect,
                                const nsRect& aBorderArea,
                                const nsStyleBorder& aBorder,
                                const nsStylePadding& aPadding,
                                PRBool aUsePrintSettings,
                                nsRect* aBGClipRect)
{
  NS_PRECONDITION(aForFrame,
                  "Frame is expected to be provided to PaintBackground");

  PRBool isCanvas;
  const nsStyleBackground *color;

  if (!FindBackground(aPresContext, aForFrame, &color, &isCanvas)) {
    // we don't want to bail out of moz-appearance is set on a root
    // node. If it has a parent content node, bail because it's not
    // a root, other wise keep going in order to let the theme stuff
    // draw the background. The canvas really should be drawing the
    // bg, but there's no way to hook that up via css.
    if (!aForFrame->GetStyleDisplay()->mAppearance) {
      return;
    }

    nsIContent* content = aForFrame->GetContent();
    if (!content || content->GetParent()) {
      return;
    }
        
    color = aForFrame->GetStyleBackground();
  }
  if (!isCanvas) {
    PaintBackgroundWithSC(aPresContext, aRenderingContext, aForFrame,
                          aDirtyRect, aBorderArea, *color, aBorder,
                          aPadding, aUsePrintSettings, aBGClipRect);
    return;
  }

  nsStyleBackground canvasColor(*color);

  nsIViewManager* vm = aPresContext->GetViewManager();

  if (canvasColor.mBackgroundFlags & NS_STYLE_BG_COLOR_TRANSPARENT) {
    nsIView* rootView;
    vm->GetRootView(rootView);
    if (!rootView->GetParent()) {
      PRBool widgetIsTransparent = PR_FALSE;

      if (rootView->HasWidget()) {
        rootView->GetWidget()->GetHasTransparentBackground(widgetIsTransparent);
      }
      
      if (!widgetIsTransparent) {
        // Ensure that we always paint a color for the root (in case there's
        // no background at all or a partly transparent image).
        canvasColor.mBackgroundFlags &= ~NS_STYLE_BG_COLOR_TRANSPARENT;
        canvasColor.mBackgroundColor = aPresContext->DefaultBackgroundColor();
      }
    }
  }

  vm->SetDefaultBackgroundColor(canvasColor.mBackgroundColor);

  PaintBackgroundWithSC(aPresContext, aRenderingContext, aForFrame,
                        aDirtyRect, aBorderArea, canvasColor,
                        aBorder, aPadding, aUsePrintSettings, aBGClipRect);
}

inline nscoord IntDivFloor(nscoord aDividend, nscoord aDivisor)
{
  NS_PRECONDITION(aDivisor > 0,
                  "this function only works for positive divisors");
  // ANSI C, ISO 9899:1999 section 6.5.5 defines integer division as
  // truncation of the result towards zero.  Earlier C standards, as
  // well as the C++ standards (1998 and 2003) do not, but we depend
  // on it elsewhere.
  return (aDividend < 0 ? (aDividend - aDivisor + 1) : aDividend) / aDivisor;
}

inline nscoord IntDivCeil(nscoord aDividend, nscoord aDivisor)
{
  NS_PRECONDITION(aDivisor > 0,
                  "this function only works for positive divisors");
  // ANSI C, ISO 9899:1999 section 6.5.5 defines integer division as
  // truncation of the result towards zero.  Earlier C standards, as
  // well as the C++ standards (1998 and 2003) do not, but we depend
  // on it elsewhere.
  return (aDividend > 0 ? (aDividend + aDivisor - 1) : aDividend) / aDivisor;
}

/**
 * Return the largest 'v' such that v = aTileOffset + N*aTileSize, for some
 * integer N, and v <= aDirtyStart.
 */
static nscoord
FindTileStart(nscoord aDirtyStart, nscoord aTileOffset, nscoord aTileSize)
{
  // Find largest integer N such that aTileOffset + N*aTileSize <= aDirtyStart
  return aTileOffset +
         IntDivFloor(aDirtyStart - aTileOffset, aTileSize) * aTileSize;
}

/**
 * Return the smallest 'v' such that v = aTileOffset + N*aTileSize, for some
 * integer N, and v >= aDirtyEnd.
 */
static nscoord
FindTileEnd(nscoord aDirtyEnd, nscoord aTileOffset, nscoord aTileSize)
{
  // Find smallest integer N such that aTileOffset + N*aTileSize >= aDirtyEnd
  return aTileOffset +
         IntDivCeil(aDirtyEnd - aTileOffset, aTileSize) * aTileSize;
}

static void
PixelSnapRectangle(gfxContext* aContext, nsIDeviceContext *aDC, nsRect& aRect)
{
  gfxRect tmpRect;
  tmpRect.pos.x = aDC->AppUnitsToGfxUnits(aRect.x);
  tmpRect.pos.y = aDC->AppUnitsToGfxUnits(aRect.y);
  tmpRect.size.width = aDC->AppUnitsToGfxUnits(aRect.width);
  tmpRect.size.height = aDC->AppUnitsToGfxUnits(aRect.height);
  if (aContext->UserToDevicePixelSnapped(tmpRect)) {
    tmpRect = aContext->DeviceToUser(tmpRect);
    aRect.x = aDC->GfxUnitsToAppUnits(tmpRect.pos.x);
    aRect.y = aDC->GfxUnitsToAppUnits(tmpRect.pos.y);
    aRect.width = aDC->GfxUnitsToAppUnits(tmpRect.XMost()) - aRect.x;
    aRect.height = aDC->GfxUnitsToAppUnits(tmpRect.YMost()) - aRect.y;
  }
}

static void
PixelSnapPoint(gfxContext* aContext, nsIDeviceContext *aDC, nsPoint& aPoint)
{
  gfxRect tmpRect;
  tmpRect.pos.x = aDC->AppUnitsToGfxUnits(aPoint.x);
  tmpRect.pos.y = aDC->AppUnitsToGfxUnits(aPoint.y);
  tmpRect.size.width = 0;
  tmpRect.size.height = 0;
  if (aContext->UserToDevicePixelSnapped(tmpRect)) {
    tmpRect = aContext->DeviceToUser(tmpRect);
    aPoint.x = aDC->GfxUnitsToAppUnits(tmpRect.pos.x);
    aPoint.y = aDC->GfxUnitsToAppUnits(tmpRect.pos.y);
  }
}

void
nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext,
                                      nsIRenderingContext& aRenderingContext,
                                      nsIFrame* aForFrame,
                                      const nsRect& aDirtyRect,
                                      const nsRect& aBorderArea,
                                      const nsStyleBackground& aColor,
                                      const nsStyleBorder& aBorder,
                                      const nsStylePadding& aPadding,
                                      PRBool aUsePrintSettings,
                                      nsRect* aBGClipRect)
{
  NS_PRECONDITION(aForFrame,
                  "Frame is expected to be provided to PaintBackground");

  PRBool canDrawBackgroundImage = PR_TRUE;
  PRBool canDrawBackgroundColor = PR_TRUE;

  if (aUsePrintSettings) {
    canDrawBackgroundImage = aPresContext->GetBackgroundImageDraw();
    canDrawBackgroundColor = aPresContext->GetBackgroundColorDraw();
  }

  // Check to see if we have an appearance defined.  If so, we let the theme
  // renderer draw the background and bail out.
  const nsStyleDisplay* displayData = aForFrame->GetStyleDisplay();
  if (displayData->mAppearance) {
    nsITheme *theme = aPresContext->GetTheme();
    if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame, displayData->mAppearance)) {
      nsPoint offset = aBorderArea.TopLeft();
      nsIRenderingContext::AutoPushTranslation
          translate(&aRenderingContext, offset.x, offset.y);
      nsRect dirty;
      nsRect border = aBorderArea - offset;
      dirty.IntersectRect(aDirtyRect - offset, border);
      theme->DrawWidgetBackground(&aRenderingContext, aForFrame, 
                                  displayData->mAppearance, border, dirty);
      return;
    }
  }

  nsRect bgClipArea;
  if (aBGClipRect) {
    bgClipArea = *aBGClipRect;
  }
  else {
    // The background is rendered over the 'background-clip' area.
    bgClipArea = aBorderArea;
    if (aColor.mBackgroundClip != NS_STYLE_BG_CLIP_BORDER) {
      NS_ASSERTION(aColor.mBackgroundClip == NS_STYLE_BG_CLIP_PADDING,
                   "unknown background-clip value");
      nsMargin border = aForFrame->GetUsedBorder();
      aForFrame->ApplySkipSides(border);
      bgClipArea.Deflate(border);
    }
  }

  nsIDeviceContext *dc = aPresContext->DeviceContext();
  nsRefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();

  // Snap bgClipArea to device pixel boundaries.  (We have to snap
  // bgOriginArea below; if we don't do this as well then we could make
  // incorrect decisions about various optimizations.)
  PixelSnapRectangle(ctx, dc, bgClipArea);

  // The actual dirty rect is the intersection of the 'background-clip'
  // area and the dirty rect we were given
  nsRect dirtyRect;
  if (!dirtyRect.IntersectRect(bgClipArea, aDirtyRect)) {
    // Nothing to paint
    return;
  }

  // if there is no background image or background images are turned off, try a color.
  if (!aColor.mBackgroundImage || !canDrawBackgroundImage) {
    PaintBackgroundColor(aPresContext, aRenderingContext, aForFrame, bgClipArea,
                         aColor, aBorder, aPadding, canDrawBackgroundColor);
    return;
  }

  // We have a background image

  // Lookup the image
  imgIRequest *req = aPresContext->LoadImage(aColor.mBackgroundImage,
                                             aForFrame);

  PRUint32 status = imgIRequest::STATUS_ERROR;
  if (req)
    req->GetImageStatus(&status);

  if (!req || !(status & imgIRequest::STATUS_FRAME_COMPLETE) || !(status & imgIRequest::STATUS_SIZE_AVAILABLE)) {
    PaintBackgroundColor(aPresContext, aRenderingContext, aForFrame, bgClipArea,
                         aColor, aBorder, aPadding, canDrawBackgroundColor);
    return;
  }

  nsCOMPtr<imgIContainer> image;
  req->GetImage(getter_AddRefs(image));

  nsSize imageSize;
  image->GetWidth(&imageSize.width);
  image->GetHeight(&imageSize.height);

  imageSize.width = nsPresContext::CSSPixelsToAppUnits(imageSize.width);
  imageSize.height = nsPresContext::CSSPixelsToAppUnits(imageSize.height);

  req = nsnull;

  nsRect bgOriginArea;

  nsIAtom* frameType = aForFrame->GetType();
  if (frameType == nsGkAtoms::inlineFrame ||
      frameType == nsGkAtoms::positionedInlineFrame) {
    switch (aColor.mBackgroundInlinePolicy) {
    case NS_STYLE_BG_INLINE_POLICY_EACH_BOX:
      bgOriginArea = aBorderArea;
      break;
    case NS_STYLE_BG_INLINE_POLICY_BOUNDING_BOX:
      bgOriginArea = gInlineBGData->GetBoundingRect(aForFrame) +
                     aBorderArea.TopLeft();
      break;
    default:
      NS_ERROR("Unknown background-inline-policy value!  "
               "Please, teach me what to do.");
    case NS_STYLE_BG_INLINE_POLICY_CONTINUOUS:
      bgOriginArea = gInlineBGData->GetContinuousRect(aForFrame) +
                     aBorderArea.TopLeft();
      break;
    }
  }
  else {
    bgOriginArea = aBorderArea;
  }

  // Background images are tiled over the 'background-clip' area
  // but the origin of the tiling is based on the 'background-origin' area
  if (aColor.mBackgroundOrigin != NS_STYLE_BG_ORIGIN_BORDER) {
    nsMargin border = aForFrame->GetUsedBorder();
    aForFrame->ApplySkipSides(border);
    bgOriginArea.Deflate(border);
    if (aColor.mBackgroundOrigin != NS_STYLE_BG_ORIGIN_PADDING) {
      nsMargin padding = aForFrame->GetUsedPadding();
      aForFrame->ApplySkipSides(padding);
      bgOriginArea.Deflate(padding);
      NS_ASSERTION(aColor.mBackgroundOrigin == NS_STYLE_BG_ORIGIN_CONTENT,
                   "unknown background-origin value");
    }
  }

  // Snap bgOriginArea to device pixel boundaries to avoid variations in
  // tiling when the subpixel position of the element changes.
  PixelSnapRectangle(ctx, dc, bgOriginArea);

  // Based on the repeat setting, compute how many tiles we should
  // lay down for each axis. The value computed is the maximum based
  // on the dirty rect before accounting for the background-position.
  nscoord tileWidth = imageSize.width;
  nscoord tileHeight = imageSize.height;
  PRBool  needBackgroundColor = !(aColor.mBackgroundFlags &
                                  NS_STYLE_BG_COLOR_TRANSPARENT);
  PRIntn  repeat = aColor.mBackgroundRepeat;
  nscoord xDistance, yDistance;

  switch (repeat) {
    case NS_STYLE_BG_REPEAT_X:
      xDistance = dirtyRect.width;
      yDistance = tileHeight;
      break;
    case NS_STYLE_BG_REPEAT_Y:
      xDistance = tileWidth;
      yDistance = dirtyRect.height;
      break;
    case NS_STYLE_BG_REPEAT_XY:
      xDistance = dirtyRect.width;
      yDistance = dirtyRect.height;
      if (needBackgroundColor) {
        // If the image is completely opaque, we do not need to paint the
        // background color
        nsCOMPtr<gfxIImageFrame> gfxImgFrame;
        image->GetCurrentFrame(getter_AddRefs(gfxImgFrame));
        if (gfxImgFrame) {
          gfxImgFrame->GetNeedsBackground(&needBackgroundColor);

          /* check for tiling of a image where frame smaller than container */
          nsSize iSize;
          image->GetWidth(&iSize.width);
          image->GetHeight(&iSize.height);
          nsRect iframeRect;
          gfxImgFrame->GetRect(iframeRect);
          if (iSize.width != iframeRect.width ||
              iSize.height != iframeRect.height) {
            needBackgroundColor = PR_TRUE;
          }
        }
      }
      break;
    case NS_STYLE_BG_REPEAT_OFF:
    default:
      NS_ASSERTION(repeat == NS_STYLE_BG_REPEAT_OFF, "unknown background-repeat value");
      xDistance = tileWidth;
      yDistance = tileHeight;
      break;
  }

  // The background color is rendered over the 'background-clip' area
  if (needBackgroundColor) {
    PaintBackgroundColor(aPresContext, aRenderingContext, aForFrame, bgClipArea,
                         aColor, aBorder, aPadding, canDrawBackgroundColor);
  }

  if ((tileWidth == 0) || (tileHeight == 0) || dirtyRect.IsEmpty()) {
    // Nothing left to paint
    return;
  }

  nsPoint borderAreaOriginSnapped = aBorderArea.TopLeft();
  PixelSnapPoint(ctx, dc, borderAreaOriginSnapped);

  // Compute the anchor point.
  //
  // When tiling, the anchor coordinate values will be negative offsets
  // from the background-origin area.

  // relative to the origin of aForFrame
  nsPoint anchor;
  if (NS_STYLE_BG_ATTACHMENT_FIXED == aColor.mBackgroundAttachment) {
    // If it's a fixed background attachment, then the image is placed
    // relative to the viewport, which is the area of the root frame
    // in a screen context or the page content frame in a print context.

    // Remember that we've drawn position-varying content in this prescontext
    aPresContext->SetRenderedPositionVaryingContent();

    nsIFrame* topFrame =
      aPresContext->PresShell()->FrameManager()->GetRootFrame();
    NS_ASSERTION(topFrame, "no root frame");
    nsIFrame* pageContentFrame = nsnull;
    if (aPresContext->IsPaginated()) {
      pageContentFrame =
        nsLayoutUtils::GetClosestFrameOfType(aForFrame, nsGkAtoms::pageContentFrame);
      if (pageContentFrame) {
        topFrame = pageContentFrame;
      }
      // else this is an embedded shell and its root frame is what we want
    }

    nsRect viewportArea = topFrame->GetRect();

    if (!pageContentFrame) {
      // Subtract the size of scrollbars.
      nsIScrollableFrame* scrollableFrame =
        aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
      if (scrollableFrame) {
        nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
        viewportArea.Deflate(scrollbars);
      }
    }
     
    // Get the anchor point, relative to the viewport.
    ComputeBackgroundAnchorPoint(aColor, viewportArea, viewportArea, tileWidth, tileHeight, anchor);

    // Convert the anchor point from viewport coordinates to aForFrame
    // coordinates.
    anchor -= aForFrame->GetOffsetTo(topFrame);
  } else {
    if (frameType == nsGkAtoms::canvasFrame) {
      // If the frame is the canvas, the image is placed relative to
      // the root element's (first) frame (see bug 46446)
      nsRect firstRootElementFrameArea;
      nsIFrame* firstRootElementFrame = aForFrame->GetFirstChild(nsnull);
      NS_ASSERTION(firstRootElementFrame, "A canvas with a background "
        "image had no child frame, which is impossible according to CSS. "
        "Make sure there isn't a background image specified on the "
        "|:viewport| pseudo-element in |html.css|.");

      // temporary null check -- see bug 97226
      if (firstRootElementFrame) {
        firstRootElementFrameArea = firstRootElementFrame->GetRect();

        // Take the border out of the frame's rect
        const nsStyleBorder* borderStyle = firstRootElementFrame->GetStyleBorder();
        firstRootElementFrameArea.Deflate(borderStyle->GetBorder());

        // Get the anchor point
        ComputeBackgroundAnchorPoint(aColor, firstRootElementFrameArea +
            aBorderArea.TopLeft(), bgClipArea, tileWidth, tileHeight, anchor);
      } else {
        ComputeBackgroundAnchorPoint(aColor, bgOriginArea, bgClipArea, tileWidth, tileHeight, anchor);
      }
    } else {
      // Otherwise, it is the normal case, and the background is
      // simply placed relative to the frame's background-clip area
      ComputeBackgroundAnchorPoint(aColor, bgOriginArea, bgClipArea, tileWidth, tileHeight, anchor);
    }

    // For scrolling attachment, the anchor is within the 'background-clip'
    anchor.x += bgClipArea.x - borderAreaOriginSnapped.x;
    anchor.y += bgClipArea.y - borderAreaOriginSnapped.y;
  }

  // Pixel-snap the anchor point so that we don't end up with blurry
  // images due to subpixel positions.  But round 0.5 down rather than
  // up, since that's what we've always done.  (And do that by just
  // snapping the negative of the point.)
  anchor.x = -anchor.x; anchor.y = -anchor.y;
  PixelSnapPoint(ctx, dc, anchor);
  anchor.x = -anchor.x; anchor.y = -anchor.y;

  ctx->Save();

  nscoord appUnitsPerPixel = aPresContext->DevPixelsToAppUnits(1);

  ctx->NewPath();
  ctx->Rectangle(RectToGfxRect(dirtyRect, appUnitsPerPixel), PR_TRUE);
  ctx->Clip();

  nsStyleCoord bordStyleRadius[4];
  nscoord borderRadii[4];

  // get the radius for our border
  bordStyleRadius[NS_SIDE_TOP] = aBorder.mBorderRadius.GetTop();       // topleft
  bordStyleRadius[NS_SIDE_RIGHT] = aBorder.mBorderRadius.GetRight();   // topright
  bordStyleRadius[NS_SIDE_BOTTOM] = aBorder.mBorderRadius.GetBottom(); // bottomright
  bordStyleRadius[NS_SIDE_LEFT] = aBorder.mBorderRadius.GetLeft();     // bottomleft

  PRBool haveRadius = PR_FALSE;
  PRUint8 side = 0;
  for (; side < 4; ++side) {
    borderRadii[side] = 0;
    switch (bordStyleRadius[side].GetUnit()) {
      case eStyleUnit_Percent:
        borderRadii[side] = nscoord(bordStyleRadius[side].GetPercentValue() *
                                    aForFrame->GetSize().width);
        break;
      case eStyleUnit_Coord:
        borderRadii[side] = bordStyleRadius[side].GetCoordValue();
        break;
      default:
        break;
    }

    if (borderRadii[side] != 0)
      haveRadius = PR_TRUE;
  }

  if (haveRadius) {
    gfxFloat radii[4];
    ComputePixelRadii(borderRadii, bgClipArea, aBorder.GetBorder(),
                      aForFrame ? aForFrame->GetSkipSides() : 0,
                      appUnitsPerPixel, radii);

    gfxRect oRect(RectToGfxRect(bgClipArea, appUnitsPerPixel));
    oRect.Round();
    oRect.Condition();

    ctx->NewPath();
    DoRoundedRectCWSubPath(ctx, oRect, radii);
    ctx->Clip();
  }      

  // Compute the x and y starting points and limits for tiling

  /* An Overview Of The Following Logic

          A........ . . . . . . . . . . . . . .
          :   +---:-------.-------.-------.----  /|\
          :   |   :       .       .       .       |  nh 
          :.......: . . . x . . . . . . . . . .  \|/   
          .   |   .       .       .       .        
          .   |   .       .  ###########  .        
          . . . . . . . . . .#. . . . .#. . . .     
          .   |   .       .  ###########  .      /|\
          .   |   .       .       .       .       |  h
          . . | . . . . . . . . . . . . . z . .  \|/
          .   |   .       .       .       .    
          |<-----nw------>|       |<--w-->|

       ---- = the background clip area edge. The painting is done within
              to this area.  If the background is positioned relative to the 
              viewport ('fixed') then this is the viewport edge.

       .... = the primary tile.

       . .  = the other tiles.

       #### = the dirtyRect. This is the minimum region we want to cover.

          A = The anchor point. This is the point at which the tile should
              start. Always negative or zero.

          x = x0 and y0 in the code. The point at which tiling must start
              so that the fewest tiles are laid out while completely
              covering the dirtyRect area.

          z = x1 and y1 in the code. The point at which tiling must end so
              that the fewest tiles are laid out while completely covering
              the dirtyRect area.

          w = the width of the tile (tileWidth).

          h = the height of the tile (tileHeight).

          n = the number of whole tiles that fit between 'A' and 'x'.
              (the vertical n and the horizontal n are different)


       Therefore, 

          x0 = bgClipArea.x + anchor.x + n * tileWidth;

       ...where n is an integer greater or equal to 0 fitting:

          n * tileWidth <= 
                      dirtyRect.x - (bgClipArea.x + anchor.x) <=
                                                             (n+1) * tileWidth

       ...i.e.,

          n <= (dirtyRect.x - (bgClipArea.x + anchor.x)) / tileWidth < n + 1

       ...which, treating the division as an integer divide rounding down, gives:

          n = (dirtyRect.x - (bgClipArea.x + anchor.x)) / tileWidth

       Substituting into the original expression for x0:

          x0 = bgClipArea.x + anchor.x +
               ((dirtyRect.x - (bgClipArea.x + anchor.x)) / tileWidth) *
               tileWidth;

       From this x1 is determined,

          x1 = x0 + m * tileWidth;

       ...where m is an integer greater than 0 fitting:

          (m - 1) * tileWidth <
                            dirtyRect.x + dirtyRect.width - x0 <=
                                                               m * tileWidth

       ...i.e.,

          m - 1 < (dirtyRect.x + dirtyRect.width - x0) / tileWidth <= m

       ...which, treating the division as an integer divide, and making it
          round up, gives:

          m = (dirtyRect.x + dirtyRect.width - x0 + tileWidth - 1) / tileWidth

       Substituting into the original expression for x1:

          x1 = x0 + ((dirtyRect.x + dirtyRect.width - x0 + tileWidth - 1) /
                     tileWidth) * tileWidth

       The vertical case is analogous. If the background is fixed, then 
       bgClipArea.x and bgClipArea.y are set to zero when finding the parent
       viewport, above.

  */

  // relative to aBorderArea.TopLeft()
  // ... but pixel-snapped, so that it comes out correctly relative to
  // all the other pixel-snapped things
  nsRect tileRect(anchor, nsSize(tileWidth, tileHeight));
  if (repeat & NS_STYLE_BG_REPEAT_X) {
    // When tiling in the x direction, adjust the starting position of the
    // tile to account for dirtyRect.x. When tiling in x, the anchor.x value
    // will be a negative value used to adjust the starting coordinate.
    nscoord x0 = FindTileStart(dirtyRect.x - borderAreaOriginSnapped.x, anchor.x, tileWidth);
    nscoord x1 = FindTileEnd(dirtyRect.XMost() - borderAreaOriginSnapped.x, anchor.x, tileWidth);
    tileRect.x = x0;
    tileRect.width = x1 - x0;
  }
  if (repeat & NS_STYLE_BG_REPEAT_Y) {
    // When tiling in the y direction, adjust the starting position of the
    // tile to account for dirtyRect.y. When tiling in y, the anchor.y value
    // will be a negative value used to adjust the starting coordinate.
    nscoord y0 = FindTileStart(dirtyRect.y - borderAreaOriginSnapped.y, anchor.y, tileHeight);
    nscoord y1 = FindTileEnd(dirtyRect.YMost() - borderAreaOriginSnapped.y, anchor.y, tileHeight);
    tileRect.y = y0;
    tileRect.height = y1 - y0;
  }

  // Take the intersection again to paint only the required area.
  nsRect absTileRect = tileRect + borderAreaOriginSnapped;
  
  nsRect drawRect;
  if (drawRect.IntersectRect(absTileRect, dirtyRect)) {
    // Note that due to the way FindTileStart works we're guaranteed
    // that drawRect overlaps the top-left-most tile when repeating.
    NS_ASSERTION(drawRect.x >= absTileRect.x && drawRect.y >= absTileRect.y,
                 "Bogus intersection");
    NS_ASSERTION(drawRect.x < absTileRect.x + tileWidth,
                 "Bogus x coord for draw rect");
    NS_ASSERTION(drawRect.y < absTileRect.y + tileHeight,
                 "Bogus y coord for draw rect");
    // Figure out whether we can get away with not tiling at all.
    nsRect sourceRect = drawRect - absTileRect.TopLeft();
    // Compute the subimage rectangle that we expect to be sampled.
    // This is the tile rectangle, clipped to the bgClipArea, and then
    // passed in relative to the image top-left.
    nsRect destRect; // The rectangle we would draw ignoring dirty-rect
    destRect.IntersectRect(absTileRect, bgClipArea);
    nsRect subimageRect = destRect - borderAreaOriginSnapped - tileRect.TopLeft();
    if (sourceRect.XMost() <= tileWidth && sourceRect.YMost() <= tileHeight) {
      // The entire drawRect is contained inside a single tile; just
      // draw the corresponding part of the image once.
      nsLayoutUtils::DrawImage(&aRenderingContext, image,
              destRect, drawRect, &subimageRect);
    } else {
      // Note that the subimage is in tile space so it may cover
      // multiple tiles of the image.
      subimageRect.ScaleRoundOutInverse(nsIDeviceContext::AppUnitsPerCSSPixel());
      aRenderingContext.DrawTile(image, absTileRect.x, absTileRect.y,
              &drawRect, &subimageRect);
    }
  }

  ctx->Restore();

}

void
nsCSSRendering::PaintBackgroundColor(nsPresContext* aPresContext,
                                     nsIRenderingContext& aRenderingContext,
                                     nsIFrame* aForFrame,
                                     const nsRect& aBgClipArea,
                                     const nsStyleBackground& aColor,
                                     const nsStyleBorder& aBorder,
                                     const nsStylePadding& aPadding,
                                     PRBool aCanPaintNonWhite)
{
  // If we're only allowed to paint white, then don't bail out on transparent
  // color if we're not completely transparent.  See the corresponding check
  // for whether we're allowed to paint background images in
  // PaintBackgroundWithSC before the first call to PaintBackgroundColor.
  if ((aColor.mBackgroundFlags & NS_STYLE_BG_COLOR_TRANSPARENT) &&
      (aCanPaintNonWhite || aColor.IsTransparent())) {
    // nothing to paint
    return;
  }

  nsStyleCoord bordStyleRadius[4];
  nscoord borderRadii[4];
  nsRect bgClipArea(aBgClipArea);

  // get the radius for our border
  bordStyleRadius[NS_SIDE_TOP] = aBorder.mBorderRadius.GetTop();       // topleft
  bordStyleRadius[NS_SIDE_RIGHT] = aBorder.mBorderRadius.GetRight();   // topright
  bordStyleRadius[NS_SIDE_BOTTOM] = aBorder.mBorderRadius.GetBottom(); // bottomright
  bordStyleRadius[NS_SIDE_LEFT] = aBorder.mBorderRadius.GetLeft();     // bottomleft

  PRUint8 side = 0;
  for (; side < 4; ++side) {
    borderRadii[side] = 0;
    switch (bordStyleRadius[side].GetUnit()) {
      case eStyleUnit_Percent:
        borderRadii[side] = nscoord(bordStyleRadius[side].GetPercentValue() *
                                    aForFrame->GetSize().width);
        break;
      case eStyleUnit_Coord:
        borderRadii[side] = bordStyleRadius[side].GetCoordValue();
        break;
      default:
        break;
    }
  }

  // Rounded version of the border
  for (side = 0; side < 4; ++side) {
    if (borderRadii[side] > 0) {
      PaintRoundedBackground(aPresContext, aRenderingContext, aForFrame,
                             bgClipArea, aColor, aBorder, borderRadii,
                             aCanPaintNonWhite);
      return;
    }
  }

  nscolor color;
  if (!aCanPaintNonWhite) {
    color = NS_RGB(255, 255, 255);
  } else {
    color = aColor.mBackgroundColor;
  }
  
  aRenderingContext.SetColor(color);
  aRenderingContext.FillRect(bgClipArea);
}

/** ---------------------------------------------------
 *  See documentation in nsCSSRendering.h
 *  @update 3/26/99 dwc
 */
void
nsCSSRendering::PaintRoundedBackground(nsPresContext* aPresContext,
                                       nsIRenderingContext& aRenderingContext,
                                       nsIFrame* aForFrame,
                                       const nsRect& aBgClipArea,
                                       const nsStyleBackground& aColor,
                                       const nsStyleBorder& aBorder,
                                       nscoord aTheRadius[4],
                                       PRBool aCanPaintNonWhite)
{
  nsRefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();

  // needed for our border thickness
  nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();

  nscolor color = aColor.mBackgroundColor;
  if (!aCanPaintNonWhite) {
    color = NS_RGB(255, 255, 255);
  }
  aRenderingContext.SetColor(color);

  // Adjust for background-clip, if necessary
  if (aColor.mBackgroundClip != NS_STYLE_BG_CLIP_BORDER) {
    NS_ASSERTION(aColor.mBackgroundClip == NS_STYLE_BG_CLIP_PADDING, "unknown background-clip value");

    // Get the radius to the outer edge of the padding.
    // -moz-border-radius is the radius to the outer edge of the border.
    NS_FOR_CSS_SIDES(side) {
      aTheRadius[side] -= aBorder.GetBorderWidth(side);
      aTheRadius[side] = PR_MAX(aTheRadius[side], 0);
    }
  }

  // the bgClipArea is the outside
  gfxRect oRect(RectToGfxRect(aBgClipArea, appUnitsPerPixel));
  oRect.Round();
  oRect.Condition();
  if (oRect.IsEmpty())
    return;

  // convert the radii
  gfxFloat radii[4];
  nsMargin border = aBorder.GetBorder();

  ComputePixelRadii(aTheRadius, aBgClipArea, border,
                    aForFrame ? aForFrame->GetSkipSides() : 0,
                    appUnitsPerPixel, radii);

  // Add 1.0 to any border radii; if we don't, the border and background
  // curves will combine to have fringing at the rounded corners.  Since
  // alpha is used for coverage, we have problems because the border and
  // background should have identical coverage, and the border should
  // overlay the background exactly.  The way to avoid this is by using
  // a supersampling scheme, but we don't have the mechanism in place to do
  // this.  So, this will do for now.
  for (int i = 0; i < 4; i++) {
    if (radii[i] > 0.0)
      radii[i] += 1.0;
  }

  ctx->NewPath();
  DoRoundedRectCWSubPath(ctx, oRect, radii);
  ctx->SetColor(gfxRGBA(color));
  ctx->Fill();
}


void FillOrInvertRect(nsIRenderingContext& aRC, nscoord aX, nscoord aY, nscoord aWidth, nscoord aHeight, PRBool aInvert)
{
#ifdef GFX_HAS_INVERT
  if (aInvert) {
    aRC.InvertRect(aX, aY, aWidth, aHeight);
  } else {
#endif
    aRC.FillRect(aX, aY, aWidth, aHeight);
#ifdef GFX_HAS_INVERT
  }
#endif
}

void FillOrInvertRect(nsIRenderingContext& aRC, const nsRect& aRect, PRBool aInvert)
{
#ifdef GFX_HAS_INVERT
  if (aInvert) {
    aRC.InvertRect(aRect);
  } else {
#endif
    aRC.FillRect(aRect);
#ifdef GFX_HAS_INVERT
  }
#endif
}

// Begin table border-collapsing section
// These functions were written to not disrupt the normal ones and yet satisfy some additional requirements
// At some point, all functions should be unified to include the additional functionality that these provide

static nscoord
RoundIntToPixel(nscoord aValue, 
                nscoord aTwipsPerPixel,
                PRBool  aRoundDown = PR_FALSE)
{
  if (aTwipsPerPixel <= 0) 
    // We must be rendering to a device that has a resolution greater than Twips! 
    // In that case, aValue is as accurate as it's going to get.
    return aValue; 

  nscoord halfPixel = NSToCoordRound(aTwipsPerPixel / 2.0f);
  nscoord extra = aValue % aTwipsPerPixel;
  nscoord finalValue = (!aRoundDown && (extra >= halfPixel)) ? aValue + (aTwipsPerPixel - extra) : aValue - extra;
  return finalValue;
}

static nscoord
RoundFloatToPixel(float   aValue, 
                  nscoord aTwipsPerPixel,
                  PRBool  aRoundDown = PR_FALSE)
{
  return RoundIntToPixel(NSToCoordRound(aValue), aTwipsPerPixel, aRoundDown);
}

static void
SetPoly(const nsRect& aRect,
        nsPoint*      poly)
{
  poly[0].x = aRect.x;
  poly[0].y = aRect.y;
  poly[1].x = aRect.x + aRect.width;
  poly[1].y = aRect.y;
  poly[2].x = aRect.x + aRect.width;
  poly[2].y = aRect.y + aRect.height;
  poly[3].x = aRect.x;
  poly[3].y = aRect.y + aRect.height;
  poly[4].x = aRect.x;
  poly[4].y = aRect.y;
}
          
static void 
DrawSolidBorderSegment(nsIRenderingContext& aContext,
                       nsRect               aRect,
                       nscoord              aTwipsPerPixel,
                       PRUint8              aStartBevelSide = 0,
                       nscoord              aStartBevelOffset = 0,
                       PRUint8              aEndBevelSide = 0,
                       nscoord              aEndBevelOffset = 0)
{

  if ((aRect.width == aTwipsPerPixel) || (aRect.height == aTwipsPerPixel) ||
      ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) {
    // simple line or rectangle
    if ((NS_SIDE_TOP == aStartBevelSide) || (NS_SIDE_BOTTOM == aStartBevelSide)) {
      if (1 == aRect.height) 
        aContext.DrawLine(aRect.x, aRect.y, aRect.x, aRect.y + aRect.height); 
      else 
        aContext.FillRect(aRect);
    }
    else {
      if (1 == aRect.width) 
        aContext.DrawLine(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y); 
      else 
        aContext.FillRect(aRect);
    }
  }
  else {
    // polygon with beveling
    nsPoint poly[5];
    SetPoly(aRect, poly);
    switch(aStartBevelSide) {
    case NS_SIDE_TOP:
      poly[0].x += aStartBevelOffset;
      poly[4].x = poly[0].x;
      break;
    case NS_SIDE_BOTTOM:
      poly[3].x += aStartBevelOffset;
      break;
    case NS_SIDE_RIGHT:
      poly[1].y += aStartBevelOffset;
      break;
    case NS_SIDE_LEFT:
      poly[0].y += aStartBevelOffset;
      poly[4].y = poly[0].y;
    }

    switch(aEndBevelSide) {
    case NS_SIDE_TOP:
      poly[1].x -= aEndBevelOffset;
      break;
    case NS_SIDE_BOTTOM:
      poly[2].x -= aEndBevelOffset;
      break;
    case NS_SIDE_RIGHT:
      poly[2].y -= aEndBevelOffset;
      break;
    case NS_SIDE_LEFT:
      poly[3].y -= aEndBevelOffset;
    }

    aContext.FillPolygon(poly, 5);
  }


}

static void
GetDashInfo(nscoord  aBorderLength,
            nscoord  aDashLength,
            nscoord  aTwipsPerPixel,
            PRInt32& aNumDashSpaces,
            nscoord& aStartDashLength,
            nscoord& aEndDashLength)
{
  aNumDashSpaces = 0;
  if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) {
    aStartDashLength = aBorderLength;
    aEndDashLength = 0;
  }
  else {
    aNumDashSpaces = aBorderLength / (2 * aDashLength); // round down
    nscoord extra = aBorderLength - aStartDashLength - aEndDashLength - (((2 * aNumDashSpaces) - 1) * aDashLength);
    if (extra > 0) {
      nscoord half = RoundIntToPixel(extra / 2, aTwipsPerPixel);
      aStartDashLength += half;
      aEndDashLength += (extra - half);
    }
  }
}

void 
nsCSSRendering::DrawTableBorderSegment(nsIRenderingContext&     aContext,
                                       PRUint8                  aBorderStyle,  
                                       nscolor                  aBorderColor,
                                       const nsStyleBackground* aBGColor,
                                       const nsRect&            aBorder,
                                       PRInt32                  aAppUnitsPerCSSPixel,
                                       PRUint8                  aStartBevelSide,
                                       nscoord                  aStartBevelOffset,
                                       PRUint8                  aEndBevelSide,
                                       nscoord                  aEndBevelOffset)
{
  aContext.SetColor (aBorderColor); 

  PRBool horizontal = ((NS_SIDE_TOP == aStartBevelSide) || (NS_SIDE_BOTTOM == aStartBevelSide));
  nscoord twipsPerPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerCSSPixel);
  PRUint8 ridgeGroove = NS_STYLE_BORDER_STYLE_RIDGE;

  if ((twipsPerPixel >= aBorder.width) || (twipsPerPixel >= aBorder.height) ||
      (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) || (NS_STYLE_BORDER_STYLE_DOTTED == aBorderStyle)) {
    // no beveling for 1 pixel border, dash or dot
    aStartBevelOffset = 0;
    aEndBevelOffset = 0;
  }

  gfxContext *ctx = aContext.ThebesContext();
  gfxContext::AntialiasMode oldMode = ctx->CurrentAntialiasMode();
  ctx->SetAntialiasMode(gfxContext::MODE_ALIASED);

  switch (aBorderStyle) {
  case NS_STYLE_BORDER_STYLE_NONE:
  case NS_STYLE_BORDER_STYLE_HIDDEN:
    //NS_ASSERTION(PR_FALSE, "style of none or hidden");
    break;
  case NS_STYLE_BORDER_STYLE_DOTTED:
  case NS_STYLE_BORDER_STYLE_DASHED: 
    {
      nscoord dashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH;
      // make the dash length proportional to the border thickness
      dashLength *= (horizontal) ? aBorder.height : aBorder.width;
      // make the min dash length for the ends 1/2 the dash length
      nscoord minDashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) 
                              ? RoundFloatToPixel(((float)dashLength) / 2.0f, twipsPerPixel) : dashLength;
      minDashLength = PR_MAX(minDashLength, twipsPerPixel);
      nscoord numDashSpaces = 0;
      nscoord startDashLength = minDashLength;
      nscoord endDashLength   = minDashLength;
      if (horizontal) {
        GetDashInfo(aBorder.width, dashLength, twipsPerPixel, numDashSpaces, startDashLength, endDashLength);
        nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height);
        DrawSolidBorderSegment(aContext, rect, twipsPerPixel);
        for (PRInt32 spaceX = 0; spaceX < numDashSpaces; spaceX++) {
          rect.x += rect.width + dashLength;
          rect.width = (spaceX == (numDashSpaces - 1)) ? endDashLength : dashLength;
          DrawSolidBorderSegment(aContext, rect, twipsPerPixel);
        }
      }
      else {
        GetDashInfo(aBorder.height, dashLength, twipsPerPixel, numDashSpaces, startDashLength, endDashLength);
        nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength);
        DrawSolidBorderSegment(aContext, rect, twipsPerPixel);
        for (PRInt32 spaceY = 0; spaceY < numDashSpaces; spaceY++) {
          rect.y += rect.height + dashLength;
          rect.height = (spaceY == (numDashSpaces - 1)) ? endDashLength : dashLength;
          DrawSolidBorderSegment(aContext, rect, twipsPerPixel);
        }
      }
    }
    break;                                  
  case NS_STYLE_BORDER_STYLE_GROOVE:
    ridgeGroove = NS_STYLE_BORDER_STYLE_GROOVE; // and fall through to ridge
  case NS_STYLE_BORDER_STYLE_RIDGE:
    if ((horizontal && (twipsPerPixel >= aBorder.height)) ||
        (!horizontal && (twipsPerPixel >= aBorder.width))) {
      // a one pixel border
      DrawSolidBorderSegment(aContext, aBorder, twipsPerPixel, aStartBevelSide, aStartBevelOffset,
                             aEndBevelSide, aEndBevelOffset);
    }
    else {
      nscoord startBevel = (aStartBevelOffset > 0) 
                            ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset, twipsPerPixel, PR_TRUE) : 0;
      nscoord endBevel =   (aEndBevelOffset > 0) 
                            ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset, twipsPerPixel, PR_TRUE) : 0;
      PRUint8 ridgeGrooveSide = (horizontal) ? NS_SIDE_TOP : NS_SIDE_LEFT;
      aContext.SetColor ( 
        MakeBevelColor(ridgeGrooveSide, ridgeGroove, aBGColor->mBackgroundColor, aBorderColor));
      nsRect rect(aBorder);
      nscoord half;
      if (horizontal) { // top, bottom
        half = RoundFloatToPixel(0.5f * (float)aBorder.height, twipsPerPixel);
        rect.height = half;
        if (NS_SIDE_TOP == aStartBevelSide) {
          rect.x += startBevel;
          rect.width -= startBevel;
        }
        if (NS_SIDE_TOP == aEndBevelSide) {
          rect.width -= endBevel;
        }
        DrawSolidBorderSegment(aContext, rect, twipsPerPixel, aStartBevelSide, 
                               startBevel, aEndBevelSide, endBevel);
      }
      else { // left, right
        half = RoundFloatToPixel(0.5f * (float)aBorder.width, twipsPerPixel);
        rect.width = half;
        if (NS_SIDE_LEFT == aStartBevelSide) {
          rect.y += startBevel;
          rect.height -= startBevel;
        }
        if (NS_SIDE_LEFT == aEndBevelSide) {
          rect.height -= endBevel;
        }
        DrawSolidBorderSegment(aContext, rect, twipsPerPixel, aStartBevelSide, 
                               startBevel, aEndBevelSide, endBevel);
      }

      rect = aBorder;
      ridgeGrooveSide = (NS_SIDE_TOP == ridgeGrooveSide) ? NS_SIDE_BOTTOM : NS_SIDE_RIGHT;
      aContext.SetColor ( 
        MakeBevelColor(ridgeGrooveSide, ridgeGroove, aBGColor->mBackgroundColor, aBorderColor));
      if (horizontal) {
        rect.y = rect.y + half;
        rect.height = aBorder.height - half;
        if (NS_SIDE_BOTTOM == aStartBevelSide) {
          rect.x += startBevel;
          rect.width -= startBevel;
        }
        if (NS_SIDE_BOTTOM == aEndBevelSide) {
          rect.width -= endBevel;
        }
        DrawSolidBorderSegment(aContext, rect, twipsPerPixel, aStartBevelSide, 
                               startBevel, aEndBevelSide, endBevel);
      }
      else {
        rect.x = rect.x + half;
        rect.width = aBorder.width - half;
        if (NS_SIDE_RIGHT == aStartBevelSide) {
          rect.y += aStartBevelOffset - startBevel;
          rect.height -= startBevel;
        }
        if (NS_SIDE_RIGHT == aEndBevelSide) {
          rect.height -= endBevel;
        }
        DrawSolidBorderSegment(aContext, rect, twipsPerPixel, aStartBevelSide, 
                               startBevel, aEndBevelSide, endBevel);
      }
    }
    break;
  case NS_STYLE_BORDER_STYLE_DOUBLE:
    if ((aBorder.width > 2) && (aBorder.height > 2)) {
      nscoord startBevel = (aStartBevelOffset > 0) 
                            ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset, twipsPerPixel) : 0;
      nscoord endBevel =   (aEndBevelOffset > 0) 
                            ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset, twipsPerPixel) : 0;
      if (horizontal) { // top, bottom
        nscoord thirdHeight = RoundFloatToPixel(0.333333f * (float)aBorder.height, twipsPerPixel);

        // draw the top line or rect
        nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight);
        if (NS_SIDE_TOP == aStartBevelSide) {
          topRect.x += aStartBevelOffset - startBevel;
          topRect.width -= aStartBevelOffset - startBevel;
        }
        if (NS_SIDE_TOP == aEndBevelSide) {
          topRect.width -= aEndBevelOffset - endBevel;
        }
        DrawSolidBorderSegment(aContext, topRect, twipsPerPixel, aStartBevelSide, 
                               startBevel, aEndBevelSide, endBevel);

        // draw the botom line or rect
        nscoord heightOffset = aBorder.height - thirdHeight; 
        nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width, aBorder.height - heightOffset);
        if (NS_SIDE_BOTTOM == aStartBevelSide) {
          bottomRect.x += aStartBevelOffset - startBevel;
          bottomRect.width -= aStartBevelOffset - startBevel;
        }
        if (NS_SIDE_BOTTOM == aEndBevelSide) {
          bottomRect.width -= aEndBevelOffset - endBevel;
        }
        DrawSolidBorderSegment(aContext, bottomRect, twipsPerPixel, aStartBevelSide, 
                               startBevel, aEndBevelSide, endBevel);
      }
      else { // left, right
        nscoord thirdWidth = RoundFloatToPixel(0.333333f * (float)aBorder.width, twipsPerPixel);

        nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height); 
        if (NS_SIDE_LEFT == aStartBevelSide) {
          leftRect.y += aStartBevelOffset - startBevel;
          leftRect.height -= aStartBevelOffset - startBevel;
        }
        if (NS_SIDE_LEFT == aEndBevelSide) {
          leftRect.height -= aEndBevelOffset - endBevel;
        }
        DrawSolidBorderSegment(aContext, leftRect, twipsPerPixel, aStartBevelSide,
                               startBevel, aEndBevelSide, endBevel);

        nscoord widthOffset = aBorder.width - thirdWidth; 
        nsRect rightRect(aBorder.x + widthOffset, aBorder.y, aBorder.width - widthOffset, aBorder.height);
        if (NS_SIDE_RIGHT == aStartBevelSide) {
          rightRect.y += aStartBevelOffset - startBevel;
          rightRect.height -= aStartBevelOffset - startBevel;
        }
        if (NS_SIDE_RIGHT == aEndBevelSide) {
          rightRect.height -= aEndBevelOffset - endBevel;
        }
        DrawSolidBorderSegment(aContext, rightRect, twipsPerPixel, aStartBevelSide,
                               startBevel, aEndBevelSide, endBevel);
      }
      break;
    }
    // else fall through to solid
  case NS_STYLE_BORDER_STYLE_SOLID:
    DrawSolidBorderSegment(aContext, aBorder, twipsPerPixel, aStartBevelSide, 
                           aStartBevelOffset, aEndBevelSide, aEndBevelOffset);
    break;
  case NS_STYLE_BORDER_STYLE_OUTSET:
  case NS_STYLE_BORDER_STYLE_INSET:
    NS_ASSERTION(PR_FALSE, "inset, outset should have been converted to groove, ridge");
    break;
  case NS_STYLE_BORDER_STYLE_AUTO:
    NS_ASSERTION(PR_FALSE, "Unexpected 'auto' table border");
    break;
  }

  ctx->SetAntialiasMode(oldMode);
}

// End table border-collapsing section

void
nsCSSRendering::PaintDecorationLine(gfxContext* aGfxContext,
                                    const nscolor aColor,
                                    const gfxPoint& aPt,
                                    const gfxSize& aLineSize,
                                    const gfxFloat aAscent,
                                    const gfxFloat aOffset,
                                    const PRUint8 aDecoration,
                                    const PRUint8 aStyle)
{
  gfxRect rect =
    GetTextDecorationRectInternal(aPt, aLineSize, aAscent, aOffset,
                                  aDecoration, aStyle);
  if (rect.IsEmpty())
    return;

  if (aDecoration != NS_STYLE_TEXT_DECORATION_UNDERLINE &&
      aDecoration != NS_STYLE_TEXT_DECORATION_OVERLINE &&
      aDecoration != NS_STYLE_TEXT_DECORATION_LINE_THROUGH)
  {
    NS_ERROR("Invalid decoration value!");
    return;
  }

  gfxFloat lineHeight = PR_MAX(NS_round(aLineSize.height), 1.0);
  PRBool contextIsSaved = PR_FALSE;

  gfxFloat oldLineWidth;
  nsRefPtr<gfxPattern> oldPattern;

  switch (aStyle) {
    case NS_STYLE_BORDER_STYLE_SOLID:
    case NS_STYLE_BORDER_STYLE_DOUBLE:
      oldLineWidth = aGfxContext->CurrentLineWidth();
      oldPattern = aGfxContext->GetPattern();
      break;
    case NS_STYLE_BORDER_STYLE_DASHED: {
      aGfxContext->Save();
      contextIsSaved = PR_TRUE;
      gfxFloat dashWidth = lineHeight * DOT_LENGTH * DASH_LENGTH;
      gfxFloat dash[2] = { dashWidth, dashWidth };
      aGfxContext->SetLineCap(gfxContext::LINE_CAP_BUTT);
      aGfxContext->SetDash(dash, 2, 0.0);
      break;
    }
    case NS_STYLE_BORDER_STYLE_DOTTED: {
      aGfxContext->Save();
      contextIsSaved = PR_TRUE;
      gfxFloat dashWidth = lineHeight * DOT_LENGTH;
      gfxFloat dash[2];
      if (lineHeight > 2.0) {
        dash[0] = 0.0;
        dash[1] = dashWidth * 2.0;
        aGfxContext->SetLineCap(gfxContext::LINE_CAP_ROUND);
      } else {
        dash[0] = dashWidth;
        dash[1] = dashWidth;
      }
      aGfxContext->SetDash(dash, 2, 0.0);
      break;
    }
    default:
      NS_ERROR("Invalid style value!");
      return;
  }

  // The y position should be set to the middle of the line.
  rect.pos.y += lineHeight / 2;

  aGfxContext->SetColor(gfxRGBA(aColor));
  aGfxContext->SetLineWidth(lineHeight);
  switch (aStyle) {
    case NS_STYLE_BORDER_STYLE_SOLID:
      aGfxContext->NewPath();
      aGfxContext->MoveTo(rect.TopLeft());
      aGfxContext->LineTo(rect.TopRight());
      aGfxContext->Stroke();
      break;
    case NS_STYLE_BORDER_STYLE_DOUBLE:
      aGfxContext->NewPath();
      aGfxContext->MoveTo(rect.TopLeft());
      aGfxContext->LineTo(rect.TopRight());
      rect.size.height -= lineHeight;
      aGfxContext->MoveTo(rect.BottomLeft());
      aGfxContext->LineTo(rect.BottomRight());
      aGfxContext->Stroke();
      break;
    case NS_STYLE_BORDER_STYLE_DOTTED:
    case NS_STYLE_BORDER_STYLE_DASHED:
      aGfxContext->NewPath();
      aGfxContext->MoveTo(rect.TopLeft());
      aGfxContext->LineTo(rect.TopRight());
      aGfxContext->Stroke();
      break;
    default:
      NS_ERROR("Invalid style value!");
      break;
  }

  if (contextIsSaved) {
    aGfxContext->Restore();
  } else {
    aGfxContext->SetPattern(oldPattern);
    aGfxContext->SetLineWidth(oldLineWidth);
  }
}

nsRect
nsCSSRendering::GetTextDecorationRect(nsPresContext* aPresContext,
                                      const gfxSize& aLineSize,
                                      const gfxFloat aAscent,
                                      const gfxFloat aOffset,
                                      const PRUint8 aDecoration,
                                      const PRUint8 aStyle)
{
  NS_ASSERTION(aPresContext, "aPresContext is null");

  gfxRect rect =
    GetTextDecorationRectInternal(gfxPoint(0, 0), aLineSize, aAscent, aOffset,
                                  aDecoration, aStyle);
  // The rect values are already rounded to nearest device pixels.
  nsRect r;
  r.x = aPresContext->GfxUnitsToAppUnits(rect.X());
  r.y = aPresContext->GfxUnitsToAppUnits(rect.Y());
  r.width = aPresContext->GfxUnitsToAppUnits(rect.Width());
  r.height = aPresContext->GfxUnitsToAppUnits(rect.Height());
  return r;
}

gfxRect
nsCSSRendering::GetTextDecorationRectInternal(const gfxPoint& aPt,
                                              const gfxSize& aLineSize,
                                              const gfxFloat aAscent,
                                              const gfxFloat aOffset,
                                              const PRUint8 aDecoration,
                                              const PRUint8 aStyle)
{
  gfxRect r;
  r.pos.x = NS_floor(aPt.x + 0.5);
  r.size.width = NS_round(aLineSize.width);

  gfxFloat basesize = NS_round(aLineSize.height);
  basesize = PR_MAX(basesize, 1.0);
  r.size.height = basesize;
  if (aStyle == NS_STYLE_BORDER_STYLE_DOUBLE) {
    gfxFloat gap = NS_round(basesize / 2.0);
    gap = PR_MAX(gap, 1.0);
    r.size.height = basesize * 2.0 + gap;
  } else {
    r.size.height = basesize;
  }

  gfxFloat baseline = NS_floor(aPt.y + aAscent + 0.5);
  gfxFloat offset = 0;
  switch (aDecoration) {
    case NS_STYLE_TEXT_DECORATION_UNDERLINE:
      offset = aOffset;
      break;
    case NS_STYLE_TEXT_DECORATION_OVERLINE:
      offset = aOffset - basesize + r.Height();
      break;
    case NS_STYLE_TEXT_DECORATION_LINE_THROUGH: {
      gfxFloat extra = NS_floor(r.Height() / 2.0 + 0.5);
      extra = PR_MAX(extra, basesize);
      offset = aOffset - basesize + extra;
      break;
    }
    default:
      NS_ERROR("Invalid decoration value!");
  }
  r.pos.y = baseline - NS_floor(offset + 0.5);
  return r;
}
