/*
 * 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 MathML Project.
 * 
 * The Initial Developer of the Original Code is The University Of 
 * Queensland.  Portions created by The University Of Queensland are
 * Copyright (C) 1999 The University Of Queensland.  All Rights Reserved.
 * 
 * Contributor(s): 
 *   Roger B. Sidje <rbs@maths.uq.edu.au>
 *   Shyjan Mahamud <mahamud@cs.cmu.edu>
 */

#include "nsCOMPtr.h"
#include "nsHTMLParts.h"
#include "nsIHTMLContent.h"
#include "nsFrame.h"
#include "nsLineLayout.h"
#include "nsHTMLIIDs.h"
#include "nsIPresContext.h"
#include "nsHTMLAtoms.h"
#include "nsUnitConversion.h"
#include "nsIStyleContext.h"
#include "nsStyleConsts.h"
#include "nsINameSpaceManager.h"
#include "nsString.h"
#include "nsIRenderingContext.h"
#include "nsIFontMetrics.h"
#include "nsStyleUtil.h"

#include "nsMathMLOperators.h"
#include "nsMathMLChar.h"

//
// nsMathMLChar implementation
//

// Table of extensible chars in a suitable format for algorithmic purpose
// XXX Error-prone stuff. See how it can be automated.

/* Structure of the table gMathMLCharGlyph[]:
   ------------------------------------------
   The table is divided into sections, each corresponding to a
   stretchy symbol. Each section itself is divided into two blocks
   that are separated with the special null code 0x0000.
   The first block consists of *any number* of different glyphs
   of *increasing* size. The second block consists of exactly
   *4* glyphs that can be used to build a larger size.

   Another separate table, gMathMLCharIndex[], is used to store 
   pointers to the beginning of each section.

   Three steps are needed to extend the table:
   ---------------------------------------=---
   1. Append your new enum in nsMathMLCharList.h
   2. Append your new section in gMathMLCharGlyph[].
   3. Append a new pointer in gMathMLCharIndex[], so that 
      gMathMLCharIndex[your_enum] points to the begining of
      your new section in gMathMLCharGlyph[]. That is, 
      your_section = gMathMLCharGlyph[gMathMLCharIndex[your_enum]]
 */ 

// XXX The current values in the table correspond to the Symbol font.
//     Other fonts will need their own customized tables.

// XXX The Stretching doesn't align the glyphs in italic style. Need to
//     force the style to normal before starting to stretch.

