Package com.google.minijoe.html

Source Code of com.google.minijoe.html.TextFragmentWidget

// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.minijoe.html;

import com.google.minijoe.common.Util;
import com.google.minijoe.html.css.Style;
import com.google.minijoe.html.uibase.Widget;

import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;

/**
* Widget representing a (HTML) text fragment.
*
* TODO: Consider caching all line breaking opportunities in a string
* buffer or array
*
* @author Stefan Haustein
*/
public class TextFragmentWidget extends Widget {
 
  /** Element this widget corresponds to */
  Element element;
  private boolean focusable;
  String text;
  short[] indices;
  private int firstLineYOffset;
  private int mainYOffset;
  private int lastLineYOffset;

  /**
   * Creates a new TextFragmentWidget
   */
  public TextFragmentWidget(Element element, String text, boolean focusable) {
    this.element = element;
    this.text = text;
    this.focusable = focusable;
    if (focusable && isFocused()) {
      element.setFocused();
    }
  }
 
  /**
   * This widget can be traversed.
   */
  public boolean isFocusable() {
    return focusable;
  }

  /**
   * Returns the number of lines for the current layout state. If called before
   * the first call of doLayout(), 1 is returned.
   */
  public int getLineCount() {
    return indices == null ? 1 : indices.length / 3;
  }

  /**
   * Returns the width of the nth line in pixels.
   */
  public int getLineWidth(int n) {
    return indices == null ? getFont().stringWidth(text) :
      getFont().substringWidth(text, indices[n * 3], indices[n * 3 + 1]);
  }

  /**
   * Returns the GoogleFont associated with the computed style for the element
   * owning this text fragment.
   */
  public Font getFont() {
    return element.getComputedStyle().getFont();
  }

  /**
   * Layouts this text fragment.
   *
   * Precondition: x, y, w are set correctly.
   *
   * @param index index of this widget in the parent widget
   * @param x0 start position for the first character, relative to getX()
   * @param breakPos first line break position.
   * @param lineHeight height of the current line
   */
  public int doLayout(int myIndex, LayoutContext borders, int breakPos,
      int lineStartIndex, int insertionIndex) {

    firstLineYOffset = 0;
    mainYOffset = 0;
    lastLineYOffset = 0;
   
    BlockWidget parent = (BlockWidget) getParent();

    Font font = getFont();
    int maxWidth = borders.getMaxWidth();
    int fontHeight = font.getHeight();

    int availableWidth = borders.getHorizontalSpace(fontHeight);

    // breakpos invalid?
    if (breakPos == -1) {
      breakPos = Math.max(0,
          findBreakPosition(parent, myIndex, 0, availableWidth,
              availableWidth == maxWidth));
    }
   
    int len = text.length();
   
    if (breakPos > len) {
      int w = Math.min(font.stringWidth(text), maxWidth);
      borders.placeBox(w, fontHeight, Style.NONE, 0);
     
      indices = null;
      setX(getX() + borders.getBoxX());
      setWidth(w);
      setHeight(fontHeight);
      return breakPos - len;
    }

    StringBuffer buf = new StringBuffer();
    int lastBreak = 0;

    int h = Math.max(borders.getLineHeight(), fontHeight);
    borders.setLineHeight(h);
    firstLineYOffset = borders.getAdjustmentY(h - fontHeight);
    mainYOffset = h - fontHeight - firstLineYOffset;
   
    do {
      int end = breakPos;
      if (end > lastBreak && text.charAt(end - 1) <= ' ') {
        end--;
      }

      buf.append((char) lastBreak);
      buf.append((char) (end - lastBreak));
     
      int w = Math.min(font.substringWidth(text, lastBreak, end - lastBreak),  maxWidth);
     
      if (lineStartIndex != insertionIndex) {
        ((BlockWidget) getParent()).adjustLine(lineStartIndex,
            insertionIndex, borders);
        lineStartIndex = insertionIndex;
      }
     
      borders.placeBox(w, fontHeight, borders.getLineHeight(), 0);
      buf.append((char) (borders.getBoxX() +
          borders.getAdjustmentX(availableWidth - w)));
      borders.advance(borders.getLineHeight());
     
      lastBreak = breakPos;
     
      availableWidth = borders.getHorizontalSpace(fontHeight);
      breakPos = Math.max(lastBreak, findBreakPosition(parent, myIndex, lastBreak, availableWidth,
          availableWidth == maxWidth));
     
      h += fontHeight;
    } while (breakPos <= len);

    buf.append((char) lastBreak);
    buf.append((char) (text.length() - lastBreak));
    int w = Math.min(font.substringWidth(text, lastBreak,
        text.length() - lastBreak), borders.getHorizontalSpace(fontHeight));
   
    borders.placeBox(w, fontHeight, Style.NONE, 0);
    buf.append((char) borders.getBoxX());
   
    indices = new short[buf.length()];
    for (int i = 0; i < indices.length; i++) {
      indices[i] = (short) buf.charAt(i);
    }
   
    setHeight(h);
   
    return breakPos - len;
  }

