/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 2003-2004 Pierre G. Richard *
* Copyright (C) 2003-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine 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. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
package totalcross.ui.html;
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//!!!! REMEMBER THAT ANY CHANGE YOU MAKE IN THIS CODE MUST BE SENT BACK TO SUPERWABA COMPANY !!!!
//!!!! LEMBRE-SE QUE QUALQUER ALTERACAO QUE SEJA FEITO NESSE CODIGO DEVER� SER ENVIADA PARA NOS !!!!
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
import totalcross.ui.*;
import totalcross.ui.font.*;
import totalcross.ui.gfx.*;
import totalcross.xml.*;
/**
*
* The class <code>Style</code> group the several values required to paint
* a <code>Tile</code>.
* <P>
* <code>Style</code>s are grouped into a linked list in ancestry order.
* This disposition permits to implement style inheritance.
*
* @author Pierre G. Richard
*/
public class Style
{
/** Set the default font size to be used in the current build.
* Note that the Document must be re-rendered in order to update its font.
*/
public static int defaultFontSize = Font.NORMAL_SIZE;
// Font Bits
static final int BOLD = 1 << 0;
static final int ITALIC = 1 << 1;
static final int UNDERLINE = 1 << 2;
static final int STRIKETHROUGH = 1 << 3;
static final int SUBSCRIPT = 1 << 4;
static final int SUPERSCRIPT = 1 << 5;
static final int MONOSPACE = 1 << 6;
static final int FontMask = 0x7F;
private static final int DISJOINT = 1 << 7;
private static final int HEADED = 1 << 8; // List items, etc
private static final int CHG_STYLE = 1 << 9;
private static final int LINK = 1 << 10;
static final int P_AFTER = 1 << 11;
private static final int TopMarginPos = 16; // Mask pos
private static final int TopMarginLen = 2; // Mask width: [0,7]
private static final int IndentPos = 18; // Mask pos
private static final int IndentLen = 3; // Mask width: [-4,+3]
private static final int AlignPos = 21; // Mask pos
private static final int AlignLen = 2; // Mask width: [0,3]
private static final int FontSizePos = 23; // Mask pos: next pos 15
private static final int FontSizeLen = 3; // Mask width: [-4,+3]
private static final int TypePos = 26; // Mask pos
private static final int TypeLen = 2; // Mask width: [0,3]
static final int ALIGN_LEFT = 0;
static final int ALIGN_CENTER = 1;
static final int ALIGN_RIGHT = 2;
static final int ALIGN_NONE = 3; // no formatting
private static final int TYPE_SPECIAL = 3;
private static final int TYPE_DIV = 2;
private static final int TYPE_BLOCK = 1;
private static final int TYPE_INLINE = 0;
private static final int TYPE_WIERD = 0;
private static final int tagStyles[] =
{
/* 0 UNKNOWN */ 0,
/* 1 A */ type(TYPE_INLINE) | CHG_STYLE | UNDERLINE | LINK, // miketogg@570_59: define fontcolor for A tags, remove underline
/* 2 ABBR */ 0,
/* 3 ACRONYM */ 0,
/* 4 ADDRESS */ type(TYPE_BLOCK) | CHG_STYLE | ITALIC,
/* 5 APPLET */ 0,
/* 6 AREA */ 0,
/* 7 B */ type(TYPE_INLINE) | CHG_STYLE | BOLD,
/* 8 BASE */ 0,
/* 9 BASEFONT */ 0,
/* 10 BGSOUND */ 0,
/* 11 BIG */ type(TYPE_INLINE) | CHG_STYLE | fontSize(+2) | BOLD,
/* 12 BLOCKQUOTE*/ type(TYPE_BLOCK) | CHG_STYLE | DISJOINT | P_AFTER | top(5) | indent(+2),
/* 13 BODY */ 0,
/* 14 BR */ type(TYPE_INLINE) | CHG_STYLE | DISJOINT | indent(-1),
/* 15 BUTTON */ 0,
/* 16 CAPTION */ 0,
/* 17 CENTER */ type(TYPE_BLOCK) | CHG_STYLE | DISJOINT | align(ALIGN_CENTER),
/* 18 CITE */ 0,
/* 19 CODE */ type(TYPE_INLINE) | CHG_STYLE | fontSize(-1),
/* 20 COL */ 0,
/* 21 COLGROUP */ 0,
/* 22 COMMENT */ 0,
/* 23 DD */ type(TYPE_BLOCK) | CHG_STYLE | DISJOINT | indent(2),
/* 24 DEL */ type(TYPE_INLINE) | CHG_STYLE | STRIKETHROUGH,
/* 25 DFN */ 0,
/* 26 DIR */ type(TYPE_DIV) | CHG_STYLE,
/* 27 DIV */ 0,
/* 28 DL */ type(TYPE_DIV) | CHG_STYLE | DISJOINT,
/* 29 DT */ type(TYPE_BLOCK) | CHG_STYLE | DISJOINT | BOLD | indent(-2),
/* 30 EM */ type(TYPE_INLINE) | CHG_STYLE | BOLD | ITALIC,
/* 31 EMBED */ 0,
/* 32 FIELDSET */ 0,
/* 33 FONT */ type(TYPE_INLINE) | CHG_STYLE,
/* 34 FORM */ type(TYPE_WIERD),
/* 35 FRAME */ 0,
/* 36 FRAMESET */ 0,
/* 37 H1 */ type(TYPE_INLINE) | CHG_STYLE | DISJOINT | P_AFTER | align(ALIGN_NONE) | fontSize(+3) | BOLD,
/* 38 H2 */ type(TYPE_INLINE) | CHG_STYLE | DISJOINT | P_AFTER | align(ALIGN_NONE) | fontSize(+2) | BOLD,
/* 39 H3 */ type(TYPE_INLINE) | CHG_STYLE | DISJOINT | P_AFTER | align(ALIGN_NONE) | fontSize(+1) | BOLD,
/* 40 H4 */ type(TYPE_INLINE) | CHG_STYLE | DISJOINT | P_AFTER | align(ALIGN_NONE) | fontSize(+0) | BOLD,
/* 41 H5 */ type(TYPE_INLINE) | CHG_STYLE | DISJOINT | P_AFTER | align(ALIGN_NONE) | fontSize(-1) | BOLD,
/* 42 H6 */ type(TYPE_INLINE) | CHG_STYLE | DISJOINT | P_AFTER | align(ALIGN_NONE) | fontSize(-2) | BOLD,
/* 43 HEAD */ 0,
/* 44 HR */ type(TYPE_WIERD),
/* 45 HTML */ 0,
/* 46 I */ type(TYPE_INLINE) | CHG_STYLE | ITALIC,
/* 47 IFRAME */ 0,
/* 48 IMG */ type(TYPE_INLINE),
/* 49 INPUT */ type(TYPE_INLINE),
/* 50 INS */ 0,
/* 51 ISINDEX */ 0,
/* 52 KBD */ 0,
/* 53 LABEL */ 0,
/* 54 LEGEND */ 0,
/* 55 LI */ type(TYPE_BLOCK) | CHG_STYLE | DISJOINT | HEADED | indent(+3),
/* 56 LINK */ 0,
/* 57 LISTING */
/* 58 MAP */ 0,
/* 59 MARQUEE */
/* 60 MENU */ 0,
/* 61 META */ 0,
/* 62 NOBR */
/* 63 NOFRAMES */ 0,
/* 64 NOSCRIPT */ 0,
/* 65 OBJECT */ 0,
/* 66 OL */ type(TYPE_DIV) | CHG_STYLE | DISJOINT | indent(+2),
/* 67 OPTGROUP */ 0,
/* 68 OPTION */ 0,
/* 69 P */ type(TYPE_BLOCK) | CHG_STYLE | DISJOINT | top(3),
/* 70 PARAM */ 0,
/* 71 PLAINTEXT */ 0,
/* 72 PRE */ type(TYPE_BLOCK) | CHG_STYLE | align(ALIGN_NONE),
/* 73 Q */ type(TYPE_INLINE) | CHG_STYLE | indent(+2),
/* 74 S */ type(TYPE_INLINE) | CHG_STYLE | STRIKETHROUGH,
/* 75 SAMP */ type(TYPE_BLOCK) | CHG_STYLE | fontSize(-2),
/* 76 SCRIPT */ 0,
/* 77 SELECT */ 0,
/* 78 SMALL */ type(TYPE_INLINE) | CHG_STYLE | fontSize(-1),
/* 79 SPAN */ 0,
/* 80 STRIKE */ type(TYPE_INLINE) | CHG_STYLE | STRIKETHROUGH,
/* 81 STRONG */ type(TYPE_INLINE) | CHG_STYLE | BOLD,
/* 82 STYLE */ 0,
/* 83 SUB */ type(TYPE_INLINE) | CHG_STYLE | SUBSCRIPT | fontSize(-2),
/* 84 SUP */ type(TYPE_INLINE) | CHG_STYLE | SUPERSCRIPT | fontSize(-2),
/* 85 TABLE */ type(TYPE_SPECIAL) | CHG_STYLE,
/* 86 TBODY */ 0,
/* 87 TD */ type(TYPE_SPECIAL) | CHG_STYLE,
/* 88 TEXTAREA */ 0,
/* 89 TFOOT */ 0,
/* 90 TH */ type(TYPE_SPECIAL) | CHG_STYLE | BOLD,
/* 91 THEAD */ 0,
/* 92 TITLE */ 0,
/* 93 TR */ type(TYPE_SPECIAL) | CHG_STYLE,
/* 94 TT */ 0,
/* 95 U */ type(TYPE_INLINE) | CHG_STYLE | UNDERLINE,
/* 96 UL */ type(TYPE_DIV) | CHG_STYLE | DISJOINT | indent(+2),
/* 97 VAR */ 0,
/* 98 WBR */ 0,
/* 99 XMP */ type(TYPE_BLOCK) | CHG_STYLE | DISJOINT | top(3) | MONOSPACE
};
private static int type(int val)
{
return (val << TypePos) & ((-1 >>> (32 - TypeLen)) << TypePos);
}
private static int fontSize(int grow)
{
return (grow << FontSizePos) & ((-1 >>> (32 - FontSizeLen)) << FontSizePos);
}
private static int indent(int val)
{
return (val << IndentPos) & ((-1 >>> (32 - IndentLen)) << IndentPos);
}
private static int align(int val)
{
return (val << AlignPos) & ((-1 >>> (32 - AlignLen)) << AlignPos);
}
private static int top(int val)
{
return (val << TopMarginPos) & ((-1 >>> (32 - TopMarginLen)) << TopMarginPos);
}
protected boolean isParagraph;
private Style parent;
private int tagHashId;
protected int fontBits;
protected int topMargin;
protected int fontSize;
protected String fontFace;
protected int fontColor;
protected int backColor;
protected int alignment;
protected int indent;
private boolean isInited;
private boolean isHeaded;
protected boolean isDisjoint;
private int style;
protected AttributeList atts;
/** If this text is part of a link, what is the href for this link */
protected String href;
/**
* Constructor for the initial Style. Note that the tagHashId is 0.
*/
Style()
{
fontSize = defaultFontSize;
fontFace = Font.DEFAULT;
fontColor = UIColors.htmlContainerControlsFore;
backColor = UIColors.htmlContainerControlsBack;
}
/**
* Constructor
*
* @param tag
* tag identifier for this element
* @param atts
* The attributes attached to the element
* @param parent
* parent Style
*/
private Style(int tagHashId, AttributeList atts, Style parent)
{
this.parent = parent;
this.tagHashId = tagHashId;
this.atts = atts;
String attval;
style = getStyle(tagHashId);
href = parent.href;
isParagraph = parent.isParagraph;
attval = atts.getAttributeValue("align");
if (attval != null)
{
if (attval.equalsIgnoreCase("center"))
alignment = ALIGN_CENTER;
else
if (attval.equalsIgnoreCase("right"))
alignment = ALIGN_RIGHT; // guich@511_2: defaults to left instead of right.
else
alignment = ALIGN_LEFT;
}
else alignment = getAlignment();
topMargin = getTopMargin();
isDisjoint = (style & DISJOINT) != 0;
isHeaded = (style & HEADED) != 0;
boolean isLink = (style & LINK) != 0;
// inherited styles
// bottomMargin = parent.bottomMargin;
indent = parent.indent + getIndention();
if (indent < 0)
indent = 0;
fontBits = getFontBits() | parent.fontBits;
fontSize = parent.fontSize + getFontSizeDelta();
fontFace = parent.fontFace;
fontColor = isLink ? UIColors.htmlContainerLink : parent.fontColor;
if ((attval = atts.getAttributeValue("bgcolor")) != null)
backColor = getColor(attval,parent.backColor);
else
backColor = parent.backColor;
switch (tagHashId)
{
case TagDereferencer.TD: // a TD stops indent inheritance
indent = 0;
break;
case TagDereferencer.A:
href = atts.getAttributeValue("href");
break;
case TagDereferencer.FONT:
int v = atts.getAttributeValueAsInt("size",-1); if (v != -1) fontSize = v;
if (((attval = atts.getAttributeValue("color")) != null))
fontColor = getColor(attval,parent.backColor);
if ((attval = atts.getAttributeValue("face")) != null)
fontFace = attval;
break;
}
}
static int getColor(String attval, int defaultColor)
{
if (attval.startsWith("#"))
attval = attval.substring(1);
return attval.length() == 0 ? defaultColor : Color.getRGB(attval);
}
/**
* End enough Styles in the list to create a legal element content for a
* new element starting.
*
* @param current
* current Style
* @param tag
* tag identifier of the starting element
* @param atts
* The attributes attached to the element
* @return the new current Style
*
* Impl. Note:
*
* To ensure proper element nesting requires the use of well-balanced end tag
* (ala XHTML), however we want to allow bad markup. The algorithm used here
* consists to class HTML tags in four categories:
* | Specials | Division | Block | Inlines : Wierd |
* +----------+----------+---------------+-------------------------+-------+ |
* TABLE | DIR | ADDRESS DD | H1 A Q : HR | | TR | DL | BLOCKQUOTE DT | H2 B
* S : FORM | | TD | OL | CENTER LI | H3 BIG SMALL : | | | UL | P BR | H4
* CODE STRIKE : | | | | PRE | H5 DEL STRONG : | | | | SAPos | H6 EM SUB : | | | |
* XPos | IMG FONT SUP : | | | | | INPUT I U : |
* +----------+----------+---------------+-------------------------+-------+
*
* "Wierd" tags are categorized as "inlines", but are not as such - So is HR --
* though HR could be seen as a true inline. HR is empty, cleaned up from the
* list here. - FORM is also special. FORMS ends nothing. The dangling of
* FORM is OK for many parsers.
*
* When a tag that belongs to column 'n' starts, it ends all tags in columns
* 'm', with 'm > n';
*
* When (n == m) - the "Inlines" category does not end another Inline
* (nested) - the "Block" category does end an other Block (not nested) - the
* "Division" category does not end another Division (nested) - in the
* "Special" category: . TABLE ends nothing in its column, nor a DIV . TR
* ends TD . TD ends TD
*
* These are simplimistic rules, but it works with most bad markup.
*
*/
static Style tagStartFound(Style current, int tagHashId, AttributeList atts)
{
int type = getType(tagHashId);
if ((current.tagHashId == TagDereferencer.HR) || (current.tagHashId == TagDereferencer.IMG) || (current.tagHashId == TagDereferencer.INPUT))
current = current.parent;
redo: while (true)
{
switch (type)
{
case TYPE_SPECIAL: // end themselves and INLINEs
switch (tagHashId)
{
case TagDereferencer.TR: // A TR ends everything up to <TABLE>
while ((current.tagHashId != 0) && (current.tagHashId != TagDereferencer.TABLE))
{
if (current.tagHashId == TagDereferencer.TR)
{
current = current.parent;
break;
}
current = current.parent;
}
break;
case TagDereferencer.TD: // <TD> ends everything up to <TR>
while ((current.tagHashId != 0) && (current.tagHashId != TagDereferencer.TR))
{
current = current.parent;
}
break;
case TagDereferencer.TABLE:
type -= 2; // <TABLE>s end what BLOCKs end
continue redo; // a kind of "fall thru"
}
break;
case TYPE_DIV: // end what BLOCKs end
--type;
/* fall thru */
case TYPE_BLOCK: // end themselves and INLINEs
while ((current.tagHashId != 0) && (type >= current.getType()))
{
current = current.parent;
}
break;
default: // INLINEs or WIERD: do nothing
break;
}
break;
}
if (isStyleChanged(tagHashId))
{
switch (tagHashId)
{
case TagDereferencer.OL:
current = new Style.HeaderMaker.Ordered(tagHashId, atts, current);
break;
case TagDereferencer.UL:
current = new Style.HeaderMaker.Unordered(tagHashId, atts, current);
break;
default:
int originalAlign = current.alignment;
current = new Style(tagHashId, atts, current);
//flsobral@tc126: allow <p> to inherit the current alignment
if (tagHashId != TagDereferencer.P && originalAlign == ALIGN_NONE) // guich@tc112_34: inherit <pre> when <b> is found
current.alignment = ALIGN_NONE;
else if (tagHashId == TagDereferencer.BR && originalAlign != ALIGN_LEFT) //flsobral@tc126: <br> no longer overwrites the inherited alignment
current.alignment = ALIGN_NONE;
break;
}
}
return current;
}
/**
* End enough Style in the list until we find the matching start tag which
* is now ended.
*
* @param current
* current Style
* @param tag
* tag identifier of the starting element
* @return the new current Style
*/
static Style tagEndFound(Style current, int tagHashId)
{
if (isStyleChanged(tagHashId))
{
Style save = current; // avoid it to be gc'ed
while ((current.tagHashId != 0) && (current.tagHashId != tagHashId))
{
current = current.parent;
}
current = current.tagHashId == 0 ? save : current.parent;
}
return current;
}
/**
* Find out if the initial values of this Style have already been applied,
* and compute these values if they haven't been queried before.
*
* @return false if the initial values have been applied. true, otherwise.
*/
boolean hasInitialValues()
{
if (isInited)
return false;
isInited = true;
for (Style s = parent; (s != null) && (!s.isInited); s = s.parent)
{
isParagraph |= s.isParagraph;
isDisjoint |= s.isDisjoint;
topMargin += s.topMargin;
s.isInited = true;
}
return true;
}
static int getStyle(int tagHashId)
{
return tagStyles[tagHashId];
}
private int getFontBits()
{
return style & FontMask;
}
private int getType()
{
// >>> b/c unsigned
int style = tagStyles[tagHashId];
return (style << (32 - (TypePos + TypeLen))) >>> (32 - TypeLen);
}
private int getFontSizeDelta()
{
return (style << (32 - (FontSizePos + FontSizeLen))) >> (32 - FontSizeLen);
}
private int getIndention()
{
// >> b/c signed - indentions are 4 pixel per unit
return ((style << (32 - (IndentPos + IndentLen))) >> (32 - IndentLen)) << 2;
}
private int getAlignment()
{
// >>> b/c unsigned
return (style << (32 - (AlignPos + AlignLen))) >>> (32 - AlignLen);
}
int getTopMargin()
{
return (style << (32 - (TopMarginPos + TopMarginLen))) >>> (32 - TopMarginLen);
}
private static boolean isStyleChanged(int tagHashId)
{
return (tagStyles[tagHashId] & CHG_STYLE) != 0;
}
private static int getType(int tagHashId)
{
// >>> b/c unsigned
int style = tagStyles[tagHashId];
return (style << (32 - (TypePos + TypeLen))) >>> (32 - TypeLen);
}
/**
* Get the header associated to this style, if any.
*
* @return the header associated to this style
* @see Header
*/
Header getHeader()
{
if (isHeaded)
for (Style s = parent; s != null; s = s.parent)
if (s instanceof Style.HeaderMaker)
return ((Style.HeaderMaker) s).getHeader();
return null;
}
static class Header extends Control
{
}
private static abstract class HeaderMaker extends Style
{
HeaderMaker(int tagHashId, AttributeList atts, Style parent)
{
super(tagHashId, atts, parent);
}
abstract Header getHeader();
private static class Unordered extends HeaderMaker
{
private class Bullet extends Header
{
public Bullet()
{
focusTraversable = false;
}
public void onPaint(Graphics g)
{
g.backColor = UIColors.htmlContainerControlsFore;
g.fillCircle(height/2,height-height/2,height/4);
}
public int getPreferredWidth()
{
return fmH;
}
public int getPreferredHeight()
{
return fmH;
}
}
Unordered(int tagHashId, AttributeList atts, Style parent)
{
super(tagHashId, atts, parent);
}
Header getHeader()
{
return new Bullet();
}
}
private static class Ordered extends HeaderMaker
{
int order;
private class Number extends Header
{
String text;
Number(int i)
{
this.text = i + ". ";
focusTraversable = false;
}
public void onPaint(Graphics g)
{
g.drawText(text, 0,(height-fmH)/2); // guich@tc114_21: vertical align
}
public int getPreferredWidth()
{
return fm.stringWidth(text);
}
public int getPreferredHeight()
{
return fmH+Edit.prefH; // guich@tc114_21: add prefH
}
}
Ordered(int tagHashId, AttributeList atts, Style parent)
{
super(tagHashId, atts, parent);
order = 0;
}
Header getHeader()
{
return new Number(++order);
}
}
}
public Font getFont()
{
return Font.getFont(fontFace, ((fontBits & BOLD) != 0), fontSize);
}
/**
* Returns the Control alignment constant to be used by elements using this style.
*
* @param isRelative
* true if the alignment constant returned should be relative to another control instead of relative to the
* screen.
* @return the matching Control alignment constant
* @since TotalCross 1.27
*/
public int getControlAlignment(boolean isRelative)
{
switch (alignment)
{
case Style.ALIGN_CENTER:
return isRelative ? Control.CENTER_OF : Control.CENTER;
case Style.ALIGN_RIGHT:
return isRelative ? Control.RIGHT_OF : Control.RIGHT;
case Style.ALIGN_LEFT:
default:
return isRelative ? 0 : Control.LEFT;
}
}
}