static PRUnichar gMathMLCharGlyph[] = {

  //------------
  //index = 0
  //Section for: left parenthesis
  //Block of different variants of *increasing* size...	
  0x0028, // LEFT PARENTHESIS	# parenleft
          // more here ...
  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  0xF8EB, // LEFT PAREN TOP	# parenlefttp (CUS)
  0xF8EC, // LEFT PAREN EXTENDER	# parenleftex (CUS)
  0xF8ED, // LEFT PAREN BOTTOM	# parenleftbt (CUS)
  0xF8EC, // glue = LEFT PAREN EXTENDER	# parenleftex (CUS)

  //------------
  //index = 6 
  //Section for: right parenthesis
  //Block of different variants of *increasing* size...	
  0x0029, // RIGHT PARENTHESIS	# parenright
          // more here ...
  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  0xF8F6, // RIGHT PAREN TOP	# parenrighttp (CUS)
  0xF8F7, // MIDDLE = RIGHT PAREN EXTENDER	# parenrightex (CUS)
  0xF8F8, // RIGHT PAREN BOTTOM	# parenrightbt (CUS)
  0xF8F7, // glue = RIGHT PAREN EXTENDER	# parenrightex (CUS)

  //------------
  //index = 12
  //Section for: integral
  //Block of different variants of *increasing* size...	
  0x222B, // INTEGRAL	# integral
          // more here ...
  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  0x2320, // TOP HALF INTEGRAL	# integraltp
  0xF8F5, // INTEGRAL EXTENDER	# integralex (CUS)
  0x2321, // BOTTOM HALF INTEGRAL	# integralbt
  0xF8F5, // glue = INTEGRAL EXTENDER	# integralex (CUS)

  //------------
  //index = 18
  //Section for: left square bracket
  //Block of different variants of *increasing* size...	
  0x005B, // LEFT SQUARE BRACKET	# bracketleft

  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  0xF8EE, // LEFT SQUARE BRACKET TOP	# bracketlefttp (CUS)
  0xF8EF, // LEFT SQUARE BRACKET EXTENDER	# bracketleftex (CUS)
  0xF8F0, // LEFT SQUARE BRACKET BOTTOM	# bracketleftbt (CUS)
  0xF8EF, // glue = LEFT SQUARE BRACKET EXTENDER	# bracketleftex (CUS)

  //------------
  //index = 24
  //Section for: right square bracket
  //Block of different variants of *increasing* size...	
  0x005D, // RIGHT SQUARE BRACKET	# bracketright
 
  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  0xF8F9, // RIGHT SQUARE BRACKET TOP	# bracketrighttp (CUS)
  0xF8FA, // RIGHT SQUARE BRACKET EXTENDER	# bracketrightex (CUS)
  0xF8FB, // RIGHT SQUARE BRACKET BOTTOM	# bracketrightbt (CUS)0
  0xF8FA, // glue = RIGHT SQUARE BRACKET EXTENDER	# bracketrightex (CUS)

  //------------
  //index = 30
  //Section for: left curly bracket
  //Block of different variants of *increasing* size...	
  0x007B, // LEFT CURLY BRACKET	# braceleft

  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  0xF8F1, // LEFT CURLY BRACKET TOP	# bracelefttp (CUS)
  0xF8F2, // LEFT CURLY BRACKET MID	# braceleftmid (CUS)
  0xF8F3, // LEFT CURLY BRACKET BOTTOM	# braceleftbt (CUS)
  0xF8F4, // glue = CURLY BRACKET EXTENDER	# braceex (CUS)

  //------------
  //index = 36
  //Section for: right square bracket
  //Block of different variants of *increasing* size...	
  0x007D, // RIGHT CURLY BRACKET	# braceright

  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  0xF8FC, // RIGHT CURLY BRACKET TOP	# bracerighttp (CUS)
  0xF8FD, // RIGHT CURLY BRACKET MID	# bracerightmid (CUS)
  0xF8FE, // RIGHT CURLY BRACKET BOTTOM	# bracerightbt (CUS)
  0xF8F4, // glue = CURLY BRACKET EXTENDER	# braceex (CUS)

  //------------
  //index = 42
  //Section for: down arrow
  //Block of different variants of *increasing* size...	
  0x2193, // DOWNWARDS ARROW    # arrowdown

  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  0xF8E6, // VERTICAL ARROW EXTENDER	# arrowvertex (CUS)
  0xF8E6, // VERTICAL ARROW EXTENDER	# arrowvertex (CUS)
  0x2193, // DOWNWARDS ARROW    # arrowdown
  0xF8E6, // glue = VERTICAL ARROW EXTENDER	# arrowvertex (CUS)

  //------------
  //index = 48
  //Section for: up arrow
  //Block of different variants of *increasing* size...	
  0x2191, // UPWARDS ARROW	# arrowup

  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  0x2191, // UPWARDS ARROW	# arrowup
  0xF8E6, // VERTICAL ARROW EXTENDER	# arrowvertex (CUS)
  0xF8E6, // VERTICAL ARROW EXTENDER	# arrowvertex (CUS)
  0xF8E6, // glue = VERTICAL ARROW EXTENDER	# arrowvertex (CUS)

  //------------
  //index = 54
  //Section for: left arrow
  //Block of different variants of *increasing* size...	
  0x2190, // LEFTWARDS ARROW	# arrowleft

  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  0x2190, // LEFTWARDS ARROW	# arrowleft
  0xF8E7, // HORIZONTAL ARROW EXTENDER	# arrowhorizex (CUS)
  0xF8E7, // HORIZONTAL ARROW EXTENDER	# arrowhorizex (CUS)
  0xF8E7, // glue = HORIZONTAL ARROW EXTENDER	# arrowhorizex (CUS)
 
  //------------
  //index = 60
  //Section for: right arrow
  //Block of different variants of *increasing* size...	
  0x2192, // RIGHTWARDS ARROW	# arrowright

  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  0xF8E7, // HORIZONTAL ARROW EXTENDER	# arrowhorizex (CUS)
  0xF8E7, // HORIZONTAL ARROW EXTENDER	# arrowhorizex (CUS)
  0x2192, // RIGHTWARDS ARROW	# arrowright
  0xF8E7, // glue = HORIZONTAL ARROW EXTENDER	# arrowhorizex (CUS) 
  
  //------------
  //index = 66
  //Section for: square-root bar
  0x00AF, // RADICAL EXTENDER	# radicalex (CUS) -- formerly 0xF8E5

  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  0x00AF, // RADICAL EXTENDER	# radicalex (CUS)
  0x00AF, // RADICAL EXTENDER	# radicalex (CUS)
  0x00AF, // RADICAL EXTENDER	# radicalex (CUS)
  0x00AF, // RADICAL EXTENDER	# radicalex (CUS)

  //------------
  //index = 72
  //Section for: vertical bar (0x2223)
  '|',

  0x0000, // That's all folks ...
  //Block of partial glyphs in the order top/left, middle, bottom/right, glue
  '|',
  '|',
  '|',
  '|',

  //------------
  //index = 78
  //end of table ...
  0x0000, 
};

