/**************************************************************************

    HTML widget for xclass. Based on tkhtml 1.28
    Copyright (C) 1997-2000 D. Richard Hipp <drh@acm.org>
    Copyright (C) 2002-2003 Hector Peraza.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

**************************************************************************/

// This file contains the code used to position elements of the
// HTML file on the screen.

#include <stdlib.h>
#include <string.h>

#include <xclass/OXHtml.h>


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

OHtmlLayoutContext::OHtmlLayoutContext() {
  pStart = 0;
  pEnd = 0;
  leftMargin = 0;
  rightMargin = 0;
  Reset();
}

// Reset the layout context.

void OHtmlLayoutContext::Reset() {
  headRoom = 0;
  top = 0;
  bottom = 0;   
  ClearMarginStack(&leftMargin);
  ClearMarginStack(&rightMargin);
}


// Push a new margin onto the given margin stack.
//
// If the "bottom" parameter is non-negative, then this margin will
// automatically expire for all text that is placed below the y-coordinate
// given by "bottom". This feature is used for <IMG ALIGN=left> and <IMG
// ALIGN=right> kinds of markup. It allows text to flow around an image.
//
// If "bottom" is negative, then the margin stays in force until it is
// explicitly canceled by a call to PopMargin().
//
//  ppMargin - The margin stack onto which to push
//  indent   - The indentation for the new margin
//  bottom   - The margin expires at this Y coordinate
//  tag      - Markup that will cancel this margin

void OHtmlLayoutContext::PushMargin(SHtmlMargin **ppMargin,
                                    int indent, int bottom, int tag) {
  SHtmlMargin *pNew = new SHtmlMargin;
  pNew->pNext = *ppMargin;
  if (pNew->pNext) {
    pNew->indent = indent + pNew->pNext->indent;
  } else {
    pNew->indent = indent;
  }
  pNew->bottom = bottom;
  pNew->tag = tag;
  *ppMargin = pNew;
}


// Pop one margin off of the given margin stack.

void OHtmlLayoutContext::PopOneMargin(SHtmlMargin **ppMargin) {
  if (*ppMargin) {
    SHtmlMargin *pOld = *ppMargin;
    *ppMargin = pOld->pNext;
    delete pOld;
  }
}


// Pop as many margins as necessary until the margin that was
// created with "tag" is popped off. Update the layout context
// to move past obstacles, if necessary.
//
// If there are some margins on the stack that contain non-negative
// bottom fields, that means there are some obstacles that we have
// not yet cleared. If these margins get popped off the stack,
// then we have to be careful to advance the 'bottom' value so
// that the next line of text will clear the obstacle.

void OHtmlLayoutContext::PopMargin(SHtmlMargin **ppMargin, int tag) {
  int bot = -1;
  int oldTag;
  SHtmlMargin *pM;

  for (pM = *ppMargin; pM && pM->tag != tag; pM = pM->pNext) {}
  if (pM == 0) {
    // No matching margin is found. Do nothing.
    return;
  }
  while ((pM = *ppMargin) != 0) {
    if (pM->bottom > bot) bot = pM->bottom;
    oldTag = pM->tag;
    PopOneMargin(ppMargin);
    if (oldTag == tag) break;
  }
  if (bottom < bot) {
    headRoom += bot - bottom;
    bottom = bot;
  }
}


// Pop all expired margins from the stack. 
//
// An expired margin is one with a non-negative bottom parameter
// that is less than the value "y". "y" is the Y-coordinate of
// the top edge the next line of text to by positioned. What this
// function does is check to see if we have cleared any obstacles
// (an obstacle is an <IMG ALIGN=left> or <IMG ALIGN=right>) and
// expands the margins if we have.

void OHtmlLayoutContext::PopExpiredMargins(SHtmlMargin **ppMarginStack,
                                           int y) {
  while (*ppMarginStack && (**ppMarginStack).bottom >= 0 &&
         (**ppMarginStack).bottom <= y) {
    PopOneMargin(ppMarginStack);
  }
}


// Clear a margin stack to reclaim memory. This routine just blindly
// pops everything off the stack. Typically used when the screen is
// cleared or the widget is deleted, etc.

void OHtmlLayoutContext::ClearMarginStack(SHtmlMargin **ppMargin) {
  while (*ppMargin) PopOneMargin(ppMargin);
}


