// $Id: TGHtmlLayout.cxx,v 1.1 2007/05/04 17:07:01 brun Exp $
// Author: Valeriy Onuchin 03/05/2007
/**************************************************************************
HTML widget for xclass. Based on tkhtml 1.28
Copyright (C) 1997-2000 D. Richard Hipp
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
#include
#include "TGHtml.h"
////////////////////////////////////////////////////////////////////////////////
/// Html Layout Context constructor.
TGHtmlLayoutContext::TGHtmlLayoutContext()
{
fPStart = 0;
fPEnd = 0;
fLeftMargin = 0;
fRightMargin = 0;
fHtml = 0;
fLeft = 0;
fRight = 0;
fMaxX = 0;
fMaxY = 0;
fPageWidth = 0;
Reset();
}
////////////////////////////////////////////////////////////////////////////////
/// Reset the layout context.
void TGHtmlLayoutContext::Reset()
{
fHeadRoom = 0;
fTop = 0;
fBottom = 0;
ClearMarginStack(&fLeftMargin);
ClearMarginStack(&fRightMargin);
}
////////////////////////////////////////////////////////////////////////////////
/// 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 and 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
/// mbottom - The margin expires at this Y coordinate
/// tag - Markup that will cancel this margin
void TGHtmlLayoutContext::PushMargin(SHtmlMargin_t **ppMargin,
int indent, int mbottom, int tag)
{
SHtmlMargin_t *pNew = new SHtmlMargin_t;
pNew->fPNext = *ppMargin;
if (pNew->fPNext) {
pNew->fIndent = indent + pNew->fPNext->fIndent;
} else {
pNew->fIndent = indent;
}
pNew->fBottom = mbottom;
pNew->fTag = tag;
*ppMargin = pNew;
}
////////////////////////////////////////////////////////////////////////////////
/// Pop one margin off of the given margin stack.
void TGHtmlLayoutContext::PopOneMargin(SHtmlMargin_t **ppMargin)
{
if (*ppMargin) {
SHtmlMargin_t *pOld = *ppMargin;
*ppMargin = pOld->fPNext;
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 TGHtmlLayoutContext::PopMargin(SHtmlMargin_t **ppMargin, int tag)
{
int bot = -1;
int oldTag;
SHtmlMargin_t *pM;
for (pM = *ppMargin; pM && pM->fTag != tag; pM = pM->fPNext) {}
if (pM == 0) {
// No matching margin is found. Do nothing.
return;
}
while ((pM = *ppMargin) != 0) {
if (pM->fBottom > bot) bot = pM->fBottom;
oldTag = pM->fTag;
PopOneMargin(ppMargin);
if (oldTag == tag) break;
}
if (fBottom < bot) {
fHeadRoom += bot - fBottom;
fBottom = 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 or ) and
/// expands the margins if we have.
void TGHtmlLayoutContext::PopExpiredMargins(SHtmlMargin_t **ppMarginStack, int y)
{
while (*ppMarginStack && (**ppMarginStack).fBottom >= 0 &&
(**ppMarginStack).fBottom <= 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 TGHtmlLayoutContext::ClearMarginStack(SHtmlMargin_t **ppMargin)
{
while (*ppMargin) PopOneMargin(ppMargin);
}
////////////////////////////////////////////////////////////////////////////////
/// This routine gathers as many tokens as will fit on one line.
///
/// The candidate tokens begin with fPStart and go thru the end of
/// the list or to fPEnd, 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 ..
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 elements will be negative.
/// Text within .. 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
TGHtmlElement *TGHtmlLayoutContext::GetLine(TGHtmlElement *p_start,
TGHtmlElement *p_end, int width, int minX, int *actualWidth)
{
int x; // Current X coordinate
int spaceWanted = 0; // Add this much space before next token
TGHtmlElement *p; // For looping over tokens
TGHtmlElement *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->fStyle.fFlags & STY_Invisible) != 0) {
p = p->fPNext;
}
if (p && p->fStyle.fFlags & STY_DT) {
origin = -HTML_INDENT;
} else {
origin = 0;
}
x = origin;
if (x < minX) x = minX;
if (p && p != p_end && p->fType == Html_LI) {
TGHtmlLi *li = (TGHtmlLi *) p;
li->fX = x - HTML_INDENT / 3;
if (li->fX - (HTML_INDENT * 2) / 3 < minX) {
x += minX - li->fX + (HTML_INDENT * 2) / 3;
li->fX = minX + (HTML_INDENT * 2) / 3;
}
isEmpty = 0;
*actualWidth = 1;
p = p->fPNext;
while (p && (p->fType == Html_Space || p->fType == Html_P)) {
p = p->fPNext;
}
}
// coverity[dead_error_line]
for (; p && p != p_end; p = p ? p->fPNext : 0) {
if (p->fStyle.fFlags & STY_Invisible) continue;
switch (p->fType) {
case Html_Text: {
TGHtmlTextElement *text = (TGHtmlTextElement *) p;
text->fX = x + spaceWanted;
if ((p->fStyle.fFlags & STY_Preformatted) == 0) {
if (lastBreak && x + spaceWanted + text->fW > width)
return lastBreak;
}
// TRACE(HtmlTrace_GetLine2, ("Place token %s at x=%d w=%d\n",
// HtmlTokenName(p), text->fX, text->fW));
x += text->fW + spaceWanted;
isEmpty = 0;
spaceWanted = 0;
break;
}
case Html_Space: {
TGHtmlSpaceElement *space = (TGHtmlSpaceElement *) p;
if (p->fStyle.fFlags & STY_Preformatted) {
if (p->fFlags & HTML_NewLine) {
*actualWidth = (x <= 0) ? 1 : x;
return p->fPNext;
}
x += space->fW * p->fCount;
} else {
int w;
if ((p->fStyle.fFlags & STY_NoBreak) == 0) {
lastBreak = p->fPNext;
*actualWidth = ((x <= 0) && !isEmpty) ? 1 : x;
}
w = space->fW;
if (spaceWanted < w && x > origin) spaceWanted = w;
}
break;
}
case Html_IMG: {
TGHtmlImageMarkup *image = (TGHtmlImageMarkup *) p;
switch (image->fAlign) {
case IMAGE_ALIGN_Left:
case IMAGE_ALIGN_Right:
*actualWidth = ((x <= 0) && !isEmpty) ? 1 : x;
return p;
default:
break;
}
image->fX = x + spaceWanted;
if ((p->fStyle.fFlags & STY_Preformatted) == 0) {
if (lastBreak && x + spaceWanted + image->fW > 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->fW + spaceWanted;
if ((p->fStyle.fFlags & STY_NoBreak) == 0) {
lastBreak = p->fPNext;
*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: {
TGHtmlInput *input = (TGHtmlInput *) p;
input->fX = x + spaceWanted + input->fPadLeft;
if ((p->fStyle.fFlags & STY_Preformatted) == 0) {
if (lastBreak && x + spaceWanted + input->fW > 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->fX + input->fW;
if ((p->fStyle.fFlags & STY_NoBreak) == 0) {
lastBreak = p->fPNext;
*actualWidth = ((x <= 0) && !isEmpty) ? 1 : x;
}
spaceWanted = 0;
isEmpty = 0;
break;
}
case Html_EndTEXTAREA: {
TGHtmlRef *ref = (TGHtmlRef *) p;
if (ref->fPOther) {
// fHtml->ResetTextarea(ref->fPOther);
}
break;
}
case Html_DD: {
TGHtmlRef *ref = (TGHtmlRef *) p;
if (ref->fPOther == 0) break;
if (((TGHtmlListStart *)ref->fPOther)->fCompact == 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->fPNext;
} else {
lastBreak = p->fPNext;
}
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 TGHtmlLayoutContext::FixAnchors(TGHtmlElement *p, TGHtmlElement *p_end, int y)
{
while (p && p != p_end) {
if (p->fType == Html_A) ((TGHtmlAnchor *)p)->fY = y;
p = p->fPNext;
}
}
////////////////////////////////////////////////////////////////////////////////
/// 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
/// mbottom - 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
/// lMargin - The current left margin
/// max_x - Write maximum X coordinate of ink here
int TGHtmlLayoutContext::FixLine(TGHtmlElement *p_start,
TGHtmlElement *p_end, int mbottom, int width,
int actualWidth, int lMargin, 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
TGHtmlElement *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->fType != Html_Text; p = p->fPNext) {}
if (p == p_end || p == 0) p = p_start;
maxAscent = maxTextAscent = 0;
for (p = p_start; p && p != p_end; p = p->fPNext) {
int ss;
if (p->fStyle.fAlign == ALIGN_Center) {
dx = lMargin + (width - actualWidth) / 2;
} else if (p->fStyle.fAlign == ALIGN_Right) {
dx = lMargin + (width - actualWidth);
} else {
dx = lMargin;
}
if (dx < 0) dx = 0;
if (p->fStyle.fFlags & STY_Invisible) continue;
switch (p->fType) {
case Html_Text: {
TGHtmlTextElement *text = (TGHtmlTextElement *) p;
text->fX += dx;
max = text->fX + text->fW;
ss = p->fStyle.fSubscript;
if (ss > 0) {
int ascent2 = text->fAscent;
int delta = (ascent2 + text->fDescent) * ss / 2;
ascent2 += delta;
text->fY = -delta;
if (ascent2 > maxAscent) maxAscent = ascent2;
if (ascent2 > maxTextAscent) maxTextAscent = ascent2;
} else if (ss < 0) {
int descent2 = text->fDescent;
int delta = (descent2 + text->fAscent) * (-ss) / 2;
descent2 += delta;
text->fY = delta;
} else {
text->fY = 0;
if (text->fAscent > maxAscent) maxAscent = text->fAscent;
if (text->fAscent > maxTextAscent) maxTextAscent = text->fAscent;
}
break;
}
case Html_Space: {
TGHtmlSpaceElement *space = (TGHtmlSpaceElement *) p;
if (space->fAscent > maxAscent) maxAscent = space->fAscent;
break;
}
case Html_LI: {
TGHtmlLi *li = (TGHtmlLi *) p;
li->fX += dx;
if (li->fX > max) max = li->fX;
break;
}
case Html_IMG: {
TGHtmlImageMarkup *image = (TGHtmlImageMarkup *) p;
image->fX += dx;
max = image->fX + image->fW;
switch (image->fAlign) {
case IMAGE_ALIGN_Middle:
image->fDescent = image->fH / 2;
image->fAscent = image->fH - image->fDescent;
if (image->fAscent > maxAscent) maxAscent = image->fAscent;
break;
case IMAGE_ALIGN_AbsMiddle:
dy2center = (image->fTextDescent - image->fTextAscent) / 2;
image->fDescent = image->fH / 2 + dy2center;
image->fAscent = image->fH - image->fDescent;
if (image->fAscent > maxAscent) maxAscent = image->fAscent;
break;
case IMAGE_ALIGN_Bottom:
image->fDescent = 0;
image->fAscent = image->fH;
if (image->fAscent > maxAscent) maxAscent = image->fAscent;
break;
case IMAGE_ALIGN_AbsBottom:
image->fDescent = image->fTextDescent;
image->fAscent = image->fH - image->fDescent;
if (image->fAscent > maxAscent) maxAscent = image->fAscent;
break;
default:
break;
}
break;
}
case Html_TABLE:
break;
case Html_TEXTAREA:
case Html_INPUT:
case Html_SELECT:
case Html_EMBED:
case Html_APPLET: {
TGHtmlInput *input = (TGHtmlInput *) p;
input->fX += dx;
max = input->fX + input->fW;
dy2center = (input->fTextDescent - input->fTextAscent) / 2;
input->fY = dy2center - input->fH / 2;
ascent = -input->fY;
if (ascent > maxAscent) maxAscent = ascent;
break;
}
default:
// Shouldn't happen
break;
}
}
*max_x = max;
y = maxAscent + mbottom;
maxDescent = 0;
for (p = p_start; p && p != p_end; p = p->fPNext) {
if (p->fStyle.fFlags & STY_Invisible) continue;
switch (p->fType) {
case Html_Text: {
TGHtmlTextElement *text = (TGHtmlTextElement *) p;
text->fY += y;
if (text->fDescent > maxDescent) maxDescent = text->fDescent;
break;
}
case Html_LI: {
TGHtmlLi *li = (TGHtmlLi *) p;
li->fY = y;
if (li->fDescent > maxDescent) maxDescent = li->fDescent;
break;
}
case Html_IMG: {
TGHtmlImageMarkup *image = (TGHtmlImageMarkup *) p;
image->fY = y;
switch (image->fAlign) {
case IMAGE_ALIGN_Top:
image->fAscent = maxAscent;
image->fDescent = image->fH - maxAscent;
break;
case IMAGE_ALIGN_TextTop:
image->fAscent = maxTextAscent;
image->fDescent = image->fH - maxTextAscent;
break;
default:
break;
}
if (image->fDescent > maxDescent) maxDescent = image->fDescent;
break;
}
case Html_TABLE:
break;
case Html_INPUT:
case Html_SELECT:
case Html_TEXTAREA:
case Html_APPLET:
case Html_EMBED: {
TGHtmlInput *input = (TGHtmlInput *) p;
descent = input->fY + input->fH;
input->fY += y;
if (descent > maxDescent) maxDescent = descent;
break;
}
default:
/* Shouldn't happen */
break;
}
}
// TRACE(HtmlTrace_FixLine,
// ("Setting baseline to %d. mbottom=%d ascent=%d descent=%d dx=%d\n",
// y, mbottom, maxAscent, maxDescent, dx));
} else {
maxDescent = 0;
y = mbottom;
}
return y + maxDescent;
}
////////////////////////////////////////////////////////////////////////////////
/// Increase the headroom to create a paragraph break at the current token
void TGHtmlLayoutContext::Paragraph(TGHtmlElement *p)
{
int headroom;
if (p == 0) return;
if (p->fType == Html_Text) {
TGHtmlTextElement *text = (TGHtmlTextElement *) p;
headroom = text->fAscent + text->fDescent;
} else if (p->fPNext && p->fPNext->fType == Html_Text) {
TGHtmlTextElement *text = (TGHtmlTextElement *) p->fPNext;
headroom = text->fAscent + text->fDescent;
} else {
//// headroom = 10;
FontMetrics_t fontMetrics;
TGFont *font;
font = fHtml->GetFont(p->fStyle.fFont);
if (font == 0) return;
font->GetFontMetrics(&fontMetrics);
headroom = fontMetrics.fDescent + fontMetrics.fAscent;
}
if (fHeadRoom < headroom && fBottom > fTop) fHeadRoom = 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 TGHtmlLayoutContext::ComputeMargins(int *pX, int *pY, int *pW)
{
int x, y, w;
y = fBottom + fHeadRoom;
PopExpiredMargins(&fLeftMargin, fBottom);
PopExpiredMargins(&fRightMargin, fBottom);
w = fPageWidth - fRight;
if (fLeftMargin) {
x = fLeftMargin->fIndent + fLeft;
} else {
x = fLeft;
}
w -= x;
if (fRightMargin) w -= fRightMargin->fIndent;
*pX = x;
*pY = y;
*pW = w;
}
#define CLEAR_Left 0
#define CLEAR_Right 1
#define CLEAR_Both 2
#define CLEAR_First 3
////////////////////////////////////////////////////////////////////////////////
/// 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.
void TGHtmlLayoutContext::ClearObstacle(int mode)
{
int newBottom = fBottom;
PopExpiredMargins(&fLeftMargin, fBottom);
PopExpiredMargins(&fRightMargin, fBottom);
switch (mode) {
case CLEAR_Both:
ClearObstacle(CLEAR_Left);
ClearObstacle(CLEAR_Right);
break;
case CLEAR_Left:
while (fLeftMargin && fLeftMargin->fBottom >= 0) {
if (newBottom < fLeftMargin->fBottom) {
newBottom = fLeftMargin->fBottom;
}
PopOneMargin(&fLeftMargin);
}
if (newBottom > fBottom + fHeadRoom) {
fHeadRoom = 0;
} else {
fHeadRoom = newBottom - fBottom;
}
fBottom = newBottom;
PopExpiredMargins(&fRightMargin, fBottom);
break;
case CLEAR_Right:
while (fRightMargin && fRightMargin->fBottom >= 0) {
if (newBottom < fRightMargin->fBottom) {
newBottom = fRightMargin->fBottom;
}
PopOneMargin(&fRightMargin);
}
if (newBottom > fBottom + fHeadRoom) {
fHeadRoom = 0;
} else {
fHeadRoom = newBottom - fBottom;
}
fBottom = newBottom;
PopExpiredMargins(&fLeftMargin, fBottom);
break;
case CLEAR_First:
if (fLeftMargin && fLeftMargin->fBottom >= 0) {
if (fRightMargin &&
fRightMargin->fBottom < fLeftMargin->fBottom) {
if (newBottom < fRightMargin->fBottom) {
newBottom = fRightMargin->fBottom;
}
PopOneMargin(&fRightMargin);
} else {
if (newBottom < fLeftMargin->fBottom) {
newBottom = fLeftMargin->fBottom;
}
PopOneMargin(&fLeftMargin);
}
} else if (fRightMargin && fRightMargin->fBottom >= 0) {
newBottom = fRightMargin->fBottom;
PopOneMargin(&fRightMargin);
}
if (newBottom > fBottom + fHeadRoom) {
fHeadRoom = 0;
} else {
fHeadRoom = newBottom - fBottom;
}
fBottom = newBottom;
break;
default:
break;
}
}
////////////////////////////////////////////////////////////////////////////////
/// Return the next markup type [TGHtmlElement::NextMarkupType]
int TGHtml::NextMarkupType(TGHtmlElement *p)
{
while ((p = p->fPNext)) {
if (p->IsMarkup()) return p->fType;
}
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 ), then NULL is returned.
TGHtmlElement *TGHtmlLayoutContext::DoBreakMarkup(TGHtmlElement *p)
{
TGHtmlElement *fPNext = p->fPNext;
const char *z;
int x, y, w;
switch (p->fType) {
case Html_A:
((TGHtmlAnchor *)p)->fY = fBottom;
break;
case Html_BLOCKQUOTE:
PushMargin(&fLeftMargin, HTML_INDENT, -1, Html_EndBLOCKQUOTE);
PushMargin(&fRightMargin, HTML_INDENT, -1, Html_EndBLOCKQUOTE);
Paragraph(p);
break;
case Html_EndBLOCKQUOTE:
PopMargin(&fLeftMargin, Html_EndBLOCKQUOTE);
PopMargin(&fRightMargin, Html_EndBLOCKQUOTE);
Paragraph(p);
break;
case Html_IMG: {
TGHtmlImageMarkup *image = (TGHtmlImageMarkup *) p;
switch (image->fAlign) {
case IMAGE_ALIGN_Left:
ComputeMargins(&x, &y, &w);
image->fX = x;
image->fY = y;
image->fAscent = 0;
image->fDescent = image->fH;
PushMargin(&fLeftMargin, image->fW + 2, y + image->fH, 0);
if (fMaxY < y + image->fH) fMaxY = y + image->fH;
if (fMaxX < x + image->fW) fMaxX = x + image->fW;
break;
case IMAGE_ALIGN_Right:
ComputeMargins(&x, &y, &w);
image->fX = x + w - image->fW;
image->fY = y;
image->fAscent = 0;
image->fDescent = image->fH;
PushMargin(&fRightMargin, image->fW + 2, y + image->fH, 0);
if (fMaxY < y + image->fH) fMaxY = y + image->fH;
if (fMaxX < x + image->fW) fMaxX = x + image->fW;
break;
default:
fPNext = p;
break;
}
break;
}
case Html_PRE:
// Skip space tokens thru the next newline.
while (fPNext->fType == Html_Space) {
TGHtmlElement *pThis = fPNext;
fPNext = fPNext->fPNext;
if (pThis->fFlags & HTML_NewLine) break;
}
Paragraph(p);
break;
case Html_UL:
case Html_MENU:
case Html_DIR:
case Html_OL:
if (((TGHtmlListStart *)p)->fCompact == 0) Paragraph(p);
PushMargin(&fLeftMargin, HTML_INDENT, -1, p->fType + 1);
break;
case Html_EndOL:
case Html_EndUL:
case Html_EndMENU:
case Html_EndDIR: {
TGHtmlRef *ref = (TGHtmlRef *) p;
if (ref->fPOther) {
PopMargin(&fLeftMargin, p->fType);
if (!((TGHtmlListStart *)ref->fPOther)->fCompact) Paragraph(p);
}
break;
}
case Html_DL:
Paragraph(p);
PushMargin(&fLeftMargin, HTML_INDENT, -1, Html_EndDL);
break;
case Html_EndDL:
PopMargin(&fLeftMargin, Html_EndDL);
Paragraph(p);
break;
case Html_HR: {
int zl, wd;
TGHtmlHr *hr = (TGHtmlHr *) p;
hr->fIs3D = (p->MarkupArg("noshade", 0) == 0);
z = p->MarkupArg("size", 0);
if (z) {
int hrsz = atoi(z);
hr->fH = (hrsz < 0) ? 2 : hrsz;
} else {
hr->fH = 0;
}
if (hr->fH < 1) {
int relief = fHtml->GetRuleRelief();
if (hr->fIs3D &&
(relief == HTML_RELIEF_SUNKEN || relief == HTML_RELIEF_RAISED)) {
hr->fH = 3;
} else {
hr->fH = 2;
}
}
ComputeMargins(&x, &y, &w);
hr->fY = y + fHtml->GetRulePadding();
y += hr->fH + fHtml->GetRulePadding() * 2 + 1;
hr->fX = x;
z = p->MarkupArg("width", "100%");
zl = z ? strlen(z) : 0;
if (zl > 0 && z[zl-1] == '%') {
wd = (atoi(z) * w) / 100;
} else {
wd = z ? atoi(z) : w;
}
if (wd > w) wd = w;
hr->fW = wd;
switch (p->fStyle.fAlign) {
case ALIGN_Center:
case ALIGN_None:
hr->fX += (w - wd) / 2;
break;
case ALIGN_Right:
hr->fX += (w - wd);
break;
default:
break;
}
if (fMaxY < y) fMaxY = y;
if (fMaxX < wd + hr->fX) fMaxX = wd + hr->fX;
fBottom = y;
fHeadRoom = 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:
fPNext = TableLayout((TGHtmlTable *) 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->fPNext && p->fPNext->fPNext && p->fPNext->fType == Html_Space &&
p->fPNext->fPNext->fType == 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:
fPNext = p;
break;
default:
break;
}
return fPNext;
}
////////////////////////////////////////////////////////////////////////////////
/// Return TRUE (non-zero) if we are currently wrapping text around
/// one or more images.
int TGHtmlLayoutContext::InWrapAround()
{
if (fLeftMargin && fLeftMargin->fBottom >= 0) return 1;
if (fRightMargin && fRightMargin->fBottom >= 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 TGHtmlLayoutContext::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 TGHtmlLayoutContext::LayoutBlock()
{
TGHtmlElement *p, *pNext;
for (p = fPStart; p && p != fPEnd; p = pNext) {
int lineWidth;
int actualWidth;
int y = 0;
int lMargin;
int max_x = 0;
// Do as much break markup as we can.
while (p && p != fPEnd) {
pNext = DoBreakMarkup(p);
if (pNext == p) break;
if (pNext) {
// TRACE(HtmlTrace_BreakMarkup,
// ("Processed token %s as break markup\n", HtmlTokenName(p)));
fPStart = p;
}
p = pNext;
}
if (p == 0 || p == fPEnd) 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(&lMargin, &y, &lineWidth);
// Layout a single line of text
pNext = GetLine(p, fPEnd, lineWidth, fLeft-lMargin, &actualWidth);
// TRACE(HtmlTrace_GetLine,
// ("GetLine page=%d left=%d right=%d available=%d used=%d\n",
// fPageWidth, fLeft, fRight, lineWidth, actualWidth));
FixAnchors(p, pNext, fBottom);
// 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, lMargin, &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 > fBottom) {
PopIndent();
fBottom = y;
fPStart = pNext;
}
if (y > fMaxY) fMaxY = y;
if (max_x > fMaxX) fMaxX = max_x;
}
}
////////////////////////////////////////////////////////////////////////////////
/// Adjust (push) ident.
void TGHtmlLayoutContext::PushIndent()
{
fHeadRoom += fHtml->GetMarginHeight();
if (fHtml->GetMarginWidth()) {
PushMargin(&fLeftMargin, fHtml->GetMarginWidth(), -1, Html_EndBLOCKQUOTE);
PushMargin(&fRightMargin, fHtml->GetMarginWidth(), -1, Html_EndBLOCKQUOTE);
}
}
////////////////////////////////////////////////////////////////////////////////
/// Adjust (pop) ident.
void TGHtmlLayoutContext::PopIndent()
{
if (fHeadRoom <= 0) return;
fHeadRoom = 0;
PopMargin(&fRightMargin, Html_EndBLOCKQUOTE);
}
////////////////////////////////////////////////////////////////////////////////
/// Advance the layout as far as possible
void TGHtml::LayoutDoc()
{
int btm;
if (fPFirst == 0) return;
Sizer();
fLayoutContext.fHtml = this;
#if 0 // orig
fLayoutContext.PushIndent();
fLayoutContext.fPageWidth = fCanvas->GetWidth();
fLayoutContext.fLeft = 0;
#else
fLayoutContext.fHeadRoom = HTML_INDENT/4;
fLayoutContext.fPageWidth = fCanvas->GetWidth() - HTML_INDENT/4;
fLayoutContext.fLeft = HTML_INDENT/4;
#endif
fLayoutContext.fRight = 0;
fLayoutContext.fPStart = fNextPlaced;
if (fLayoutContext.fPStart == 0) fLayoutContext.fPStart = fPFirst;
if (fLayoutContext.fPStart) {
TGHtmlElement *p;
fLayoutContext.fMaxX = fMaxX;
fLayoutContext.fMaxY = fMaxY;
btm = fLayoutContext.fBottom;
fLayoutContext.LayoutBlock();
fMaxX = fLayoutContext.fMaxX;
#if 0
fMaxY = fLayoutContext.fMaxY;
#else
fMaxY = fLayoutContext.fMaxY + fYMargin;
#endif
fNextPlaced = fLayoutContext.fPStart;
fFlags |= HSCROLL | VSCROLL;
if (fZGoto && (p = AttrElem("name", fZGoto+1))) {
fVisible.fY = ((TGHtmlAnchor *)p)->fY;
delete[] fZGoto;
fZGoto = 0;
}
RedrawText(btm);
}
}