  /**
   * Draws the text.
   */
  public void drawContent(Graphics g, int dx, int dy) {
    // System.out.println("drawing tfw: "+text);
    Style style = element.getComputedStyle();
    Font font = style.getFont();
    int textColor = style.getValue(Style.COLOR);
    if (isFocused() && !element.isFocused()) {
      element.setFocused();
    }
    boolean focus = element.isFocused();

    if (indices == null) {
      if (focus) {
      Skin.get().fillFocusRect(g, dx, dy,
            font.stringWidth(text), font.getHeight());
      }
      g.setColor(textColor);
      g.setFont(font);
      g.drawString(text, dx, dy, Graphics.TOP | Graphics.LEFT);
    } else {
      int y = dy + firstLineYOffset;
      int clipY = g.getClipY();
      int clipH = g.getClipHeight();
      int fh = font.getHeight();

      for (int i = 0; i < indices.length; i += 3) {
        if (clipY < y + fh &&  y < clipY + clipH) {
          int start = indices[i];
          int len = indices[i + 1];

          if (focus) {
            Skin.get().fillFocusRect(g, dx + indices[i + 2], y,
                font.substringWidth(text, start, len), font.getHeight());
          }
          g.setColor(textColor);
          g.setFont(font);
          g.drawSubstring(text, start, len, dx + indices[i + 2], y, Graphics.TOP | Graphics.LEFT);
        }
        y += fh;
        if (i == 0) {
          y += mainYOffset;
        }
        if (i == indices.length - 6) {
          y += lastLineYOffset;
        }
      }
    }
   
    if (HtmlWidget.debug == this) {
      g.setColor(0x00ff00);
      g.drawRect(dx, dy, getWidth() - 1, getHeight() - 1);
      if (getLineCount() > 1) {
      
        int fh = font.getHeight();
        int y = dy;
        g.setColor(0x0ff0000);
        g.drawLine(dx, y, dx, y + firstLineYOffset);
        y += firstLineYOffset;
       
        for (int i = 0; i < indices.length; i += 3) {
          g.setColor(0x0000ff);
          g.drawRect(dx + indices[i + 2], y,
              font.substringWidth(text, indices[i], indices[i + 1]), fh - 1);
          y += fh;
          if (i == 0) {
            g.setColor(0x0ff0000);
            g.drawLine(dx + 2, y, dx + 2, y + mainYOffset);
            y += mainYOffset;
          }
          if (i == indices.length - 6) {
            g.setColor(0x0ff0000);
            g.drawLine(dx + 4, y, dx + 4, y + lastLineYOffset);
            y += lastLineYOffset;
          }
        }
      }
    }
  }

  /**
   * Finds a suitable line break position in a run of TextFragmentWidgets,
   * starting at character index startCharIndex (relative to the start of
   * the first child). Assumes no kerning for performance reasons,
   * which may result in breaking the line slightly too early.
   *
   * @param myIndex the child index of this text fragment in the parent widget
   * @param startCharIndex start character index
   * @param maxWidth maximum line width
   * @param force if true, the next break position is returned, even if it does
   *        not fit within maxWidth
   * @return character index for break (first character on new line); May be
   *         outside of this TextFragmentWidget;
   *         Integer.MAX_VALUE if no break is necessary;
   *         Integer.MIN_VALUE if force is false and no suitable break position
   *         could be found
   */
  final int findBreakPosition(BlockWidget parent,
      int myIndex, int startCharIndex, int maxWidth, boolean force) {   
    int len = text.length();
   
    if (startCharIndex >= len) {
      TextFragmentWidget next = getNextSibling(parent, myIndex);
      if (next == null) {
        return Integer.MAX_VALUE;
      }
      int result = next.findBreakPosition(parent,
          myIndex + 1, startCharIndex - len, maxWidth, force);
     
      return result > Integer.MIN_VALUE && result < Integer.MAX_VALUE
          ? result + len : result;
    }
    char startChar = text.charAt(startCharIndex);
    return findBreakPosition(parent, myIndex, startChar, startCharIndex + 1,
        getFont().charWidth(startChar)
        force ? Integer.MAX_VALUE : Integer.MIN_VALUE, maxWidth);
  }
 