// This routine gathers as many tokens as will fit on one line.
//
// The candidate tokens begin with pStart and go thru the end of
// the list or to pEnd, whichever comes first. The first token
// at the start of the next line is returned. NULL is returned if
// we exhaust data.
//
// "width" is the maximum allowed width of the line. The actual
// width is returned in *actualWidth. The actual width does not
// include any trailing spaces. Sometimes the actual width will
// be greater than the maximum width. This will happen, for example,
// for text enclosed in <pre>..</pre> that has lines longer than
// the width of the page.
//
// If the list begins with text, at least one token is returned,
// even if that one token is longer than the allowed line length.
// But if the list begins with some kind of break markup (possibly
// preceded by white space) then the returned list may be empty.
//
// The "x" coordinates of all elements are set assuming that the line
// begins at 0. The calling routine should adjust these coordinates
// to position the line horizontally. (The FixLine() procedure does
// this.)  Note that the "x" coordinate of <li> elements will be negative.
// Text within <dt>..</dt> might also have a negative "x" coordinate.
// But in no case will the x coordinate every be less than "minX".
//
// p_start     - First token on new line
// p_end       - End of line. Might be NULL
// width       - How much space is on this line
// minX        - The minimum value of the X coordinate
// actualWidth - Return space actually required

OHtmlElement *OHtmlLayoutContext::GetLine(OHtmlElement *p_start,
                OHtmlElement *p_end, int width, int minX, int *actualWidth) {
  int x;                        // Current X coordinate
  int spaceWanted = 0;          // Add this much space before next token
  OHtmlElement *p;              // For looping over tokens
  OHtmlElement *lastBreak = 0;  // Last line-break opportunity
  int isEmpty = 1;              // True if link contains nothing
  int origin;                   // Initial value of "x"

  *actualWidth = 0;
  p = p_start;
  while (p && p != p_end && (p->style.flags & STY_Invisible) != 0) {
    p = p->pNext;
  }
  if (p && p->style.flags & STY_DT) {
    origin = -HTML_INDENT;
  } else {
    origin = 0;
  }
  x = origin;
  if (x < minX) x = minX;
  if (p && p != p_end && p->type == Html_LI) {
    OHtmlLi *li = (OHtmlLi *) p;
    li->x = x - HTML_INDENT / 3;
    if (li->x - (HTML_INDENT * 2) / 3 < minX) {
      x += minX - li->x + (HTML_INDENT * 2) / 3;
      li->x = minX + (HTML_INDENT * 2) / 3;
    }
    isEmpty = 0;
    *actualWidth = 1;
    p = p->pNext;
    while (p && (p->type == Html_Space || p->type == Html_P)) {
      p = p->pNext;
    }
  }
  for (; p && p != p_end; p = p ? p->pNext : 0) {
    if (p->style.flags & STY_Invisible) continue;
    switch (p->type) {
      case Html_Text: {
        OHtmlTextElement *text = (OHtmlTextElement *) p;
        text->x = x + spaceWanted;
        if ((p->style.flags & STY_Preformatted) == 0) {
          if (lastBreak && x + spaceWanted + text->w > width)
            return lastBreak;
        }
//        TRACE(HtmlTrace_GetLine2, ("Place token %s at x=%d w=%d\n",
//           HtmlTokenName(p), text->x, text->w));
        x += text->w + spaceWanted;
        isEmpty = 0;
        spaceWanted = 0;
        break;
      }

      case Html_Space: {
        OHtmlSpaceElement *space = (OHtmlSpaceElement *) p;
        if (p->style.flags & STY_Preformatted) {
          if (p->flags & HTML_NewLine) {
            *actualWidth = (x <= 0) ? 1 : x;
            return p->pNext;
          }
          x += space->w * p->count;
        } else {
          int w;
          if ((p->style.flags & STY_NoBreak) == 0) {
            lastBreak = p->pNext;
            *actualWidth = ((x <= 0) && !isEmpty) ? 1 : x;
          }
          w = space->w;
          if (spaceWanted < w && x > origin) spaceWanted = w;
        }
        break;
      }

      case Html_IMG: {
        OHtmlImageMarkup *image = (OHtmlImageMarkup *) p;
        switch (image->align) {
          case IMAGE_ALIGN_Left:
          case IMAGE_ALIGN_Right:
            *actualWidth = ((x <= 0) && !isEmpty) ? 1 : x;
            return p;
          default:
            break;
        }
        image->x = x + spaceWanted;
        if ((p->style.flags & STY_Preformatted) == 0) {
          if (lastBreak && x + spaceWanted + image->w > width)
            return lastBreak;
        }
//        TRACE(HtmlTrace_GetLine2, ("Place in-line image %s at x=%d w=%d\n",
//           HtmlTokenName(p), p->image.x, p->image.w));
        x += image->w + spaceWanted;
        if ((p->style.flags & STY_NoBreak) == 0) {
          lastBreak = p->pNext;
          *actualWidth = ((x <= 0) && !isEmpty) ? 1 : x;
        }
        spaceWanted = 0;
        isEmpty = 0;
        break;
      }

      case Html_APPLET:
      case Html_EMBED:
      case Html_INPUT:
      case Html_SELECT:
      case Html_TEXTAREA: {
        OHtmlInput *input = (OHtmlInput *) p;
        input->x = x + spaceWanted + input->padLeft;
        if ((p->style.flags & STY_Preformatted) == 0) {
          if (lastBreak && x + spaceWanted + input->w > width)
            return lastBreak;
        }
//        TRACE(HtmlTrace_GetLine2, ("Place token %s at x=%d w=%d\n",
//           HtmlTokenName(p), p->input.x, p->input.w));
        x = input->x + input->w;
        if ((p->style.flags & STY_NoBreak) == 0) {
          lastBreak = p->pNext;
          *actualWidth = ((x <= 0) && !isEmpty) ? 1 : x;
        }
        spaceWanted = 0;
        isEmpty = 0;
        break;
      }

      case Html_EndTEXTAREA: {
        OHtmlRef *ref = (OHtmlRef *) p;
        if (ref->pOther) {
          // html->ResetTextarea(ref->pOther);
        }
        break;
      }

      case Html_DD: {
        OHtmlRef *ref = (OHtmlRef *) p;
        if (ref->pOther == 0) break;
        if (((OHtmlListStart *)ref->pOther)->compact == 0 ||
            x + spaceWanted >= 0) {
          *actualWidth = ((x <= 0) && !isEmpty) ? 1 : x;
          return p;
        }
        x = 0;
        spaceWanted = 0;
        break;
      }

      case Html_WBR:
        *actualWidth = ((x <= 0) && !isEmpty) ? 1 : x;
        if (x + spaceWanted >= width) {
          return p->pNext;
        } else {
          lastBreak = p->pNext;
        }
        break;

      case Html_ADDRESS:
      case Html_EndADDRESS:
      case Html_BLOCKQUOTE:
      case Html_EndBLOCKQUOTE:
      case Html_BODY:
      case Html_EndBODY:
      case Html_BR:
      case Html_CAPTION:
      case Html_EndCAPTION:
      case Html_CENTER:
      case Html_EndCENTER:
      case Html_EndDD:
      case Html_DIV:
      case Html_EndDIV:
      case Html_DL:
      case Html_EndDL:
      case Html_DT:
      case Html_H1:
      case Html_EndH1:
      case Html_H2:
      case Html_EndH2:
      case Html_H3:
      case Html_EndH3:
      case Html_H4:
      case Html_EndH4:
      case Html_H5:
      case Html_EndH5:
      case Html_H6:
      case Html_EndH6:
      case Html_EndHTML:
      case Html_HR:
      case Html_LI:
      case Html_LISTING:
      case Html_EndLISTING:
      case Html_MENU:
      case Html_EndMENU:
      case Html_OL:
      case Html_EndOL:
      case Html_P:
      case Html_EndP:
      case Html_PRE:
      case Html_EndPRE:
      case Html_TABLE:
      case Html_EndTABLE:
      case Html_TD:
      case Html_EndTD:
      case Html_TH:
      case Html_EndTH:
      case Html_TR:
      case Html_EndTR:
      case Html_UL:
      case Html_EndUL:
      case Html_EndFORM:
        *actualWidth = ((x <= 0) && !isEmpty) ? 1 : x;
        return p;

      default:
        break;
    }
  }
  *actualWidth = ((x <= 0) && !isEmpty) ? 1 : x;

  return p;
}


