diff options
Diffstat (limited to 'tkhtml1/src/htmllayout.c')
-rw-r--r-- | tkhtml1/src/htmllayout.c | 1170 |
1 files changed, 1170 insertions, 0 deletions
diff --git a/tkhtml1/src/htmllayout.c b/tkhtml1/src/htmllayout.c new file mode 100644 index 0000000..e511e53 --- /dev/null +++ b/tkhtml1/src/htmllayout.c @@ -0,0 +1,1170 @@ +/* +** This file contains the code used to position elements of the +** HTML file on the screen. +** +** Copyright (C) 1997-2000 D. Richard Hipp +** +** 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., 59 Temple Place - Suite 330, +** Boston, MA 02111-1307, USA. +** +** Author contact information: +** drh@acm.org +** http://www.hwaci.com/drh/ +*/ +#include <tk.h> +#include <stdlib.h> +#include <string.h> +#include "htmllayout.h" + + +/* +** 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 HtmlPopMargin(). +*/ +void HtmlPushMargin( + HtmlMargin **ppMargin, /* The margin stack onto which to push */ + int indent, /* The indentation for the new margin */ + int bottom, /* The margin expires at this Y coordinate */ + int tag /* Markup that will cancel this margin */ +){ + HtmlMargin *pNew = HtmlAlloc( sizeof(HtmlMargin) ); + pNew->pNext = *ppMargin; + if( pNew->pNext ){ + pNew->indent = indent + pNew->pNext->indent; + TestPoint(0); + }else{ + pNew->indent = indent; + TestPoint(0); + } + pNew->bottom = bottom; + pNew->tag = tag; + *ppMargin = pNew; +} + +/* +** Pop one margin off of the given margin stack. +*/ +static void HtmlPopOneMargin(HtmlMargin **ppMargin){ + if( *ppMargin ){ + HtmlMargin *pOld = *ppMargin; + *ppMargin = pOld->pNext; + HtmlFree(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 obsticles, if necessary. +** +** If there are some margins on the stack that contain non-negative +** bottom fields, that means there are some obsticles that we have +** not yet cleared. If these margins get popped off the stack, +** then we have to be careful to advance the pLC->bottom value so +** that the next line of text will clear the obsticle. +*/ +static void HtmlPopMargin( + HtmlMargin **ppMargin, /* The margin stack to be popped */ + int tag, /* The tag we want to pop */ + HtmlLayoutContext *pLC /* Update this layout context */ +){ + int bottom = -1; + int oldTag; + HtmlMargin *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>bottom ){ + bottom = pM->bottom; + } + oldTag = pM->tag; + HtmlPopOneMargin(ppMargin); + if( oldTag==tag ) break; + } + if( pLC && pLC->bottom<bottom ){ + pLC->headRoom += bottom - pLC->bottom; + pLC->bottom = bottom; + } +} + +/* +** 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 obsticles +** (an obsticle is an <IMG ALIGN=left> or <IMG ALIGN=right>) and +** expands the margins if we have. +*/ +static void PopExpiredMargins(HtmlMargin **ppMarginStack, int y){ + while( *ppMarginStack && (**ppMarginStack).bottom>=0 && + (**ppMarginStack).bottom <= y ){ + HtmlPopOneMargin(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 HtmlClearMarginStack(HtmlMargin **ppMargin){ + while( *ppMargin ){ + HtmlPopOneMargin(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". +*/ +static HtmlElement *GetLine( + HtmlLayoutContext *pLC, /* The complete layout context. */ + HtmlElement *pStart, /* First token on new line */ + HtmlElement *pEnd, /* End of line. Might be NULL */ + int width, /* How much space is on this line */ + int minX, /* The minimum value of the X coordinate */ + int *actualWidth /* Return space actually required */ +){ + int x; /* Current X coordinate */ + int spaceWanted = 0; /* Add this much space before next token */ + HtmlElement *p; /* For looping over tokens */ + HtmlElement *lastBreak = 0; /* Last line-break opportunity */ + int isEmpty = 1; /* True if link contains nothing */ + int origin; /* Initial value of "x" */ + + *actualWidth = 0; + p = pStart; + while( p && p!=pEnd && (p->base.style.flags & STY_Invisible)!=0 ){ + p = p->pNext; + } + if( p && p->base.style.flags & STY_DT ){ + origin = -HTML_INDENT; + }else{ + origin = 0; + } + x = origin; + if( x<minX ){ x = minX; } + if( p && p!=pEnd && p->base.type==Html_LI ){ + p->li.x = x - HTML_INDENT/3; + if( p->li.x - (HTML_INDENT*2)/3<minX ){ + x += minX - p->li.x + (HTML_INDENT*2)/3; + p->li.x = minX + (HTML_INDENT*2)/3; + } + isEmpty = 0; + *actualWidth = 1; + p = p->pNext; + while( p && (p->base.type==Html_Space || p->base.type==Html_P) ){ + p = p->pNext; + } + } + for(; p && p!=pEnd; p=p->pNext){ + if( p->base.style.flags & STY_Invisible ){ + continue; + } + switch( p->base.type ){ + case Html_Text: + p->text.x = x + spaceWanted; + if( (p->base.style.flags & STY_Preformatted) == 0 ){ + if( lastBreak && x + spaceWanted + p->text.w > width ){ + TestPoint(0); + return lastBreak; + } + } + TRACE(HtmlTrace_GetLine2, ("Place token %s at x=%d w=%d\n", + HtmlTokenName(p), p->text.x, p->text.w)); + x += p->text.w + spaceWanted; + isEmpty = 0; + spaceWanted = 0; + break; + + case Html_Space: + if( p->base.style.flags & STY_Preformatted ){ + if( p->base.flags & HTML_NewLine ){ + *actualWidth = x<=0 ? 1 : x; + TestPoint(0); + return p->pNext; + } + x += p->space.w * p->base.count; + TestPoint(0); + }else{ + int w; + if( (p->base.style.flags & STY_NoBreak)==0 ){ + lastBreak = p->pNext; + *actualWidth = x<=0 && !isEmpty ? 1 : x; + } + w = p->space.w; + if( spaceWanted < w && x>origin ){ + spaceWanted = w; + } + } + break; + + case Html_IMG: + switch( p->image.align ){ + case IMAGE_ALIGN_Left: + case IMAGE_ALIGN_Right: + *actualWidth = x<=0 && !isEmpty ? 1 : x; + return p; + default: + break; + } + p->image.x = x + spaceWanted; + if( (p->base.style.flags & STY_Preformatted) == 0 ){ + if( lastBreak && x + spaceWanted + p->image.w > width ){ + TestPoint(0); + 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 += p->image.w + spaceWanted; + if( (p->base.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: + p->input.x = x + spaceWanted + p->input.padLeft; + if( (p->base.style.flags & STY_Preformatted) == 0 ){ + if( lastBreak && x + spaceWanted + p->input.w > width ){ + TestPoint(0); + return lastBreak; + } + } + TRACE(HtmlTrace_GetLine2, ("Place token %s at x=%d w=%d\n", + HtmlTokenName(p), p->input.x, p->input.w)); + x = p->input.x + p->input.w; + if( (p->base.style.flags & STY_NoBreak)==0 ){ + lastBreak = p->pNext; + *actualWidth = x<=0 && !isEmpty ? 1 : x; + } + spaceWanted = 0; + isEmpty = 0; + break; + + case Html_EndTEXTAREA: + if( p->ref.pOther ){ + /* HtmlResetTextarea(pLC->htmlPtr, p->ref.pOther); */ + } + break; + + case Html_DD: + if( p->ref.pOther==0 ) break; + if( p->ref.pOther->list.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: + *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 +*/ +static void FixAnchors(HtmlElement *p, HtmlElement *pEnd, int y){ + while( p && p!=pEnd ){ + if( p->base.type==Html_A ){ + p->anchor.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. +*/ +static int FixLine( + HtmlElement *pStart, /* Start of tokens for this line */ + HtmlElement *pEnd, /* First token past end of this line. Maybe NULL */ + int bottom, /* Put the top of this line here */ + int width, /* This is the space available to the line */ + int actualWidth, /* This is the actual width needed by the line */ + int leftMargin, /* The current left margin */ + int *maxX /* Write maximum X coordinate of ink here */ +){ + 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 */ + HtmlElement *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=pStart; p && p!=pEnd && p->base.type!=Html_Text; p=p->pNext){} + if( p==pEnd || p==0 ) p = pStart; + if( p->base.style.align == ALIGN_Center ){ + dx = leftMargin + (width - actualWidth)/2; + }else if( p->base.style.align == ALIGN_Right ){ + dx = leftMargin + (width - actualWidth); + }else{ + dx = leftMargin; + } + if( dx<0 ) dx = 0; + maxAscent = maxTextAscent = 0; + for(p=pStart; p && p!=pEnd; p=p->pNext){ + int ss; + if( p->base.style.flags & STY_Invisible ){ + continue; + } + switch( p->base.type ){ + case Html_Text: + p->text.x += dx; + max = p->text.x + p->text.w; + ss = p->base.style.subscript; + if( ss > 0 ){ + int ascent = p->text.ascent; + int delta = (ascent + p->text.descent)*ss/2; + ascent += delta; + p->text.y = -delta; + if( ascent > maxAscent ){ TestPoint(0); maxAscent = ascent; } + if( ascent > maxTextAscent ){ TestPoint(0); maxTextAscent = ascent;} + }else if( ss < 0 ){ + int descent = p->text.descent; + int delta = (descent + p->text.ascent)*(-ss)/2; + descent += delta; + p->text.y = delta; + }else{ + p->text.y = 0; + if( p->text.ascent > maxAscent ){ + maxAscent = p->text.ascent; + } + if( p->text.ascent > maxTextAscent ){ + maxTextAscent = p->text.ascent; + } + } + break; + case Html_Space: + if( p->space.ascent > maxAscent ){ + maxAscent = p->space.ascent; + } + break; + case Html_LI: + p->li.x += dx; + if( p->li.x > max ){ + max = p->li.x; + } + break; + case Html_IMG: + p->image.x += dx; + max = p->image.x + p->image.w; + switch( p->image.align ){ + case IMAGE_ALIGN_Middle: + p->image.descent = p->image.h/2; + p->image.ascent = p->image.h - p->image.descent; + if( p->image.ascent > maxAscent ){ + maxAscent = p->image.ascent; + } + break; + case IMAGE_ALIGN_AbsMiddle: + dy2center = (p->image.textDescent - p->image.textAscent)/2; + p->image.descent = p->image.h/2 + dy2center; + p->image.ascent = p->image.h - p->image.descent; + if( p->image.ascent > maxAscent ){ + maxAscent = p->image.ascent; + } + break; + case IMAGE_ALIGN_Bottom: + p->image.descent = 0; + p->image.ascent = p->image.h; + if( p->image.ascent > maxAscent ){ + maxAscent = p->image.ascent; + } + break; + case IMAGE_ALIGN_AbsBottom: + p->image.descent = p->image.textDescent; + p->image.ascent = p->image.h - p->image.descent; + if( p->image.ascent > maxAscent ){ + maxAscent = p->image.ascent; + } + break; + default: + TestPoint(0); + break; + } + break; + case Html_TEXTAREA: + case Html_INPUT: + case Html_SELECT: + case Html_EMBED: + case Html_APPLET: + p->input.x += dx; + max = p->input.x + p->input.w; + dy2center = (p->input.textDescent - p->input.textAscent)/2; + p->input.y = dy2center - p->input.h/2; + ascent = -p->input.y; + if( ascent > maxAscent ){ + maxAscent = ascent; + } + break; + default: + /* Shouldn't happen */ + break; + } + } + *maxX = max; + y = maxAscent + bottom; + maxDescent = 0; + for(p=pStart; p && p!=pEnd; p=p->pNext){ + if( p->base.style.flags & STY_Invisible ){ + TestPoint(0); + continue; + } + switch( p->base.type ){ + case Html_Text: + p->text.y += y; + if( p->text.descent > maxDescent ){ + maxDescent = p->text.descent; + } + break; + case Html_LI: + p->li.y = y; + if( p->li.descent > maxDescent ){ + maxDescent = p->li.descent; + } + break; + case Html_IMG: + p->image.y = y; + switch( p->image.align ){ + case IMAGE_ALIGN_Top: + p->image.ascent = maxAscent; + p->image.descent = p->image.h - maxAscent; + TestPoint(0); + break; + case IMAGE_ALIGN_TextTop: + p->image.ascent = maxTextAscent; + p->image.descent = p->image.h - maxTextAscent; + TestPoint(0); + break; + default: + TestPoint(0); + break; + } + if( p->image.descent > maxDescent ){ + maxDescent = p->image.descent; + } + break; + case Html_INPUT: + case Html_SELECT: + case Html_TEXTAREA: + case Html_APPLET: + case Html_EMBED: + descent = p->input.y + p->input.h; + p->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 +*/ +static void Paragraph( + HtmlLayoutContext *pLC, + HtmlElement *p +){ + int headroom; + + if( p==0 ){ TestPoint(0); return; } + if( p->base.type==Html_Text ){ + headroom = p->text.ascent + p->text.descent; + TestPoint(0); + }else if( p->pNext && p->pNext->base.type==Html_Text ){ + headroom = p->pNext->text.ascent + p->pNext->text.descent; + TestPoint(0); + }else{ + Tk_FontMetrics fontMetrics; + Tk_Font font; + font = HtmlGetFont(pLC->htmlPtr, p->base.style.font); + if( font==0 ) return; + Tk_GetFontMetrics(font, &fontMetrics); + headroom = fontMetrics.descent + fontMetrics.ascent; + TestPoint(0); + } + if( pLC->headRoom < headroom && pLC->bottom > pLC->top ){ + pLC->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 HtmlComputeMargins( + HtmlLayoutContext *pLC, /* The current layout context */ + int *pX, /* Put the left edge here */ + int *pY, /* Put the top edge here */ + int *pW /* Put the width here */ +){ + int x, y, w; + + y = pLC->bottom + pLC->headRoom; + PopExpiredMargins(&pLC->leftMargin, pLC->bottom); + PopExpiredMargins(&pLC->rightMargin, pLC->bottom); + w = pLC->pageWidth - pLC->right; + if( pLC->leftMargin ){ + x = pLC->leftMargin->indent + pLC->left; + TestPoint(0); + }else{ + x = pLC->left; + TestPoint(0); + } + w -= x; + if( pLC->rightMargin ){ + w -= pLC->rightMargin->indent; + TestPoint(0); + }else{ + TestPoint(0); + } + *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 obsticle on either side. +*/ +#define CLEAR_Left 0 +#define CLEAR_Right 1 +#define CLEAR_Both 2 +#define CLEAR_First 3 +static void ClearObstacle(HtmlLayoutContext *pLC, int mode){ + int newBottom = pLC->bottom; + + PopExpiredMargins(&pLC->leftMargin, pLC->bottom); + PopExpiredMargins(&pLC->rightMargin, pLC->bottom); + switch( mode ){ + case CLEAR_Both: + ClearObstacle(pLC,CLEAR_Left); + ClearObstacle(pLC,CLEAR_Right); + TestPoint(0); + break; + + case CLEAR_Left: + while( pLC->leftMargin && pLC->leftMargin->bottom>=0 ){ + newBottom = pLC->leftMargin->bottom; + HtmlPopOneMargin(&pLC->leftMargin); + TestPoint(0); + } + if( newBottom > pLC->bottom + pLC->headRoom ){ + pLC->headRoom = 0; + TestPoint(0); + }else{ + pLC->headRoom = newBottom - pLC->bottom; + TestPoint(0); + } + pLC->bottom = newBottom; + PopExpiredMargins(&pLC->rightMargin, pLC->bottom); + break; + + case CLEAR_Right: + while( pLC->rightMargin && pLC->rightMargin->bottom>=0 ){ + newBottom = pLC->rightMargin->bottom; + HtmlPopOneMargin(&pLC->rightMargin); + TestPoint(0); + } + if( newBottom > pLC->bottom + pLC->headRoom ){ + pLC->headRoom = 0; + TestPoint(0); + }else{ + pLC->headRoom = newBottom - pLC->bottom; + TestPoint(0); + } + pLC->bottom = newBottom; + PopExpiredMargins(&pLC->leftMargin, pLC->bottom); + break; + + case CLEAR_First: + if( pLC->leftMargin && pLC->leftMargin->bottom>=0 ){ + if( pLC->rightMargin + && pLC->rightMargin->bottom < pLC->leftMargin->bottom + ){ + newBottom = pLC->rightMargin->bottom; + HtmlPopOneMargin(&pLC->rightMargin); + TestPoint(0); + }else{ + newBottom = pLC->leftMargin->bottom; + HtmlPopOneMargin(&pLC->leftMargin); + TestPoint(0); + } + }else if( pLC->rightMargin && pLC->rightMargin->bottom>=0 ){ + newBottom = pLC->rightMargin->bottom; + HtmlPopOneMargin(&pLC->rightMargin); + TestPoint(0); + }else{ + TestPoint(0); + } + if( newBottom > pLC->bottom + pLC->headRoom ){ + pLC->headRoom = 0; + TestPoint(0); + }else{ + pLC->headRoom = newBottom - pLC->bottom; + TestPoint(0); + } + pLC->bottom = newBottom; + break; + } +} + +/* +** 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. +*/ +static HtmlElement *DoBreakMarkup( + HtmlLayoutContext *pLC, + HtmlElement *p +){ + HtmlElement *pNext = p->pNext; + char *z; + int x, y, w; + + switch( p->base.type ){ + case Html_A: + p->anchor.y = pLC->bottom; + TestPoint(0); + break; + + case Html_BLOCKQUOTE: + HtmlPushMargin(&pLC->leftMargin, HTML_INDENT, -1, Html_EndBLOCKQUOTE); + HtmlPushMargin(&pLC->rightMargin, HTML_INDENT, -1, Html_EndBLOCKQUOTE); + Paragraph(pLC, p); + TestPoint(0); + break; + case Html_EndBLOCKQUOTE: + HtmlPopMargin(&pLC->leftMargin, Html_EndBLOCKQUOTE, pLC); + HtmlPopMargin(&pLC->rightMargin, Html_EndBLOCKQUOTE, pLC); + Paragraph(pLC, p); + TestPoint(0); + break; + + case Html_IMG: + switch( p->image.align ){ + case IMAGE_ALIGN_Left: + HtmlComputeMargins(pLC, &x, &y, &w); + p->image.x = x; + p->image.y = y; + p->image.ascent = 0; + p->image.descent = p->image.h; + HtmlPushMargin(&pLC->leftMargin, p->image.w + 2, y + p->image.h, 0); + SETMAX( pLC->maxY, y + p->image.h ); + SETMAX( pLC->maxX, x + p->image.w ); + break; + case IMAGE_ALIGN_Right: + HtmlComputeMargins(pLC, &x, &y, &w); + p->image.x = x + w - p->image.w; + p->image.y = y; + p->image.ascent = 0; + p->image.descent = p->image.h; + HtmlPushMargin(&pLC->rightMargin, p->image.w + 2, y + p->image.h, 0); + SETMAX( pLC->maxY, y + p->image.h ); + SETMAX( pLC->maxX, x + p->image.w ); + break; + default: + TestPoint(0); + pNext = p; + break; + } + break; + + + case Html_PRE: + /* Skip space tokens thru the next newline. */ + while( pNext->base.type==Html_Space ){ + HtmlElement *pThis = pNext; + pNext = pNext->pNext; + if( pThis->base.flags & HTML_NewLine ){ TestPoint(0); break; } + } + Paragraph(pLC,p); + break; + + case Html_UL: + case Html_MENU: + case Html_DIR: + case Html_OL: + if( p->list.compact==0 ){ + Paragraph(pLC,p); + } + HtmlPushMargin(&pLC->leftMargin, HTML_INDENT, -1, p->base.type+1); + break; + + case Html_EndOL: + case Html_EndUL: + case Html_EndMENU: + case Html_EndDIR: + if( p->ref.pOther ){ + HtmlPopMargin(&pLC->leftMargin, p->base.type, pLC); + if( !p->ref.pOther->list.compact ){ + Paragraph(pLC,p); + } + } + break; + + case Html_DL: + Paragraph(pLC,p); + HtmlPushMargin(&pLC->leftMargin, HTML_INDENT, -1, Html_EndDL); + TestPoint(0); + break; + + case Html_EndDL: + HtmlPopMargin(&pLC->leftMargin, Html_EndDL, pLC); + Paragraph(pLC,p); + TestPoint(0); + break; + + case Html_HR: { + int zl, wd; + + p->hr.is3D = HtmlMarkupArg(p, "noshade", 0)==0; + z = HtmlMarkupArg(p, "size", 0); + if( z ){ + p->hr.h = atoi(z); + }else{ + p->hr.h = 0; + } + if( p->hr.h<1 ){ + int relief = pLC->htmlPtr->ruleRelief; + if( p->hr.is3D + && (relief==TK_RELIEF_SUNKEN || relief==TK_RELIEF_RAISED) ){ + p->hr.h = 3; + }else{ + p->hr.h = 2; + } + } + HtmlComputeMargins(pLC, &x, &y, &w); + p->hr.y = y; + y += p->hr.h + 1; + p->hr.x = x; + z = HtmlMarkupArg(p, "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; + p->hr.w = wd; + switch( p->base.style.align ){ + case ALIGN_Center: + case ALIGN_None: + p->hr.x += (w - wd)/2; + TestPoint(0); + break; + case ALIGN_Right: + p->hr.x += (w - wd); + TestPoint(0); + break; + default: + TestPoint(0); + break; + } + SETMAX( pLC->maxY, y); + SETMAX( pLC->maxX, wd + p->hr.x ); + pLC->bottom = y; + pLC->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: + Paragraph(pLC, p); + TestPoint(0); + break; + + case Html_TABLE: + pNext = HtmlTableLayout(pLC, p); + TestPoint(0); + break; + + case Html_BR: + z = HtmlMarkupArg(p, "clear",0); + if( z ){ + if( stricmp(z,"left")==0 ){ + ClearObstacle(pLC, CLEAR_Left); + TestPoint(0); + }else if( stricmp(z,"right")==0 ){ + ClearObstacle(pLC, CLEAR_Right); + TestPoint(0); + }else{ + ClearObstacle(pLC, CLEAR_Both); + TestPoint(0); + } + }else{ + TestPoint(0); + } + 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; + TestPoint(0); + break; + + default: + TestPoint(0); + break; + } + return pNext; +} + +/* +** Return TRUE (non-zero) if we are currently wrapping text around +** one or more images. +*/ +static int InWrapAround(HtmlLayoutContext *pLC){ + if( pLC->leftMargin && pLC->leftMargin->bottom >= 0 ){ + TestPoint(0); + return 1; + } + if( pLC->rightMargin && pLC->rightMargin->bottom >= 0 ){ + TestPoint(0); + return 1; + } + TestPoint(0); + return 0; +} + +/* +** Move past obsticles until a linewidth of reqWidth is obtained, +** or until all obsticles are cleared. +*/ +void HtmlWidenLine( + HtmlLayoutContext *pLC, /* The layout context */ + int reqWidth, /* Requested line width */ + int *pX, int *pY, int *pW /* The margins. See HtmllComputeMargins() */ +){ + HtmlComputeMargins(pLC, pX, pY, pW); + if( *pW<reqWidth && InWrapAround(pLC) ){ + ClearObstacle(pLC, CLEAR_First); + HtmlComputeMargins(pLC, 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 HtmlLayoutBlock(HtmlLayoutContext *pLC){ + HtmlElement *p, *pNext; + + for(p=pLC->pStart; p && p!=pLC->pEnd; p=pNext){ + int lineWidth; + int actualWidth; + int y = 0; + int leftMargin; + int maxX = 0; + + /* Do as much break markup as we can. */ + while( p && p!=pLC->pEnd ){ + HtmlLock(pLC->htmlPtr); + pNext = DoBreakMarkup(pLC, p); + if( HtmlUnlock(pLC->htmlPtr) ) return; + if( pNext==p ){ TestPoint(0); break; } + if( pNext ){ + TRACE(HtmlTrace_BreakMarkup, + ("Processed token %s as break markup\n", HtmlTokenName(p))); + pLC->pStart = p; + } + p = pNext; + TestPoint(0); + } + if( p==0 || p==pLC->pEnd ){ TestPoint(0); 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 */ + HtmlComputeMargins(pLC, &leftMargin, &y, &lineWidth); + + /* Layout a single line of text */ + pNext = GetLine(pLC, p, pLC->pEnd, lineWidth, pLC->left-leftMargin, + &actualWidth); + TRACE(HtmlTrace_GetLine, + ("GetLine page=%d left=%d right=%d available=%d used=%d\n", + pLC->pageWidth, pLC->left, pLC->right, lineWidth, actualWidth)); + FixAnchors(p,pNext,pLC->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 obsticle. + */ + if( actualWidth > lineWidth && InWrapAround(pLC) ){ + ClearObstacle(pLC, CLEAR_First); + TestPoint(0); + continue; + } + + /* Lock the line into place and exit the loop */ + y = FixLine(p, pNext, y, lineWidth, actualWidth, leftMargin, &maxX); + TestPoint(0); + 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 > pLC->bottom ){ + pLC->bottom = y; + pLC->headRoom = 0; + pLC->pStart = pNext; + } + if( y > pLC->maxY ){ + pLC->maxY = y; + } + if( maxX > pLC->maxX ){ + pLC->maxX = maxX; + } + } +} + +/* +** Advance the layout as far as possible +*/ +void HtmlLayout(HtmlWidget *htmlPtr){ + HtmlLayoutContext *pLC; + int btm; + + if( htmlPtr->pFirst==0 ) return; + HtmlLock(htmlPtr); + HtmlSizer(htmlPtr); + if( HtmlUnlock(htmlPtr) ) return; + pLC = &htmlPtr->layoutContext; + pLC->htmlPtr = htmlPtr; + pLC->pageWidth = htmlPtr->realWidth - 2*(htmlPtr->inset + htmlPtr->padx); + pLC->left = 0; + pLC->right = 0; + pLC->pStart = htmlPtr->nextPlaced; + if( pLC->pStart==0 ){ + pLC->pStart = htmlPtr->pFirst; + } + if( pLC->pStart ){ + pLC->maxX = htmlPtr->maxX; + pLC->maxY = htmlPtr->maxY; + btm = pLC->bottom; + HtmlLock(htmlPtr); + HtmlLayoutBlock(pLC); + if( HtmlUnlock(htmlPtr) ) return; + htmlPtr->maxX = pLC->maxX; + htmlPtr->maxY = pLC->maxY; + htmlPtr->nextPlaced = pLC->pStart; + htmlPtr->flags |= HSCROLL | VSCROLL; + HtmlRedrawText(htmlPtr, btm); + } +} |