// Pointers to the beginning of each section
static PRInt32 gMathMLCharIndex[] = {
   0,  // eMathMLChar_LeftParenthesis
   6,  // eMathMLChar_RightParenthesis
  12,  // eMathMLChar_Integral
  18,  // eMathMLChar_LeftSquareBracket
  24,  // eMathMLChar_RightSquareBracket
  30,  // eMathMLChar_LeftCurlyBracket
  36,  // eMathMLChar_RightCurlyBracket
  42,  // eMathMLChar_DownArrow, 
  48,  // eMathMLChar_UpArrow, 
  54,  // eMathMLChar_LeftArrow, 
  60,  // eMathMLChar_RightArrow, 
  66,  // eMathMLChar_OverBar, 
  72,  // eMathMLChar_VerticalBar, 
  78,  // eMathMLChar_Sqrt,  -- placeholder for now
  78,  // safeguard, *must* always point at the *end* of gMathMLCharGlyph[]  
};

static const PRInt32 kMathMLChar[] = {
#define MATHML_CHAR(_name, _value, _direction) eMathMLChar_##_name, _value, _direction,
#include "nsMathMLCharList.h"
#undef MATHML_CHAR
};


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

// If you call SetData(), it will lookup the enum
// of the char and set it for you.
void
nsMathMLChar::SetData(nsString& aData) {
  mData = aData;
  // some assumptions until proven otherwise!
  mEnum = eMathMLChar_DONT_STRETCH;
  mDirection = NS_STRETCH_DIRECTION_UNSUPPORTED;
  mBoundingMetrics.Clear();
  // lookup the enum ...
  if (1 == mData.Length()) {
    PRUnichar ch = mData[0];
    PRInt32 count = sizeof(kMathMLChar) / sizeof(kMathMLChar[0]);
    for (PRInt32 i = 0; i < count; i += 3) {
      if (ch == kMathMLChar[i+1]) {
        mEnum = nsMathMLCharEnum(kMathMLChar[i]);
        mDirection = kMathMLChar[i+2];
        break;
      }
    }
  }
}

// If you call SetEnum(), it will lookup the actual value  
// of the char and set it for you.
void
nsMathMLChar::SetEnum(nsMathMLCharEnum aEnum) {
  mEnum = aEnum;
  // some assumptions until proven otherwise!
  mData = "";
  mDirection = NS_STRETCH_DIRECTION_UNSUPPORTED;
  mBoundingMetrics.Clear();
  // lookup the data ...
  if (mEnum != eMathMLChar_DONT_STRETCH) {
    PRInt32 count = sizeof(kMathMLChar) / sizeof(kMathMLChar[0]);
    for (PRInt32 i = 0; i < count; i += 3) {
      if (mEnum == kMathMLChar[i]) {
        mData = PRUnichar(kMathMLChar[i+1]);
        mDirection = kMathMLChar[i+2];
        break;
      }
    }
  }
  NS_ASSERTION(mData.Length(), "Something is wrong somewhere");
}

/*
 The Stretch:
 @param aContainerSize - suggested size for the stretched char
 @param aDesiredStretchSize - IN/OUT parameter. On input
 our current size, on output the size after stretching.

 How it works?
 The Stretch() method first looks for a glyph of appropriate
 size; If a glyph is found, it is cached by this object 
 and its size is returned in aDesiredStretchSize. The cached
 glyph will then be used at the painting stage.

 If no glyph of appropriate size is found, aDesiredStretchSize
 is set to aContainerSize and the char is later built by 
 parts, at the painting stage, to fit the size.

 * When a glyph is found, its size can actually be slighthly
   larger or smaller than aContainerSize.

 * When built by parts, the char is stretched to *exactly* 
   fit aContainerSize. [Is this what we want? Can we leave
   the leading?]

 In either case, it is the responsibility of the caller
 to account for the spacing when setting aContainerSize, and
 to leave the extra spacing when placing the stretched char.
*/

//#define SHOW_BORDERS 1