// Set the y coordinate for every anchor in the given list

void OHtmlLayoutContext::FixAnchors(OHtmlElement *p, OHtmlElement *p_end,
                                    int y) {
  while (p && p != p_end) {
    if (p->type == Html_A) ((OHtmlAnchor *)p)->y = y;
    p = p->pNext;
  }
}


// This routine computes the X and Y coordinates for all elements of
// a line that has been gathered using GetLine() above. It also figures
// the ascent and descent for in-line images.
//
// The value returned is the Y coordinate of the bottom edge of the
// new line. The X coordinates are computed by adding the left margin
// plus any extra space needed for centering or right-justification.
//
// p_start     - Start of tokens for this line
// p_end       - First token past end of this line. Maybe NULL
// bottom      - Put the top of this line here
// width       - This is the space available to the line
// actualWidth - This is the actual width needed by the line
// leftMargin  - The current left margin
// max_x       - Write maximum X coordinate of ink here

int OHtmlLayoutContext::FixLine(OHtmlElement *p_start,
               OHtmlElement *p_end, int bottom, int width,
               int actualWidth, int leftMargin, int *max_x) {
  int dx;                // Amount by which to increase all X coordinates
  int maxAscent;         // Maximum height above baseline
  int maxTextAscent;     // Maximum height above baseline for text
  int maxDescent;        // Maximum depth below baseline
  int ascent, descent;   // Computed ascent and descent for one element
  OHtmlElement *p;       // For looping
  int y;                 // Y coordinate of the baseline
  int dy2center;         // Distance from baseline to text font center
  int max = 0; 

  if (actualWidth > 0) {
    for (p = p_start; p && p != p_end && p->type != Html_Text; p = p->pNext) {}
    if (p == p_end || p == 0) p = p_start;
    maxAscent = maxTextAscent = 0;
    for (p = p_start; p && p != p_end; p = p->pNext) {
      int ss;
      if (p->style.align == ALIGN_Center) {
        dx = leftMargin + (width - actualWidth) / 2;
      } else if (p->style.align == ALIGN_Right) {
        dx = leftMargin + (width - actualWidth);
      } else {
        dx = leftMargin;
      }
      if (dx < 0) dx = 0;
      if (p->style.flags & STY_Invisible) continue;
      switch (p->type) {
        case Html_Text: {
          OHtmlTextElement *text = (OHtmlTextElement *) p;
          text->x += dx;
          max = text->x + text->w;
          ss = p->style.subscript;
          if (ss > 0) {
            int ascent = text->ascent;
            int delta = (ascent + text->descent) * ss / 2;
            ascent += delta;
            text->y = -delta;
            if (ascent > maxAscent) maxAscent = ascent;
            if (ascent > maxTextAscent) maxTextAscent = ascent;
          } else if (ss < 0) {
            int descent = text->descent;
            int delta = (descent + text->ascent) * (-ss) / 2;
            descent += delta;
            text->y = delta;
          } else {
            text->y = 0;
            if (text->ascent > maxAscent) maxAscent = text->ascent;
            if (text->ascent > maxTextAscent) maxTextAscent = text->ascent;
          }
          break;
        }

        case Html_Space: {
          OHtmlSpaceElement *space = (OHtmlSpaceElement *) p;
          if (space->ascent > maxAscent) maxAscent = space->ascent;
          break;
        }

        case Html_LI: {
          OHtmlLi *li = (OHtmlLi *) p;
          li->x += dx;
          if (li->x > max) max = li->x; 
          break;
        }

        case Html_IMG: {
          OHtmlImageMarkup *image = (OHtmlImageMarkup *) p;
          image->x += dx;
          max = image->x + image->w;
          switch (image->align) {
            case IMAGE_ALIGN_Middle:
              image->descent = image->h / 2;
              image->ascent = image->h - image->descent;
              if (image->ascent > maxAscent) maxAscent = image->ascent;
              break;

            case IMAGE_ALIGN_AbsMiddle:
              dy2center = (image->textDescent - image->textAscent) / 2;
              image->descent = image->h / 2 + dy2center;
              image->ascent = image->h - image->descent;
              if (image->ascent > maxAscent) maxAscent = image->ascent;
              break;

            case IMAGE_ALIGN_Bottom:
              image->descent = 0;
              image->ascent = image->h;
              if (image->ascent > maxAscent) maxAscent = image->ascent;
              break;

            case IMAGE_ALIGN_AbsBottom:
              image->descent = image->textDescent;
              image->ascent = image->h - image->descent;
              if (image->ascent > maxAscent) maxAscent = image->ascent;
              break;

            default:
              break;
          }
          break;
        }

        case Html_TABLE:
          break;

        case Html_TEXTAREA:
        case Html_INPUT:
        case Html_SELECT:
        case Html_EMBED:
        case Html_APPLET: {
          OHtmlInput *input = (OHtmlInput *) p;
          input->x += dx;
          max = input->x + input->w;
          dy2center = (input->textDescent - input->textAscent) / 2;
          input->y = dy2center - input->h / 2;
          ascent = -input->y;
          if (ascent > maxAscent) maxAscent = ascent;
          break;
        }

        default:
          // Shouldn't happen
          break;
      }
    }

    *max_x = max;
    y = maxAscent + bottom;
    maxDescent = 0;

    for (p = p_start; p && p != p_end; p = p->pNext) {
      if (p->style.flags & STY_Invisible) continue;
      switch (p->type) {
        case Html_Text: {
          OHtmlTextElement *text = (OHtmlTextElement *) p;
          text->y += y;
          if (text->descent > maxDescent) maxDescent = text->descent;
          break;
        }

        case Html_LI: {
          OHtmlLi *li = (OHtmlLi *) p;
          li->y = y;
          if (li->descent > maxDescent) maxDescent = li->descent;
          break;
        }

        case Html_IMG: {
          OHtmlImageMarkup *image = (OHtmlImageMarkup *) p;
          image->y = y;
          switch (image->align) {
            case IMAGE_ALIGN_Top:
              image->ascent = maxAscent;
              image->descent = image->h - maxAscent;
              break;

            case IMAGE_ALIGN_TextTop:
              image->ascent = maxTextAscent;
              image->descent = image->h - maxTextAscent;
              break;

            default:
              break;
          }
          if (image->descent > maxDescent) maxDescent = image->descent;
          break;
        }

        case Html_TABLE:
          break;

        case Html_INPUT:
        case Html_SELECT:
        case Html_TEXTAREA:
        case Html_APPLET:
        case Html_EMBED: {
          OHtmlInput *input = (OHtmlInput *) p;
          descent = input->y + input->h;
          input->y += y;
          if (descent > maxDescent) maxDescent = descent;
          break;
        }

        default:
          /* Shouldn't happen */
          break;
      }
    }

//    TRACE(HtmlTrace_FixLine, 
//       ("Setting baseline to %d. bottom=%d ascent=%d descent=%d dx=%d\n",
//       y, bottom, maxAscent, maxDescent, dx));

  } else {
    maxDescent = 0;
    y = bottom;
  }

  return y + maxDescent;
}


