/********************************************************************************* * 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.sys.*; import totalcross.ui.*; import totalcross.ui.event.*; import totalcross.ui.font.*; import totalcross.ui.gfx.*; /** * The <code>TextSpan</code> class holds consecutive characters of text having * the same font metrics. These characters are broken into lines. Each Line * -- a static inner class -- defines the Rect suitable for the portion of text * it contains. * <P> * On the display, a <code>TextSpan</code> ressembles to: * <PRE> * * +------------------------------+ * HH | xxxxxxxx | <--- Line * +----------+------------------------------+ * | yyyyyyyyyy | <--- Line * +-----------------------------+-----------+ * | zzzzzzzzzz | FF <--- Line * +-----------------------------+ * </PRE> * where "HH" and "FF" are Header (OL and UL) and Footer (not yet implemented) * pertaining to this <code>TextSpan</code>. */ class TextSpan extends Control implements Document.CustomLayout, Document.SizeDelimiter { private static final boolean DEBUG = false; private char[] text; private Style style; private int maxWordWidth; public boolean finishIdent,openIdent; /** Static class to hold each line of text within this span. */ private class TextLine extends totalcross.ui.Control { private int pos; private int len; TextLine(int start, int end) { pos = start; len = end - start; focusTraversable = style.href != null; } public int getPreferredWidth() { return fm.stringWidth(text, pos, len); } public int getPreferredHeight() { return fmH+Edit.prefH; } public void onPaint(Graphics g) { int deltaY; if ((style.fontBits & Style.SUBSCRIPT) != 0) deltaY = 3; else if ((style.fontBits & Style.SUPERSCRIPT) != 0) deltaY = -3; else deltaY = 0; g.foreColor = style.fontColor; g.backColor = style.backColor; if (style.backColor != getParent().getBackColor()) // fill background if needed g.fillRect(0,0,width,height); g.drawText(text, pos, len, 0, (height-fmH)/2+deltaY); if ((style.fontBits & (Style.STRIKETHROUGH|Style.UNDERLINE)) != 0) { int ypos = (style.fontBits & (Style.STRIKETHROUGH)) != 0 ? fm.height/2 : fm.ascent + 2; g.drawLine(0, ypos, width, ypos); } } public void onEvent(Event e) { if ((e.type == PenEvent.PEN_DOWN || e.type == KeyEvent.ACTION_KEY_PRESS) && style.href != null) HtmlContainer.getHtmlContainer(this).postLinkEvent(style.href); } } /** * Constructor. * @param currTile * * @param doc containing document * @param text the text this TextSpan holds * @param style associated style */ TextSpan(Container currTile, String text, Style style) { focusTraversable = false; if (style.alignment != Style.ALIGN_NONE) // keep PRE format as-is text = Convert.replace(text, " ", " "); this.text = text.toCharArray(); this.style = style; currTile.add(this); computeMaxWidth(text, this.text); } private int computeMaxWidthPerLine(String input, char[] inputChars) { FontMetrics fm = style.getFont().fm; int maxWidth=0; // in a pre tag, we need to get the width of the biggest text line int newPosition, position=0; while ((newPosition=input.indexOf('\n',position)) >= 0) { maxWidth = Math.max(maxWidth, fm.stringWidth(inputChars, position, newPosition-position)); position = newPosition+1; } if (position == 0) maxWidth = fm.stringWidth(input); return maxWidth; } private int computeMaxWordWidth(String input, char[] inputChars) { FontMetrics fm = style.getFont().fm; int maxWidth = 0; // in a standard text (free-flow), we get the maximum width of a single word int newPosition, position=0; input = input.replace('\n', ' '); while ((newPosition=input.indexOf(' ',position)) >= 0) { maxWidth = Math.max(maxWidth, fm.stringWidth(inputChars, position, newPosition-position)); position = newPosition+1; } if (position == 0) maxWidth = fm.stringWidth(input); return maxWidth; } private void computeMaxWidth(String input, char[] inputChars) { if (style.alignment == Style.ALIGN_NONE) // preformatted? maxWordWidth = computeMaxWidthPerLine(input, inputChars); else maxWordWidth = computeMaxWordWidth(input, inputChars); } protected void onFontChanged() { computeMaxWidth(new String(text), text); } public int getMaxWidth() { return maxWordWidth; } /** * Implement the layout method to break text into lines and * position those lines updating the layout context along the way. * @param lc layout context to update. */ public void layout(LayoutContext lc) { //String s = new String(text); setFont(style.getFont()); if (style.hasInitialValues()) { if (style.isDisjoint) lc.disjoin(); Style.Header h = style.getHeader(); if (h != null) { h.setFont(font); if (openIdent) lc.setIdentation(style.indent, true); parent.add((Control)h, lc.nextX,lc.nextY); lc.update(h.getWidth()); } } boolean isPRE = style.alignment == Style.ALIGN_NONE; if (isPRE) makePreformattedLines(lc); else makeLines(lc); boolean disjoin = isPRE; boolean wasDisjoin = lc.lastStyle != null && lc.lastStyle.alignment == Style.ALIGN_NONE; lc.lastStyle = style; if (disjoin && wasDisjoin) disjoin = false; else if (!disjoin && wasDisjoin) disjoin = true; //debug(lc,style,new String(text)); if (disjoin) lc.disjoin(); else if (!disjoin && wasDisjoin) lc.update(0); if (finishIdent) lc.setIdentation(0,false); } /** * Break up the text at word breaks, so the lines can fit (hopefully), * avoiding the need of a horizontal scrollbar. * * @param fm font metrics to use to perform text size calculations * @param lc layout context to update */ private void makeLines(LayoutContext lc) { //String s = new String(text); boolean isLastLine = false; int tries = 0; int curWidth,wordWidth,lineStart,wordEnd,wordStart,newWidth,textLen,beg,maxM2,spaceW; curWidth = lineStart = wordEnd = beg = 0; char []text = this.text; textLen = text.length; maxM2 = textLen-2; spaceW = fm.charWidth(' '); boolean glue = false; do { beg = wordEnd; // find next word for (wordStart=beg; ; wordStart++) { if (wordStart >= textLen) // trailing blanks? { if (tries > 0) // guich@tc114_81 { lc.disjoin(); addLine(lineStart, wordEnd, false, lc, false); tries = 0; } wordEnd = wordStart; isLastLine = true; break; } if (text[wordStart] != ' ') // is this the first non-space char? { wordEnd = wordStart; do { if (++wordEnd >= textLen) { isLastLine = true; break; } } while (text[wordEnd] != ' ' && text[wordEnd] != '/'); // loop until the next space/slash char // use slashes as word delimiters (useful for URL addresses). if (maxM2 > wordEnd && text[wordEnd] == '/' && text[wordEnd + 1] != '/') wordEnd++; break; } } if (!lc.atStart() && wordStart > 0 && text[wordStart-1] == ' ') // include previous space, if any and not at the start of the line wordStart--; wordWidth = fm.stringWidth(text, wordStart, wordEnd-wordStart); if (curWidth == 0) { lineStart = beg = wordStart; // no spaces at start of a line newWidth = wordWidth; } else newWidth = curWidth + wordWidth; if (lc.x+newWidth <= lc.maxWidth) // not reached the limit yet? curWidth = newWidth + spaceW; else // split: line length now exceeds the maximum allowed { //if (text[wordStart] == ' ') {wordStart++; wordWidth -= spaceW;} if (curWidth > 0) { // At here, wordStart and wordEnd refer to the word that overflows. So, we have to stop at the previous word wordEnd = wordStart; if (text[wordEnd-1] == ' ') // stop before leading space� wordEnd--; if (DEBUG) Vm.debug("1. \""+new String(text, lineStart, wordEnd-lineStart)+"\": "+curWidth+" "+isLastLine); addLine(lineStart, wordEnd, true, lc, glue); curWidth = 0; isLastLine = false; // must recompute the last line, since there's at least a word left. } else if (!lc.atStart()) // case of "this is a text at the end <b>oftheline</b>" -> oftheline will overflow the screen { if (++tries == 2) // avoid infinite loop when an empty line is found break; if (DEBUG) Vm.debug("2 "+isLastLine); // Nothing was gathered in, but the current line has characters left by a previous TextSpan. This occurs only once. addLine(0, 0, false, lc, glue); curWidth = 0; isLastLine = false; // must recompute the last line, since there's at least a word left. } else { // Rare case where we both have nothing gathered in, and the physical line is empty. Had this not been made, then we // woud have generated an extra-line at the top of the block. if (DEBUG) Vm.debug("3. \""+new String(text, lineStart, wordEnd-lineStart)+'"'); if (lineStart != wordEnd) addLine(lineStart, wordEnd, true, lc, glue); } glue = true; } } while (!isLastLine); if (wordEnd != lineStart) { //curWidth = fm.stringWidth(text, lineStart, wordEnd-lineStart); boolean split = lc.x+curWidth > lc.maxWidth && style.hasInitialValues() && style.isDisjoint; if (DEBUG) Vm.debug("4. \""+new String(text, lineStart, wordEnd-lineStart)+"\" "+split); addLine(lineStart, wordEnd, split, lc, glue); } } /** * Break up preformatted text at each user-entered "newline". * * @param fm font metrics to use to perform text size calculations * @param lc layout context to update */ private void makePreformattedLines(LayoutContext lc) { int end = -1; int max = text.length; boolean isLastLine = false; do { int beg = end+1; do { if (++end >= max) { isLastLine = true; break; } } while (text[end] != '\n'); addLine(beg, end, true,lc,false); } while (!isLastLine); } /** * Add a line to the list of lines. * * @param name description */ private void addLine(int textStart, int textEnd, boolean isDisjoin, LayoutContext lc, boolean glue) { //String s = new String(text, textStart,textEnd-textStart); int xx = style.getControlAlignment(true); //flsobral@tc126: use the new method to get the line's alignment. if (xx == 0 || xx == Control.LEFT) xx = lc.nextX; //flsobral@tc126: always use nextX when left aligned. int yy = lc.nextY; if (glue) yy -= Edit.prefH; if (lc.atStart()) yy += style.topMargin; TextLine l = new TextLine(textStart, textEnd); parent.add(l); l.setFont(font); if (style.alignment == Style.ALIGN_LEFT) lc.verify(l.getPreferredWidth()); l.setRect(xx,yy,PREFERRED,PREFERRED); if (style.alignment == Style.ALIGN_CENTER || style.alignment == Style.ALIGN_RIGHT) l.setRect(xx, KEEP, KEEP, KEEP, lc.parentContainer); //flsobral@tc126: make line relative to the layout context parent container when aligned with center or right. if (isDisjoin) lc.disjoin(); else lc.update(l.getWidth()); lc.lastControl = l; } // debuggint routines /* static int conta; static void debug(LayoutContext lc, Style style, String text) { System.err.println(conta++ +" "+positionName(lc.nextX + ((lc.nextX == LEFT) ? style.indent : 0))+" "+positionName(lc.nextY + ((lc.nextY == AFTER) ? style.topMargin : 0))+" "+style.alignment+"/"+lc.x+" "+text); } static String positionName(int xx) { String s = "???"; switch (xx / 1000000 * 1000000) { case PREFERRED: s = "PREFERRED+"+(xx-PREFERRED); break; case LEFT: s = "LEFT+"+(xx-LEFT); break; case CENTER: s = "CENTER+"+(xx-CENTER); break; case RIGHT: s = "RIGHT+"+(xx-RIGHT); break; case TOP: s = "TOP+"+(xx-TOP); break; case BOTTOM: s = "BOTTOM+"+(xx-BOTTOM); break; case FILL: s = "FILL+"+(xx-FILL); break; case BEFORE: s = "BEFORE+"+(xx-BEFORE); break; case SAME: s = "SAME+"+(xx-SAME); break; case AFTER: s = "AFTER+"+(xx-AFTER); break; case FIT: s = "FIT+"+(xx-FIT); break; case CENTER_OF: s = "CENTER_OF+"+(xx-CENTER_OF); break; case RIGHT_OF: s = "RIGHT_OF+"+(xx-RIGHT_OF); break; case BOTTOM_OF: s = "BOTTOM_OF+"+(xx-BOTTOM_OF); break; } if (s.endsWith("+0")) s = s.substring(0,s.length()-2); return s; }*/ }