// 
NS_IMETHODIMP
nsMathMLChar::Stretch(nsIPresContext*      aPresContext,
                      nsIRenderingContext& aRenderingContext,
                      nsIStyleContext*     aStyleContext,
                      nsStretchDirection   aStretchDirection,
                      nsStretchMetrics&    aContainerSize,
                      nsStretchMetrics&    aDesiredStretchSize)
{
  nsresult rv = NS_OK;
  nsStretchDirection aDirection = aStretchDirection;

  if (aDirection == NS_STRETCH_DIRECTION_DEFAULT) {
    aDirection = mDirection;
  }

  // Set font
  nsStyleFont font;
  aStyleContext->GetStyle(eStyleStruct_Font, font);
  aRenderingContext.SetFont(font.mFont);

  // cache our bounding metrics and quickly return if there is nothing
  // special about this char
  if (eMathMLChar_DONT_STRETCH == mEnum || aDirection != mDirection) {
    rv = aRenderingContext.GetBoundingMetrics(mData.GetUnicode(), 
                                              PRUint32(mData.Length()), 
                                              mBoundingMetrics);
    if (NS_FAILED(rv)) { printf("GetBoundingMetrics failed for %04X:%c\n", mData[0], mData[0]&0x00FF); /*getchar();*/ return rv; }
    return NS_OK;
  }

  // check the common situations where stretching is not actually needed
  // XXX need better criteria
  if (aDirection == NS_STRETCH_DIRECTION_VERTICAL) {
    if (aContainerSize.height <= aDesiredStretchSize.height) {
      mEnum = eMathMLChar_DONT_STRETCH; // ensure that the char later behaves like a normal char
      rv = aRenderingContext.GetBoundingMetrics(mData.GetUnicode(), 
                                                PRUint32(mData.Length()), 
                                                mBoundingMetrics);
      if (NS_FAILED(rv)) { printf("GetBoundingMetrics failed for %04X:%c\n", mData[0], mData[0]&0x00FF); /*getchar();*/ return rv; }
      return NS_OK;
    }
  }
  else if (aDirection == NS_STRETCH_DIRECTION_HORIZONTAL) {
    if (aContainerSize.width <= aDesiredStretchSize.width) {
      mEnum = eMathMLChar_DONT_STRETCH; // ensure that the char later behaves like a normal char
      rv = aRenderingContext.GetBoundingMetrics(mData.GetUnicode(), 
                                                PRUint32(mData.Length()), 
                                                mBoundingMetrics);
      if (NS_FAILED(rv)) { printf("GetBoundingMetrics failed for %04X:%c\n", mData[0], mData[0]&0x00FF); /*getchar();*/ return rv; }
      return NS_OK;
    }
  }

//  printf("Container height:%d  width:%d  ascent:%d  descent:%d\n contains height:%d  width:%d  ascent:%d  descent:%d\n",
//         aContainerSize.height, aContainerSize.width, aContainerSize.ascent, aContainerSize.descent,
//         aDesiredStretchSize.height, aDesiredStretchSize.width, aDesiredStretchSize.ascent, 
//         aDesiredStretchSize.descent);

  // XXX Note: there are other symbols that just need to slightly
  //           increase their size, like \Sum

  nscoord height = aDesiredStretchSize.height;
  nscoord width = aDesiredStretchSize.width;
  nscoord ascent = aDesiredStretchSize.ascent; 
  nscoord descent = aDesiredStretchSize.descent;
  
  PRInt32 index = gMathMLCharIndex[mEnum];
  PRInt32 limit = gMathMLCharIndex[mEnum+1]; // next char starts here...
  nscoord w, h;

  // try first to see if there is a glyph of appropriate size ...
  PRBool sizeOK = PR_FALSE; 
  PRUnichar ch = gMathMLCharGlyph[index++];
  nsBoundingMetrics bm, bm0;

  PRInt32 index0 = index;
  while (ch && index <= limit) {
    // printf("Checking size of:%c  index:%d\n", ch & 0x00FF, index);
    rv = aRenderingContext.GetBoundingMetrics(&ch, PRUint32(1), bm);
    if (NS_FAILED(rv)) { printf("GetBoundingMetrics failed for %04X:%c\n", ch, ch&0x00FF); /*getchar();*/ return rv; }
    // keep track of the default bounding metrics
    if (index0 == index) bm0 = bm;
    
    h = bm.ascent + bm.descent;
    w = bm.rightBearing - bm.leftBearing;
 
    // printf("height:%d  width:%d  ascent:%d  descent:%d\n", 
    //         height, bm.width, bm.ascent, bm.descent);

    // XXX temp hack -- need better criteria
    if ((aDirection == NS_STRETCH_DIRECTION_VERTICAL && h > height) || 
        (aDirection == NS_STRETCH_DIRECTION_HORIZONTAL && w > width)) {
      sizeOK = PR_TRUE;
      descent = bm.descent;
      ascent = bm.ascent;
      height = bm.ascent + bm.descent;
      width = bm.rightBearing - bm.leftBearing;

      // cache all this information
      mGlyph = ch;
      mBoundingMetrics = bm;
      break;
    }
    ch = gMathMLCharGlyph[index++];
  }

  if (!sizeOK) { // see if we can build by parts...
    mGlyph = 0;
    PRInt32 i;
    nscoord a, d;
    h = w = a = d = 0;
    float flex[3] = {0.7f, 0.3f, 0.7f}; // XXX hack!

    // get metrics data to be re-used later
    nsBoundingMetrics bmdata[4];
    for (i = 0; i < 4; i++) {
      ch = gMathMLCharGlyph[index+i];
      rv = aRenderingContext.GetBoundingMetrics(&ch, PRUint32(1), bm);
      if (NS_FAILED(rv)) { 
        printf("GetBoundingMetrics failed for %04X:%c\n", ch, ch&0x00FF); /*getchar();*/
        return rv; 
      }
      bmdata[i] = bm;
    }

    if (aDirection == NS_STRETCH_DIRECTION_VERTICAL) {
      // default is to fill-up the area given to us
      width = aDesiredStretchSize.width;
      height = aContainerSize.height;
      ascent = aContainerSize.ascent;
      descent = aContainerSize.descent;

      for (i = 0; i < 4; i++) {
        bm = bmdata[i];
        if (w < bm.width) w = bm.width;
        if (i < 3) {
          h += nscoord(flex[i]*(bm.ascent+bm.descent)); // sum heights of the parts...
          // compute and cache our bounding metrics (vertical stacking!)
          if (mBoundingMetrics.leftBearing > bm.leftBearing)
            mBoundingMetrics.leftBearing = bm.leftBearing;
          if (mBoundingMetrics.rightBearing < bm.rightBearing)
            mBoundingMetrics.rightBearing = bm.rightBearing;
        }
      }
      if (h <= aContainerSize.height) { // can nicely fit in the available space...
        width = w;
        mBoundingMetrics.width = w;
        mBoundingMetrics.ascent = aContainerSize.ascent;
        mBoundingMetrics.descent = aContainerSize.descent;
      } else { // sum of parts doesn't fit in the space... will use a single glyph
        mEnum = eMathMLChar_DONT_STRETCH; // ensure that the char behaves like a normal char
        mBoundingMetrics = bm0;
        return NS_OK;
      }
    }
    else if (aDirection == NS_STRETCH_DIRECTION_HORIZONTAL) {
      // default is to fill-up the area given to us
      width = aContainerSize.width;
      height = aDesiredStretchSize.height;
      ascent = aDesiredStretchSize.ascent;
      descent = aDesiredStretchSize.descent;

      for (i = 0; i < 4; i++) {
      	bm = bmdata[i];
        if (0 == i) bm0 = bm;
        if (a < bm.ascent) a = bm.ascent;
        if (d < bm.descent) d = bm.descent;
        if (i < 3) {
          w += nscoord(flex[i]*(bm.rightBearing-bm.leftBearing)); // sum widths of the parts...
          // compute and cache our bounding metrics (horizontal stacking)
          if (0 == i)
            mBoundingMetrics  = bm;
          else
            mBoundingMetrics += bm;
        }
      }
      if (w <= aContainerSize.width) { // can nicely fit in the available space...
//printf("%04X can nicely fit in the available space...\n", ch);
//        ascent = a;
//        height = a + d;
        mBoundingMetrics.width = aContainerSize.width;
        mBoundingMetrics.leftBearing = 0;
        mBoundingMetrics.rightBearing = aContainerSize.width;
      } else { // sum of parts doesn't fit in the space... will use a single glyph
        mEnum = eMathMLChar_DONT_STRETCH; // ensure that the char behaves like a normal char
        mBoundingMetrics = bm0;
        return NS_OK;
      }
    }
  }

  aDesiredStretchSize.width = width;
  aDesiredStretchSize.height = height;
  aDesiredStretchSize.ascent = ascent;
  aDesiredStretchSize.descent = descent;

  return NS_OK;
}