// Increase the headroom to create a paragraph break at the current token

void OHtmlLayoutContext::Paragraph(OHtmlElement *p) {
  int headroom;

  if (p == 0) return;

  if (p->type == Html_Text) {
    OHtmlTextElement *text = (OHtmlTextElement *) p;
    headroom = text->ascent + text->descent;
  } else if (p->pNext && p->pNext->type == Html_Text) {
    OHtmlTextElement *text = (OHtmlTextElement *) p->pNext;
    headroom = text->ascent + text->descent;
  } else {
    //// headroom = 10;
    OFontMetrics fontMetrics;
    OXFont *font;
    font = html->GetFont(p->style.font);
    if (font == 0) return;
    font->GetFontMetrics(&fontMetrics);
    headroom = fontMetrics.descent + fontMetrics.ascent;
  }
  if (headRoom < headroom && bottom > top) headRoom = headroom;
}


// Compute the current margins for layout. Three values are returned:
//
//    *pY       The top edge of the area in which we can put ink. This
//              takes into account any requested headroom.
//
//    *pX       The left edge of the inkable area. The takes into account
//              any margin requests active at vertical position specified
//              in pLC->bottom.
//
//    *pW       The width of the inkable area. This takes into account
//              an margin requests that are active at the vertical position
//              pLC->bottom.
//