  /**
   * Internal helper method for the package visible method with the same name.
   * Delegates break position search to next sibling if necessary.
   */
  private final int findBreakPosition(BlockWidget parent, int myIndex,
      char currentChar, int nextCharIndex, int w, int bestPos, int maxWidth) {
    int[] widths = element.getComputedStyle().getCharWidths();
    Font font = getFont();
    int len = text.length();
   
    while (true) {
      if (currentChar == '\n') {
        return nextCharIndex;
      }
      if (w > maxWidth && bestPos != Integer.MAX_VALUE) {
        return bestPos;
      }
      if (nextCharIndex >= len) {
        break;
      }
      char nextChar = text.charAt(nextCharIndex);
      if (Util.canBreak(currentChar, nextChar)) {
        bestPos = nextCharIndex;
      }
      nextCharIndex++;
      currentChar = nextChar;
      // we do not consider kerning for performance reasons. This may result in
      // breaking the line slightly too early, which should be fine.
      // TODO(haustein) consider summing up word lengths instead
      w += currentChar < widths.length ? widths[currentChar] :
        font.charWidth(currentChar);
    }

    TextFragmentWidget next = getNextSibling(parent, myIndex);
    if (next == null) {
      return w <= maxWidth ? Integer.MAX_VALUE : bestPos;
    }
    int result = next.findBreakPosition(parent,
        myIndex + 1, currentChar, nextCharIndex - len, w,
        bestPos == Integer.MIN_VALUE || bestPos == Integer.MAX_VALUE
        ? bestPos : bestPos - len, maxWidth);
   
    return Integer.MIN_VALUE < result && result < Integer.MAX_VALUE
        ? result + len : result;
  }
 
  /**
   * Returns the next TextFragmentWidget sibling or null, if the next widget
   * is not an instance of TextFragmentWidget or this widget is the last child.
   * 
   * @param myIndex the index of this TextFragmentWidget in the parent widget
   * @return the next TextFragmentWidget if available; otherwise null
   */
  TextFragmentWidget getNextSibling(BlockWidget parent, int myIndex) {
  
    Util.assertTrue(parent.children.elementAt(myIndex) == this);
    if (myIndex + 1 >= parent.children.size()) {
      return null;
    }
    Object next = parent.children.elementAt(myIndex + 1);
    return next instanceof TextFragmentWidget 
        ? (TextFragmentWidget) next : null;
  }
 
  protected void handleFocusChange(boolean focused) {
    if (isFocusable() && focused) {
      element.setFocused();
    }
    invalidate(false);
  }

  /**
   * Returns the element owning the text fragment.
   */
  public Element getElement() {
    return element;
  }

  /**
   * Adjusts the vertical and horizontal alignment for the last line.
   *
   * @param indent horizontal indent for the last line
   * @param lineH line height of the last line
   * @param context current LayoutContext
   */
  public void adjustLastLine(int indent, int lineH, LayoutContext context) {
    lastLineYOffset = context.getAdjustmentY(lineH - getFont().getHeight());
    setHeight(getHeight() + lastLineYOffset);
    indices[indices.length - 1] += indent;
  }

  /**
   * Dumps members for debugging.
   */
  public void handleDebug() {
    System.out.print("Element Path: ");
    element.dumpPath();
    System.out.print("Computed Style: ");
    element.dumpStyle();
   
    System.out.println();
    System.out.println("x: " + getX() + " y: " + getY() +
        " w: " + getWidth() + " h: " + getHeight());
    System.out.println("lh " + getFont().getHeight() +
        " YOffs0: " + firstLineYOffset + " mainYOffs: " + mainYOffset +
        " lastYOffs: " + lastLineYOffset);
  }

  public void doLayout(int maxWidth) {
  // Do nothing -- handled by BlockWidget.
  }
}
TOP

Related Classes of com.google.minijoe.html.TextFragmentWidget

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.