NS_IMETHODIMP
nsMathMLChar::Paint(nsIPresContext*      aPresContext,
                    nsIRenderingContext& aRenderingContext,
                    nsIStyleContext*     aStyleContext)
{
  nsresult rv = NS_OK;

  nsStyleFont font;
  nsStyleColor color;
  aStyleContext->GetStyle(eStyleStruct_Font, font);
  aStyleContext->GetStyle(eStyleStruct_Color, color);

  // Set color and font ...
  aRenderingContext.SetColor(color.mColor);
  aRenderingContext.SetFont(font.mFont);

  if (eMathMLChar_DONT_STRETCH == mEnum || NS_STRETCH_DIRECTION_UNSUPPORTED == mDirection) {
    // normal drawing if there is nothing special about this char ...
    aRenderingContext.DrawString(mData.GetUnicode(), PRUint32(mData.Length()), mRect.x, mRect.y);
//printf("Painting %04X like a normal char\n", mData[0]);
  }
  else if (0 < mGlyph) { // wow, there is a glyph of appropriate size!
//printf("Painting %04X with a glyph of appropriate size\n", mData[0]);
    aRenderingContext.DrawString(&mGlyph, PRUint32(1), mRect.x, mRect.y);
  }
  else { // paint by parts
    // grab some metrics that will help to adjust the placements ...
    nscoord fontAscent, fontDescent;
    nsCOMPtr<nsIFontMetrics> fm;
    aRenderingContext.GetFontMetrics(*getter_AddRefs(fm));
    fm->GetMaxAscent(fontAscent);
    fm->GetMaxDescent(fontDescent);
    // do the painting ...
    if (NS_STRETCH_DIRECTION_VERTICAL == mDirection)
      return PaintVertically(aPresContext, aRenderingContext, aStyleContext,
                             fontAscent, fontDescent, mEnum, mRect);
    else if (NS_STRETCH_DIRECTION_HORIZONTAL == mDirection)
      return PaintHorizontally(aPresContext, aRenderingContext, aStyleContext,
                               fontAscent, fontDescent, mEnum, mRect);
  }
  return NS_OK;
}