void OHtmlLayoutContext::ComputeMargins(int *pX, int *pY, int *pW) {
  int x, y, w;

  y = bottom + headRoom;
  PopExpiredMargins(&leftMargin, bottom);
  PopExpiredMargins(&rightMargin, bottom);
  w = pageWidth - right;
  if (leftMargin) {
    x = leftMargin->indent + left;
  } else {
    x = left;
  }
  w -= x;
  if (rightMargin) w -= rightMargin->indent;

  *pX = x;
  *pY = y;
  *pW = w;
}


// Clear a wrap-around obstacle. The second option determines the
// precise behavior.
//
//    CLEAR_Left        Clear all obstacles on the left.
//
//    CLEAR_Right       Clear all obstacles on the right.
//
//    CLEAR_Both        Clear all obstacles on both sides.
//
//    CLEAR_First       Clear only the first obstacle on either side.

#define CLEAR_Left  0
#define CLEAR_Right 1
#define CLEAR_Both  2
#define CLEAR_First 3

void OHtmlLayoutContext::ClearObstacle(int mode) {
  int newBottom = bottom;

  PopExpiredMargins(&leftMargin, bottom);
  PopExpiredMargins(&rightMargin, bottom);

  switch (mode) {
    case CLEAR_Both:
      ClearObstacle(CLEAR_Left);
      ClearObstacle(CLEAR_Right);
      break;

    case CLEAR_Left:
      while (leftMargin && leftMargin->bottom >= 0) {
        if (newBottom < leftMargin->bottom)
          newBottom = leftMargin->bottom;
        PopOneMargin(&leftMargin);
      }
      if (newBottom > bottom + headRoom) {
        headRoom = 0;
      } else {
        headRoom = newBottom - bottom;
      }
      bottom = newBottom;
      PopExpiredMargins(&rightMargin, bottom);
      break;

    case CLEAR_Right:
      while (rightMargin && rightMargin->bottom >= 0) {
        if (newBottom < rightMargin->bottom)
          newBottom = rightMargin->bottom;
        PopOneMargin(&rightMargin);
      }
      if (newBottom > bottom + headRoom) {
        headRoom = 0;
      } else {
        headRoom = newBottom - bottom;
      }
      bottom = newBottom;
      PopExpiredMargins(&leftMargin, bottom);
      break;

    case CLEAR_First:
      if (leftMargin && leftMargin->bottom >= 0) {
        if (rightMargin &&
            rightMargin->bottom < leftMargin->bottom) {
          if (newBottom < rightMargin->bottom)
            newBottom = rightMargin->bottom;
          PopOneMargin(&rightMargin);
        } else {
          if (newBottom < leftMargin->bottom)
            newBottom = leftMargin->bottom;
          PopOneMargin(&leftMargin);
        }
      } else if (rightMargin && rightMargin->bottom >= 0) {
        newBottom = rightMargin->bottom;
        PopOneMargin(&rightMargin);
      }
      if (newBottom > bottom + headRoom) {
        headRoom = 0;
      } else {
        headRoom = newBottom - bottom;
      }
      bottom = newBottom;
      break;
  }
}


// Return the next markup type  [OHtmlElement::NextMarkupType]

int OXHtml::NextMarkupType(OHtmlElement *p) {
  while ((p = p->pNext))
    if (p->IsMarkup()) return p->type;
  return Html_Unknown;
}


// Break markup is any kind of markup that might force a line-break. This
// routine handles a single element of break markup and returns a pointer
// to the first element past that markup. If p doesn't point to break
// markup, then p is returned. If p is an incomplete table (a <TABLE>
// that lacks a </TABLE>), then NULL is returned.

OHtmlElement *OHtmlLayoutContext::DoBreakMarkup(OHtmlElement *p) {
  OHtmlElement *pNext = p->pNext;
  char *z;
  int x, y, w;

  switch (p->type) {
    case Html_A:
      ((OHtmlAnchor *)p)->y = bottom;
      break;

    case Html_BLOCKQUOTE:
      PushMargin(&leftMargin, HTML_INDENT, -1, Html_EndBLOCKQUOTE);
      PushMargin(&rightMargin, HTML_INDENT, -1, Html_EndBLOCKQUOTE);
      Paragraph(p);
      break;

    case Html_EndBLOCKQUOTE:
      PopMargin(&leftMargin, Html_EndBLOCKQUOTE);
      PopMargin(&rightMargin, Html_EndBLOCKQUOTE);
      Paragraph(p);
      break;

    case Html_IMG: {
      OHtmlImageMarkup *image = (OHtmlImageMarkup *) p;
      switch (image->align) {
        case IMAGE_ALIGN_Left:
          ComputeMargins(&x, &y, &w);
          image->x = x;
          image->y = y;
          image->ascent = 0;
          image->descent = image->h;
          PushMargin(&leftMargin, image->w + 2, y + image->h, 0);
          if (maxY < y + image->h) maxY = y + image->h;
          if (maxX < x + image->w) maxX = x + image->w;
          break;

        case IMAGE_ALIGN_Right:
          ComputeMargins(&x, &y, &w);
          image->x = x + w - image->w;
          image->y = y;
          image->ascent = 0;
          image->descent = image->h;
          PushMargin(&rightMargin, image->w + 2, y + image->h, 0);
          if (maxY < y + image->h) maxY = y + image->h;
          if (maxX < x + image->w) maxX = x + image->w;
          break;

        default:
          pNext = p;
          break;
      }
      break;
    }

    case Html_PRE:
      // Skip space tokens thru the next newline.
      while (pNext->type == Html_Space) {
        OHtmlElement *pThis = pNext;
        pNext = pNext->pNext;
        if (pThis->flags & HTML_NewLine) break;
      }
      Paragraph(p);
      break;

    case Html_UL:
    case Html_MENU:
    case Html_DIR:
    case Html_OL:
      if (((OHtmlListStart *)p)->compact == 0) Paragraph(p);
      PushMargin(&leftMargin, HTML_INDENT, -1, p->type + 1);
      break;

    case Html_EndOL:
    case Html_EndUL:
    case Html_EndMENU:
    case Html_EndDIR: {
      OHtmlRef *ref = (OHtmlRef *) p;
      if (ref->pOther) {
        PopMargin(&leftMargin, p->type);
        if (!((OHtmlListStart *)ref->pOther)->compact) Paragraph(p);
      }
      break;
    }

    case Html_DL:
      Paragraph(p);
      PushMargin(&leftMargin, HTML_INDENT, -1, Html_EndDL);
      break;

    case Html_EndDL:
      PopMargin(&leftMargin, Html_EndDL);
      Paragraph(p);
      break;

    case Html_HR: {
      int zl, wd;
      OHtmlHr *hr = (OHtmlHr *) p;
      hr->is3D = (p->MarkupArg("noshade", 0) == 0);
      z = p->MarkupArg("size", 0);
      if (z) {
        int hrsz = atoi(z);
        hr->h = (hrsz < 0) ? 2 : hrsz;
      } else {
        hr->h = 0;
      }
      if (hr->h < 1) {
        int relief = html->GetRuleRelief();
        if (hr->is3D &&
           (relief == HTML_RELIEF_SUNKEN || relief == HTML_RELIEF_RAISED)) {
          hr->h = 3;
        } else {
          hr->h = 2;
        }
      }
      ComputeMargins(&x, &y, &w);
      hr->y = y + html->GetRulePadding();
      y += hr->h + html->GetRulePadding() * 2 + 1;
      hr->x = x;
      z = p->MarkupArg("width", "100%");
      zl = strlen(z);
      if (zl > 0 && z[zl-1] == '%') {
        wd = (atoi(z) * w) / 100;
      } else {
        wd = atoi(z);
      }
      if (wd > w) wd = w;
      hr->w = wd;
      switch (p->style.align) {
        case ALIGN_Center:
        case ALIGN_None:
          hr->x += (w - wd) / 2;
          break;

        case ALIGN_Right:
          hr->x += (w - wd);
          break;

        default:
          break;
      }
      if (maxY < y) maxY = y;
      if (maxX < wd + hr->x) maxX = wd + hr->x;
      bottom = y;
      headRoom = 0;
      break;
    }

    case Html_ADDRESS:
    case Html_EndADDRESS:
    case Html_CENTER:
    case Html_EndCENTER:
    case Html_DIV:
    case Html_EndDIV:
    case Html_H1:
    case Html_EndH1:
    case Html_H2:
    case Html_EndH2:
    case Html_H3:
    case Html_EndH3:
    case Html_H4:
    case Html_EndH4:
    case Html_H5:
    case Html_EndH5:
    case Html_H6:
    case Html_EndH6:
    case Html_P:
    case Html_EndP:
    case Html_EndPRE:
    case Html_EndFORM:
      Paragraph(p);
      break;

    case Html_TABLE:
      pNext = TableLayout((OHtmlTable *) p);
      break;

    case Html_BR:
      z = p->MarkupArg("clear",0);
      if (z) {
        if (strcasecmp(z, "left") == 0) {
          ClearObstacle(CLEAR_Left);
        } else if (strcasecmp(z, "right") == 0) {
          ClearObstacle(CLEAR_Right);
        } else {
          ClearObstacle(CLEAR_Both);
        }
      }
      if (p->pNext && p->pNext->pNext && p->pNext->type == Html_Space &&
          p->pNext->pNext->type == Html_BR)
        Paragraph(p);
      break;

    // All of the following tags need to be handed to the GetLine() routine
    case Html_Text:
    case Html_Space:
    case Html_LI:
    case Html_INPUT:
    case Html_SELECT:
    case Html_TEXTAREA:
    case Html_APPLET:
    case Html_EMBED:
      pNext = p;
      break;

    default:
      break;
  }

  return pNext;
}