/* ================================================================================= 
  And now the helper routines that actually do the job of painting the char by parts
 */

// draw a glyph in a clipped area so that we don't have hairy chars pending outside
void
nsMathMLChar::DrawGlyph(nsIRenderingContext& aRenderingContext, 
                        PRUnichar            aGlyph, 
                        nscoord              aX,
                        nscoord              aY,
                        nsRect&              aClipRect) 
{
  PRBool clipState;
  aRenderingContext.PushState();
  aRenderingContext.SetClipRect(aClipRect, nsClipCombine_kIntersect, clipState);
  aRenderingContext.DrawString(&aGlyph, PRUint32(1), aX, aY);
  aRenderingContext.PopState(clipState);
}

// paint a stretchy char by assembling glyphs vertically
nsresult
nsMathMLChar::PaintVertically(nsIPresContext*      aPresContext,
                              nsIRenderingContext& aRenderingContext,
                              nsIStyleContext*     aStyleContext,
                              nscoord              fontAscent,
                              nscoord              fontDescent,
                              nsMathMLCharEnum     aCharEnum,
                              nsRect               aRect)
{
  nsresult rv = NS_OK;
  PRInt32 index = gMathMLCharIndex[aCharEnum];
  PRInt32 limit = gMathMLCharIndex[aCharEnum+1]; // next char starts here...
  // jump past the glyphs of various size...
  PRUnichar ch = gMathMLCharGlyph[index++];
  while (ch && index <= limit) {
    ch = gMathMLCharGlyph[index++];
  }
  // return if there are no partial glyphs (an erroneous table!)
  if (!gMathMLCharGlyph[index]) { printf("Erroneous gMathMLCharGlyph table!\n"); return NS_OK; }

  nscoord dx = aRect.x;
  nscoord dy = aRect.y;
  nsRect clipRect;

  float p2t;
  aPresContext->GetScaledPixelsToTwips(&p2t);
  nscoord onePixel = NSIntPixelsToTwips(1, p2t); 

  // get metrics data to be re-used later
  PRInt32 i;
  nsBoundingMetrics bm, bmdata[4];
  nscoord stride, offset[3], start[3], end[3];
  for (i = 0; i < 4; i++) {
    ch = gMathMLCharGlyph[index+i];
    rv = aRenderingContext.GetBoundingMetrics(&ch, PRUint32(1), bm);
    if (NS_FAILED(rv)) { printf("GetBoundingMetrics failed for %04X:%c\n", ch, ch&0x00FF); /*getchar();*/ return rv; }
    bmdata[i] = bm;
//    printf("bm for i:%d ch:%04X ascent:%d descent:%d lbearing:%d rbearing:%d width:%d fontAscent:%d fontDescent:%d\n",
//           i, ch, bm.ascent, bm.descent, bm.leftBearing, bm.rightBearing, bm.width, fontAscent, fontDescent);
  }
  for (i = 0; i < 3; i++) {
    ch = gMathMLCharGlyph[index+i];
    bm = bmdata[i];
//printf("Drawing i=%d ch=%04X", i, ch); getchar();
    if (0 == i) { // top
      //coord with line-spacing (i.e., leading)
      //  dy = aRect.y;

      //coord for exact fit (i.e., no leading)
      dy = aRect.y + bm.ascent - fontAscent;
    }
    else if (1 == i) { // middle
      // coord with line-spacing (i.e., leading)
      //  dy = aRect.y + (aRect.height - fontAscent - fontDescent
      //  - bmdata[0].ascent - bmdata[2].descent + bm.ascent + bm.descent)/2;

      // coord for exact fit (i.e., no leading)
      dy = aRect.y + (aRect.height - (bm.ascent + bm.descent))/2
                   - (fontAscent - bm.ascent);
    }
    else if (2 == i) { // bottom
      // coord with line-spacing (i.e., leading)
      // dy = aRect.y + aRect.height - (fontAscent + fontDescent);

      // coord for exact fit (i.e., no leading)
      dy = aRect.y + aRect.height - (fontAscent + bm.descent);
    }
    clipRect = nsRect(dx, dy, aRect.width, aRect.height);
    // abcissa that DrawString used
    offset[i] = dy;
    // *exact* abcissa where the *top-most* pixel of the glyph is painted
    start[i] = dy + fontAscent - bm.ascent; 
    // *exact* abcissa where the *bottom-most* pixel of the glyph is painted
    end[i] = dy + fontAscent + bm.descent; // note: end = start + height
  }

  /////////////////////////////////////
  // draw top, middle, bottom
  for (i = 0; i < 3; i++) {
    ch = gMathMLCharGlyph[index+i];
#ifdef SHOW_BORDERS
    aRenderingContext.SetColor(NS_RGB(0,0,0));
    aRenderingContext.DrawRect(nsRect(dx,start[i],aRect.width+30*(i+1),end[i]-start[i]));
#endif
    dy = offset[i];
         if (i==0) clipRect = nsRect(dx, dy, aRect.width, aRect.height);
    else if (i==1) clipRect = nsRect(dx, end[0], aRect.width, start[2]-end[0]);
    else if (i==2) clipRect = nsRect(dx, start[2], aRect.width, end[2]-start[2]);

//    printf("Drawing i:%d ch:%04X ascent:%d descent:%d lbearing:%d rbearing:%d width:%d at position dx:%d dy:%d\n",
//           i, ch, bm.ascent, bm.descent, bm.leftBearing, bm.rightBearing, bm.width, dx, dy);
//    getchar();

    if (!clipRect.IsEmpty()) {
      clipRect.Inflate(onePixel, onePixel);
      DrawGlyph(aRenderingContext, ch, dx, dy, clipRect);
    }
  }

  ///////////////
  // fill the gap between top and middle, and between middle and bottom.
  ch = gMathMLCharGlyph[index+3]; // glue
  for (i = 0; i < 2; i++) {
    PRInt32 count = 0;
    dy = offset[i]; 
    clipRect = nsRect(dx, end[i], aRect.width, start[i+1]-end[i]);
    clipRect.Inflate(onePixel, onePixel);
#ifdef SHOW_BORDERS
    aRenderingContext.SetColor(NS_RGB(255,0,0));
    aRenderingContext.DrawRect(clipRect);
#endif
    bm = bmdata[i];
    while (dy + fontAscent + bm.descent < start[i+1]) {
      if (2 > count) {
        stride = bm.descent;
        bm = bmdata[3]; // glue
        stride += bm.ascent;
      }
      count++;
      dy += stride;
      DrawGlyph(aRenderingContext, ch, dx, dy, clipRect);
//    NS_ASSERTION(5000 == count, "Error - gMathMLCharGlyph is incorrectly set");
      if (1000 == count) return NS_ERROR_UNEXPECTED;
    }
#ifdef SHOW_BORDERS
    // last glyph that may cross past its boundary and collide with the next
    nscoord height = bm.ascent - bm.descent;
    aRenderingContext.SetColor(NS_RGB(0,255,0));
    aRenderingContext.DrawRect(nsRect(dx, dy+fontAscent-bm.ascent, aRect.width, height));
#endif
  }
  return NS_OK;	
}