// Return TRUE (non-zero) if we are currently wrapping text around
// one or more images.

int OHtmlLayoutContext::InWrapAround() {
  if (leftMargin && leftMargin->bottom >= 0) return 1;
  if (rightMargin && rightMargin->bottom >= 0) return 1;
  return 0;
}


// Move past obstacles until a linewidth of reqWidth is obtained,
// or until all obstacles are cleared.
//
// reqWidth   - Requested line width
// pX, pY, pW - The margins. See ComputeMargins()

void OHtmlLayoutContext::WidenLine(int reqWidth, int *pX, int *pY, int *pW) {
  ComputeMargins(pX, pY, pW);
  if (*pW < reqWidth && InWrapAround()) {
    ClearObstacle(CLEAR_First);
    ComputeMargins(pX, pY, pW);
  }
}


#ifdef TABLE_TRIM_BLANK
int HtmlLineWasBlank = 0;
#endif // TABLE_TRIM_BLANK

// Do as much layout as possible on the block of text defined by
// the HtmlLayoutContext.

void OHtmlLayoutContext::LayoutBlock() {
  OHtmlElement *p, *pNext;

  for (p = pStart; p && p != pEnd; p = pNext) {
    int lineWidth;
    int actualWidth;
    int y = 0;
    int leftMargin;
    int max_x = 0;

    // Do as much break markup as we can.
    while (p && p != pEnd) {
      pNext = DoBreakMarkup(p);
      if (pNext == p) break;
      if (pNext) {
//        TRACE(HtmlTrace_BreakMarkup,
//           ("Processed token %s as break markup\n", HtmlTokenName(p)));
        pStart = p;
      }
      p = pNext;
    }

    if (p == 0 || p == pEnd) break;

#ifdef TABLE_TRIM_BLANK
    HtmlLineWasBlank = 0;
#endif // TABLE_TRIM_BLANK

    // We might try several times to layout a single line...
    while (1) {

      // Compute margins
      ComputeMargins(&leftMargin, &y, &lineWidth);

      // Layout a single line of text
      pNext = GetLine(p, pEnd, lineWidth, left-leftMargin, &actualWidth);
//      TRACE(HtmlTrace_GetLine,
//         ("GetLine page=%d left=%d right=%d available=%d used=%d\n",
//         pageWidth, left, right, lineWidth, actualWidth));
      FixAnchors(p, pNext, bottom);

      // Move down and repeat the layout if we exceeded the available
      // line length and it is possible to increase the line length by
      // moving past some obstacle.

      if (actualWidth > lineWidth && InWrapAround()) {
        ClearObstacle(CLEAR_First);
        continue;
      }

      // Lock the line into place and exit the loop
      y = FixLine(p, pNext, y, lineWidth, actualWidth, leftMargin, &max_x);
      break;
    }

#ifdef TABLE_TRIM_BLANK

    // I noticed that a newline following break markup would result
    // in a blank line being drawn. So if an "empty" line was found
    // I subtract any whitespace caused by break markup.

    if (actualWidth <= 0) HtmlLineWasBlank = 1;

#endif // TABLE_TRIM_BLANK

    // If a line was completed, advance to the next line
    if (pNext && actualWidth > 0 && y > bottom) {
      PopIndent();
      bottom = y;
      pStart = pNext;
    }
    if (y > maxY) maxY = y;
    if (max_x > maxX) maxX = max_x;
  }
}