// paint a stretchy char by assembling glyphs horizontally
nsresult
nsMathMLChar::PaintHorizontally(nsIPresContext*      aPresContext,
                                nsIRenderingContext& aRenderingContext,
                                nsIStyleContext*     aStyleContext,
                                nscoord              fontAscent,
                                nscoord              fontDescent,
                                nsMathMLCharEnum     aCharEnum,
                                nsRect               aRect)
{
  nsresult rv = NS_OK;
  PRInt32 index = gMathMLCharIndex[aCharEnum];
  PRInt32 limit = gMathMLCharIndex[aCharEnum+1]; // next char starts here...
  // jump past the glyphs of various size...
  PRUnichar ch = gMathMLCharGlyph[index++];
  while (ch && index <= limit) {
   ch = gMathMLCharGlyph[index++];
  }
  // return if there are no partial glyphs (an erroneous table!)
  if (!gMathMLCharGlyph[index]) { printf("Erroneous gMathMLCharGlyph table!\n"); return NS_OK; }

  nscoord dx = aRect.x;
  nscoord dy = aRect.y;
  nsRect clipRect;

  float p2t;
  aPresContext->GetScaledPixelsToTwips(&p2t);
  nscoord onePixel = NSIntPixelsToTwips(1, p2t); 

  // get metrics data to be re-used later
  PRInt32 i;
  nsBoundingMetrics bm, bmdata[4];
  nscoord stride, offset[3], start[3], end[3];
  for (i = 0; i < 4; i++) {
    ch = gMathMLCharGlyph[index+i];
    rv = aRenderingContext.GetBoundingMetrics(&ch, PRUint32(1), bm);
    if (NS_FAILED(rv)) { printf("GetBoundingMetrics failed for %04X:%c\n", ch, ch&0x00FF); /*getchar();*/ return rv; }
    bmdata[i] = bm;
//    printf("bm for i:%d ch:%04X ascent:%d descent:%d lbearing:%d rbearing:%d width:%d fontAscent:%d fontDescent:%d\n",
//           i, ch, bm.ascent, bm.descent, bm.leftBearing, bm.rightBearing, bm.width, fontAscent, fontDescent);
  }
  for (i = 0; i < 3; i++) {
    ch = gMathMLCharGlyph[index+i];
    bm = bmdata[i];
//printf("Drawing i=%d ch=%04X", i, ch); getchar();
    if (0 == i) { // left 
      // coord with intercharacter-spacing
      // dx = aRect.x;

      // coord for exact fit (no intercharacter-spacing)
      dx = aRect.x - bm.leftBearing;
    }
    else if (1 == i) { // middle
      // coord with intercharacter-spacing
      dx = aRect.x + (aRect.width - bm.width)/2;
    }
    else if (2 == i) { // right
      // coord with intercharacter-spacing
      // dx = aRect.x + aRect.width - bm.width;

      // coord for exact fit (no intercharacter-spacing)
      dx = aRect.x + aRect.width - bm.rightBearing;
    }
    // abcissa that DrawString used
    offset[i] = dx;
    // *exact* abcissa where the *left-most* pixel of the glyph is painted
    start[i] = dx + bm.leftBearing; 
    // *exact* abcissa where the *right-most* pixel of the glyph is painted
    end[i] = dx + bm.rightBearing; // note: end = start + width
  }

  ///////////////////////////
  // draw left, middle, right
  for (i = 0; i < 3; i++) {
    ch = gMathMLCharGlyph[index+i];
#ifdef SHOW_BORDERS
    aRenderingContext.SetColor(NS_RGB(0,0,0));
    aRenderingContext.DrawRect(nsRect(start[i], dy, end[i]-start[i], aRect.height+30*(i+1)));
#endif
    dx = offset[i];
    nscoord top = PR_MIN(dy, dy + fontAscent - bm.ascent);
         if (i==0) clipRect = nsRect(dx, top, aRect.width, aRect.height);
    else if (i==1) clipRect = nsRect(end[0], top, start[2]-end[0], aRect.height);
    else if (i==2) clipRect = nsRect(start[2], top, end[2]-start[2], aRect.height);

//  printf("Drawing i:%d ch:%04X ascent:%d descent:%d lbearing:%d rbearing:%d width:%d at position dx:%d dy:%d\n",
//         i, ch, bm.ascent, bm.descent, bm.leftBearing, bm.rightBearing, bm.width, dx, dy);
//  getchar();
    if (!clipRect.IsEmpty()) {
      clipRect.Inflate(onePixel, onePixel);
      DrawGlyph(aRenderingContext, ch, dx, dy, clipRect);
    }
  }

  ////////////////
  // fill the gap between left and middle, and between middle and right.
  ch = gMathMLCharGlyph[index+3]; // glue
  for (i = 0; i < 2; i++) {
    PRInt32 count = 0;
    dx = offset[i]; 
    nscoord top = PR_MIN(dy, dy + fontAscent - bm.ascent);
    clipRect = nsRect(end[i], top, start[i+1]-end[i], aRect.height);
    clipRect.Inflate(onePixel, onePixel);
#ifdef SHOW_BORDERS
    // rectangles in-between that are to be filled
    aRenderingContext.SetColor(NS_RGB(255,0,0));
    aRenderingContext.DrawRect(clipRect);
#endif
    bm = bmdata[i];
    while (dx + bm.rightBearing < start[i+1]) {
      if (2 > count) {
        stride = bm.rightBearing;
        bm = bmdata[3]; // glue
        stride -= bm.leftBearing;
      }
      count++;
      dx += stride;
      DrawGlyph(aRenderingContext, ch, dx, dy, clipRect);
//    NS_ASSERTION(5000 == count, "Error - gMathMLCharGlyph is incorrectly set");
      if (1000 == count) return NS_ERROR_UNEXPECTED;
//    printf("Drawing %04X ascent:%d descent:%d  at position dx:%d dy:%d\n",
//           ch, bm.ascent, bm.descent, dx, dy);
    }
#ifdef SHOW_BORDERS
    // last glyph that may cross past its boundary and collide with the next
    nscoord width = bm.rightBearing - bm.leftBearing;
    aRenderingContext.SetColor(NS_RGB(0,255,0));
    aRenderingContext.DrawRect(nsRect(dx + bm.leftBearing, dy, width, aRect.height));
#endif
  }
  return NS_OK;	
}