void OHtmlLayoutContext::PushIndent() {
  headRoom += html->GetMarginHeight();
  if (html->GetMarginWidth()) {
    PushMargin(&leftMargin, html->GetMarginWidth(), -1, Html_EndBLOCKQUOTE);
    PushMargin(&rightMargin, html->GetMarginWidth(), -1, Html_EndBLOCKQUOTE);
  }
}

void OHtmlLayoutContext::PopIndent() {
  if (headRoom <= 0) return;
  headRoom = 0;
  PopMargin(&rightMargin, Html_EndBLOCKQUOTE);
}

// Advance the layout as far as possible

void OXHtml::LayoutDoc() {
  int btm;

  if (pFirst == 0) return;
  Sizer();
  layoutContext.html = this;
#if 0  // orig
  layoutContext.PushIndent();
  layoutContext.pageWidth = _canvas->GetWidth();
  layoutContext.left = 0;
#else
  layoutContext.headRoom = margins.t;
  layoutContext.pageWidth = _canvas->GetWidth() - margins.r;
  layoutContext.left = margins.l;
#endif
  layoutContext.right = 0;
  layoutContext.pStart = nextPlaced;
  if (layoutContext.pStart == 0) layoutContext.pStart = pFirst;
  if (layoutContext.pStart) {
    OHtmlElement *p;

    layoutContext.maxX = maxX;
    layoutContext.maxY = maxY;
    btm = layoutContext.bottom;
    layoutContext.LayoutBlock();
    maxX = layoutContext.maxX;
#if 0
    maxY = layoutContext.maxY;
#else
    maxY = layoutContext.maxY + margins.b;
#endif
    nextPlaced = layoutContext.pStart;
    flags |= HSCROLL | VSCROLL;
    if (zGoto && (p = AttrElem("name", zGoto+1))) {
      _visibleStart.y = ((OHtmlAnchor *)p)->y;
      delete[] zGoto;
      zGoto = 0;
    }
    RedrawText(btm);
  }
}
