0N/A/*
3261N/A * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/Apackage javax.swing.text;
0N/A
0N/Aimport java.awt.*;
0N/Aimport java.text.BreakIterator;
0N/Aimport javax.swing.event.*;
0N/Aimport java.util.BitSet;
0N/Aimport java.util.Locale;
0N/A
1087N/Aimport javax.swing.UIManager;
0N/Aimport sun.swing.SwingUtilities2;
2450N/Aimport static sun.swing.SwingUtilities2.IMPLIED_CR;
0N/A
0N/A/**
0N/A * A GlyphView is a styled chunk of text that represents a view
0N/A * mapped over an element in the text model. This view is generally
0N/A * responsible for displaying text glyphs using character level
0N/A * attributes in some way.
0N/A * An implementation of the GlyphPainter class is used to do the
0N/A * actual rendering and model/view translations. This separates
0N/A * rendering from layout and management of the association with
0N/A * the model.
0N/A * <p>
0N/A * The view supports breaking for the purpose of formatting.
0N/A * The fragments produced by breaking share the view that has
0N/A * primary responsibility for the element (i.e. they are nested
0N/A * classes and carry only a small amount of state of their own)
0N/A * so they can share its resources.
0N/A * <p>
0N/A * Since this view
0N/A * represents text that may have tabs embedded in it, it implements the
0N/A * <code>TabableView</code> interface. Tabs will only be
0N/A * expanded if this view is embedded in a container that does
0N/A * tab expansion. ParagraphView is an example of a container
0N/A * that does tab expansion.
0N/A * <p>
0N/A *
0N/A * @since 1.3
0N/A *
0N/A * @author Timothy Prinzing
0N/A */
0N/Apublic class GlyphView extends View implements TabableView, Cloneable {
0N/A
0N/A /**
0N/A * Constructs a new view wrapped on an element.
0N/A *
0N/A * @param elem the element
0N/A */
0N/A public GlyphView(Element elem) {
0N/A super(elem);
0N/A offset = 0;
0N/A length = 0;
0N/A Element parent = elem.getParentElement();
0N/A AttributeSet attr = elem.getAttributes();
0N/A
0N/A // if there was an implied CR
0N/A impliedCR = (attr != null && attr.getAttribute(IMPLIED_CR) != null &&
0N/A // if this is non-empty paragraph
0N/A parent != null && parent.getElementCount() > 1);
0N/A skipWidth = elem.getName().equals("br");
0N/A }
0N/A
0N/A /**
0N/A * Creates a shallow copy. This is used by the
0N/A * createFragment and breakView methods.
0N/A *
0N/A * @return the copy
0N/A */
0N/A protected final Object clone() {
0N/A Object o;
0N/A try {
0N/A o = super.clone();
0N/A } catch (CloneNotSupportedException cnse) {
0N/A o = null;
0N/A }
0N/A return o;
0N/A }
0N/A
0N/A /**
0N/A * Fetch the currently installed glyph painter.
0N/A * If a painter has not yet been installed, and
0N/A * a default was not yet needed, null is returned.
0N/A */
0N/A public GlyphPainter getGlyphPainter() {
0N/A return painter;
0N/A }
0N/A
0N/A /**
0N/A * Sets the painter to use for rendering glyphs.
0N/A */
0N/A public void setGlyphPainter(GlyphPainter p) {
0N/A painter = p;
0N/A }
0N/A
0N/A /**
0N/A * Fetch a reference to the text that occupies
0N/A * the given range. This is normally used by
0N/A * the GlyphPainter to determine what characters
0N/A * it should render glyphs for.
0N/A *
0N/A * @param p0 the starting document offset >= 0
0N/A * @param p1 the ending document offset >= p0
0N/A * @return the <code>Segment</code> containing the text
0N/A */
0N/A public Segment getText(int p0, int p1) {
0N/A // When done with the returned Segment it should be released by
0N/A // invoking:
0N/A // SegmentCache.releaseSharedSegment(segment);
0N/A Segment text = SegmentCache.getSharedSegment();
0N/A try {
0N/A Document doc = getDocument();
0N/A doc.getText(p0, p1 - p0, text);
0N/A } catch (BadLocationException bl) {
0N/A throw new StateInvariantError("GlyphView: Stale view: " + bl);
0N/A }
0N/A return text;
0N/A }
0N/A
0N/A /**
0N/A * Fetch the background color to use to render the
0N/A * glyphs. If there is no background color, null should
0N/A * be returned. This is implemented to call
0N/A * <code>StyledDocument.getBackground</code> if the associated
0N/A * document is a styled document, otherwise it returns null.
0N/A */
0N/A public Color getBackground() {
0N/A Document doc = getDocument();
0N/A if (doc instanceof StyledDocument) {
0N/A AttributeSet attr = getAttributes();
0N/A if (attr.isDefined(StyleConstants.Background)) {
0N/A return ((StyledDocument)doc).getBackground(attr);
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A /**
0N/A * Fetch the foreground color to use to render the
0N/A * glyphs. If there is no foreground color, null should
0N/A * be returned. This is implemented to call
0N/A * <code>StyledDocument.getBackground</code> if the associated
0N/A * document is a StyledDocument. If the associated document
0N/A * is not a StyledDocument, the associated components foreground
0N/A * color is used. If there is no associated component, null
0N/A * is returned.
0N/A */
0N/A public Color getForeground() {
0N/A Document doc = getDocument();
0N/A if (doc instanceof StyledDocument) {
0N/A AttributeSet attr = getAttributes();
0N/A return ((StyledDocument)doc).getForeground(attr);
0N/A }
0N/A Component c = getContainer();
0N/A if (c != null) {
0N/A return c.getForeground();
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A /**
0N/A * Fetch the font that the glyphs should be based
0N/A * upon. This is implemented to call
0N/A * <code>StyledDocument.getFont</code> if the associated
0N/A * document is a StyledDocument. If the associated document
0N/A * is not a StyledDocument, the associated components font
0N/A * is used. If there is no associated component, null
0N/A * is returned.
0N/A */
0N/A public Font getFont() {
0N/A Document doc = getDocument();
0N/A if (doc instanceof StyledDocument) {
0N/A AttributeSet attr = getAttributes();
0N/A return ((StyledDocument)doc).getFont(attr);
0N/A }
0N/A Component c = getContainer();
0N/A if (c != null) {
0N/A return c.getFont();
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A /**
0N/A * Determine if the glyphs should be underlined. If true,
0N/A * an underline should be drawn through the baseline.
0N/A */
0N/A public boolean isUnderline() {
0N/A AttributeSet attr = getAttributes();
0N/A return StyleConstants.isUnderline(attr);
0N/A }
0N/A
0N/A /**
0N/A * Determine if the glyphs should have a strikethrough
0N/A * line. If true, a line should be drawn through the center
0N/A * of the glyphs.
0N/A */
0N/A public boolean isStrikeThrough() {
0N/A AttributeSet attr = getAttributes();
0N/A return StyleConstants.isStrikeThrough(attr);
0N/A }
0N/A
0N/A /**
0N/A * Determine if the glyphs should be rendered as superscript.
0N/A */
0N/A public boolean isSubscript() {
0N/A AttributeSet attr = getAttributes();
0N/A return StyleConstants.isSubscript(attr);
0N/A }
0N/A
0N/A /**
0N/A * Determine if the glyphs should be rendered as subscript.
0N/A */
0N/A public boolean isSuperscript() {
0N/A AttributeSet attr = getAttributes();
0N/A return StyleConstants.isSuperscript(attr);
0N/A }
0N/A
0N/A /**
0N/A * Fetch the TabExpander to use if tabs are present in this view.
0N/A */
0N/A public TabExpander getTabExpander() {
0N/A return expander;
0N/A }
0N/A
0N/A /**
0N/A * Check to see that a glyph painter exists. If a painter
0N/A * doesn't exist, a default glyph painter will be installed.
0N/A */
0N/A protected void checkPainter() {
0N/A if (painter == null) {
0N/A if (defaultPainter == null) {
0N/A // the classname should probably come from a property file.
0N/A String classname = "javax.swing.text.GlyphPainter1";
0N/A try {
0N/A Class c;
0N/A ClassLoader loader = getClass().getClassLoader();
0N/A if (loader != null) {
0N/A c = loader.loadClass(classname);
0N/A } else {
0N/A c = Class.forName(classname);
0N/A }
0N/A Object o = c.newInstance();
0N/A if (o instanceof GlyphPainter) {
0N/A defaultPainter = (GlyphPainter) o;
0N/A }
0N/A } catch (Throwable e) {
0N/A throw new StateInvariantError("GlyphView: Can't load glyph painter: "
0N/A + classname);
0N/A }
0N/A }
0N/A setGlyphPainter(defaultPainter.getPainter(this, getStartOffset(),
0N/A getEndOffset()));
0N/A }
0N/A }
0N/A
0N/A // --- TabableView methods --------------------------------------
0N/A
0N/A /**
0N/A * Determines the desired span when using the given
0N/A * tab expansion implementation.
0N/A *
0N/A * @param x the position the view would be located
0N/A * at for the purpose of tab expansion >= 0.
0N/A * @param e how to expand the tabs when encountered.
0N/A * @return the desired span >= 0
0N/A * @see TabableView#getTabbedSpan
0N/A */
0N/A public float getTabbedSpan(float x, TabExpander e) {
0N/A checkPainter();
0N/A
0N/A TabExpander old = expander;
0N/A expander = e;
0N/A
0N/A if (expander != old) {
0N/A // setting expander can change horizontal span of the view,
0N/A // so we have to call preferenceChanged()
0N/A preferenceChanged(null, true, false);
0N/A }
0N/A
0N/A this.x = (int) x;
0N/A int p0 = getStartOffset();
0N/A int p1 = getEndOffset();
0N/A float width = painter.getSpan(this, p0, p1, expander, x);
0N/A return width;
0N/A }
0N/A
0N/A /**
0N/A * Determines the span along the same axis as tab
0N/A * expansion for a portion of the view. This is
0N/A * intended for use by the TabExpander for cases
0N/A * where the tab expansion involves aligning the
0N/A * portion of text that doesn't have whitespace
0N/A * relative to the tab stop. There is therefore
0N/A * an assumption that the range given does not
0N/A * contain tabs.
0N/A * <p>
0N/A * This method can be called while servicing the
0N/A * getTabbedSpan or getPreferredSize. It has to
0N/A * arrange for its own text buffer to make the
0N/A * measurements.
0N/A *
0N/A * @param p0 the starting document offset >= 0
0N/A * @param p1 the ending document offset >= p0
0N/A * @return the span >= 0
0N/A */
0N/A public float getPartialSpan(int p0, int p1) {
0N/A checkPainter();
0N/A float width = painter.getSpan(this, p0, p1, expander, x);
0N/A return width;
0N/A }
0N/A
0N/A // --- View methods ---------------------------------------------
0N/A
0N/A /**
0N/A * Fetches the portion of the model that this view is responsible for.
0N/A *
0N/A * @return the starting offset into the model
0N/A * @see View#getStartOffset
0N/A */
0N/A public int getStartOffset() {
0N/A Element e = getElement();
0N/A return (length > 0) ? e.getStartOffset() + offset : e.getStartOffset();
0N/A }
0N/A
0N/A /**
0N/A * Fetches the portion of the model that this view is responsible for.
0N/A *
0N/A * @return the ending offset into the model
0N/A * @see View#getEndOffset
0N/A */
0N/A public int getEndOffset() {
0N/A Element e = getElement();
0N/A return (length > 0) ? e.getStartOffset() + offset + length : e.getEndOffset();
0N/A }
0N/A
0N/A /**
0N/A * Lazily initializes the selections field
0N/A */
0N/A private void initSelections(int p0, int p1) {
0N/A int viewPosCount = p1 - p0 + 1;
0N/A if (selections == null || viewPosCount > selections.length) {
0N/A selections = new byte[viewPosCount];
0N/A return;
0N/A }
0N/A for (int i = 0; i < viewPosCount; selections[i++] = 0);
0N/A }
0N/A
0N/A /**
0N/A * Renders a portion of a text style run.
0N/A *
0N/A * @param g the rendering surface to use
0N/A * @param a the allocated region to render into
0N/A */
0N/A public void paint(Graphics g, Shape a) {
0N/A checkPainter();
0N/A
0N/A boolean paintedText = false;
0N/A Component c = getContainer();
0N/A int p0 = getStartOffset();
0N/A int p1 = getEndOffset();
0N/A Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
0N/A Color bg = getBackground();
0N/A Color fg = getForeground();
0N/A
1087N/A if (c != null && ! c.isEnabled()) {
1087N/A fg = (c instanceof JTextComponent ?
1087N/A ((JTextComponent)c).getDisabledTextColor() :
1087N/A UIManager.getColor("textInactiveText"));
0N/A }
0N/A if (bg != null) {
0N/A g.setColor(bg);
0N/A g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
0N/A }
0N/A if (c instanceof JTextComponent) {
0N/A JTextComponent tc = (JTextComponent) c;
0N/A Highlighter h = tc.getHighlighter();
0N/A if (h instanceof LayeredHighlighter) {
0N/A ((LayeredHighlighter)h).paintLayeredHighlights
0N/A (g, p0, p1, a, tc, this);
0N/A }
0N/A }
0N/A
0N/A if (Utilities.isComposedTextElement(getElement())) {
0N/A Utilities.paintComposedText(g, a.getBounds(), this);
0N/A paintedText = true;
0N/A } else if(c instanceof JTextComponent) {
0N/A JTextComponent tc = (JTextComponent) c;
0N/A Color selFG = tc.getSelectedTextColor();
0N/A
0N/A if (// there's a highlighter (bug 4532590), and
0N/A (tc.getHighlighter() != null) &&
0N/A // selected text color is different from regular foreground
0N/A (selFG != null) && !selFG.equals(fg)) {
0N/A
0N/A Highlighter.Highlight[] h = tc.getHighlighter().getHighlights();
0N/A if(h.length != 0) {
0N/A boolean initialized = false;
0N/A int viewSelectionCount = 0;
0N/A for (int i = 0; i < h.length; i++) {
0N/A Highlighter.Highlight highlight = h[i];
0N/A int hStart = highlight.getStartOffset();
0N/A int hEnd = highlight.getEndOffset();
0N/A if (hStart > p1 || hEnd < p0) {
0N/A // the selection is out of this view
0N/A continue;
0N/A }
0N/A if (!SwingUtilities2.useSelectedTextColor(highlight, tc)) {
0N/A continue;
0N/A }
0N/A if (hStart <= p0 && hEnd >= p1){
0N/A // the whole view is selected
0N/A paintTextUsingColor(g, a, selFG, p0, p1);
0N/A paintedText = true;
0N/A break;
0N/A }
0N/A // the array is lazily created only when the view
0N/A // is partially selected
0N/A if (!initialized) {
0N/A initSelections(p0, p1);
0N/A initialized = true;
0N/A }
0N/A hStart = Math.max(p0, hStart);
0N/A hEnd = Math.min(p1, hEnd);
0N/A paintTextUsingColor(g, a, selFG, hStart, hEnd);
0N/A // the array represents view positions [0, p1-p0+1]
0N/A // later will iterate this array and sum its
0N/A // elements. Positions with sum == 0 are not selected.
0N/A selections[hStart-p0]++;
0N/A selections[hEnd-p0]--;
0N/A
0N/A viewSelectionCount++;
0N/A }
0N/A
0N/A if (!paintedText && viewSelectionCount > 0) {
0N/A // the view is partially selected
0N/A int curPos = -1;
0N/A int startPos = 0;
0N/A int viewLen = p1 - p0;
0N/A while (curPos++ < viewLen) {
0N/A // searching for the next selection start
0N/A while(curPos < viewLen &&
0N/A selections[curPos] == 0) curPos++;
0N/A if (startPos != curPos) {
0N/A // paint unselected text
0N/A paintTextUsingColor(g, a, fg,
0N/A p0 + startPos, p0 + curPos);
0N/A }
0N/A int checkSum = 0;
0N/A // searching for next start position of unselected text
0N/A while (curPos < viewLen &&
0N/A (checkSum += selections[curPos]) != 0) curPos++;
0N/A startPos = curPos;
0N/A }
0N/A paintedText = true;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A if(!paintedText)
0N/A paintTextUsingColor(g, a, fg, p0, p1);
0N/A }
0N/A
0N/A /**
0N/A * Paints the specified region of text in the specified color.
0N/A */
0N/A final void paintTextUsingColor(Graphics g, Shape a, Color c, int p0, int p1) {
0N/A // render the glyphs
0N/A g.setColor(c);
0N/A painter.paint(this, g, a, p0, p1);
0N/A
0N/A // render underline or strikethrough if set.
0N/A boolean underline = isUnderline();
0N/A boolean strike = isStrikeThrough();
0N/A if (underline || strike) {
0N/A // calculate x coordinates
0N/A Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
0N/A View parent = getParent();
0N/A if ((parent != null) && (parent.getEndOffset() == p1)) {
0N/A // strip whitespace on end
0N/A Segment s = getText(p0, p1);
0N/A while (Character.isWhitespace(s.last())) {
0N/A p1 -= 1;
0N/A s.count -= 1;
0N/A }
0N/A SegmentCache.releaseSharedSegment(s);
0N/A }
0N/A int x0 = alloc.x;
0N/A int p = getStartOffset();
0N/A if (p != p0) {
0N/A x0 += (int) painter.getSpan(this, p, p0, getTabExpander(), x0);
0N/A }
0N/A int x1 = x0 + (int) painter.getSpan(this, p0, p1, getTabExpander(), x0);
0N/A
0N/A // calculate y coordinate
0N/A int y = alloc.y + alloc.height - (int) painter.getDescent(this);
0N/A if (underline) {
0N/A int yTmp = y + 1;
0N/A g.drawLine(x0, yTmp, x1, yTmp);
0N/A }
0N/A if (strike) {
0N/A // move y coordinate above baseline
0N/A int yTmp = y - (int) (painter.getAscent(this) * 0.3f);
0N/A g.drawLine(x0, yTmp, x1, yTmp);
0N/A }
0N/A
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Determines the minimum span for this view along an axis.
0N/A *
0N/A * <p>This implementation returns the longest non-breakable area within
0N/A * the view as a minimum span for {@code View.X_AXIS}.</p>
0N/A *
0N/A * @param axis may be either {@code View.X_AXIS} or {@code View.Y_AXIS}
0N/A * @return the minimum span the view can be rendered into
0N/A * @throws IllegalArgumentException if the {@code axis} parameter is invalid
0N/A * @see javax.swing.text.View#getMinimumSpan
0N/A */
0N/A @Override
0N/A public float getMinimumSpan(int axis) {
3954N/A switch (axis) {
3954N/A case View.X_AXIS:
3954N/A if (minimumSpan < 0) {
3954N/A minimumSpan = 0;
3954N/A int p0 = getStartOffset();
3954N/A int p1 = getEndOffset();
3954N/A while (p1 > p0) {
3954N/A int breakSpot = getBreakSpot(p0, p1);
3954N/A if (breakSpot == BreakIterator.DONE) {
3954N/A // the rest of the view is non-breakable
3954N/A breakSpot = p0;
3954N/A }
3954N/A minimumSpan = Math.max(minimumSpan,
3954N/A getPartialSpan(breakSpot, p1));
3954N/A // Note: getBreakSpot returns the *last* breakspot
3954N/A p1 = breakSpot - 1;
3954N/A }
3954N/A }
3954N/A return minimumSpan;
3954N/A case View.Y_AXIS:
3954N/A return super.getMinimumSpan(axis);
3954N/A default:
3954N/A throw new IllegalArgumentException("Invalid axis: " + axis);
3954N/A }
0N/A }
0N/A
0N/A /**
0N/A * Determines the preferred span for this view along an
0N/A * axis.
0N/A *
0N/A * @param axis may be either View.X_AXIS or View.Y_AXIS
0N/A * @return the span the view would like to be rendered into >= 0.
0N/A * Typically the view is told to render into the span
0N/A * that is returned, although there is no guarantee.
0N/A * The parent may choose to resize or break the view.
0N/A */
0N/A public float getPreferredSpan(int axis) {
0N/A if (impliedCR) {
0N/A return 0;
0N/A }
0N/A checkPainter();
0N/A int p0 = getStartOffset();
0N/A int p1 = getEndOffset();
0N/A switch (axis) {
0N/A case View.X_AXIS:
0N/A if (skipWidth) {
0N/A return 0;
0N/A }
0N/A return painter.getSpan(this, p0, p1, expander, this.x);
0N/A case View.Y_AXIS:
0N/A float h = painter.getHeight(this);
0N/A if (isSuperscript()) {
0N/A h += h/3;
0N/A }
0N/A return h;
0N/A default:
0N/A throw new IllegalArgumentException("Invalid axis: " + axis);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Determines the desired alignment for this view along an
0N/A * axis. For the label, the alignment is along the font
0N/A * baseline for the y axis, and the superclasses alignment
0N/A * along the x axis.
0N/A *
0N/A * @param axis may be either View.X_AXIS or View.Y_AXIS
0N/A * @return the desired alignment. This should be a value
0N/A * between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
0N/A * origin and 1.0 indicates alignment to the full span
0N/A * away from the origin. An alignment of 0.5 would be the
0N/A * center of the view.
0N/A */
0N/A public float getAlignment(int axis) {
0N/A checkPainter();
0N/A if (axis == View.Y_AXIS) {
0N/A boolean sup = isSuperscript();
0N/A boolean sub = isSubscript();
0N/A float h = painter.getHeight(this);
0N/A float d = painter.getDescent(this);
0N/A float a = painter.getAscent(this);
0N/A float align;
0N/A if (sup) {
0N/A align = 1.0f;
0N/A } else if (sub) {
0N/A align = (h > 0) ? (h - (d + (a / 2))) / h : 0;
0N/A } else {
0N/A align = (h > 0) ? (h - d) / h : 0;
0N/A }
0N/A return align;
0N/A }
0N/A return super.getAlignment(axis);
0N/A }
0N/A
0N/A /**
0N/A * Provides a mapping from the document model coordinate space
0N/A * to the coordinate space of the view mapped to it.
0N/A *
0N/A * @param pos the position to convert >= 0
0N/A * @param a the allocated region to render into
0N/A * @param b either <code>Position.Bias.Forward</code>
0N/A * or <code>Position.Bias.Backward</code>
0N/A * @return the bounding box of the given position
0N/A * @exception BadLocationException if the given position does not represent a
0N/A * valid location in the associated document
0N/A * @see View#modelToView
0N/A */
0N/A public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
0N/A checkPainter();
0N/A return painter.modelToView(this, pos, b, a);
0N/A }
0N/A
0N/A /**
0N/A * Provides a mapping from the view coordinate space to the logical
0N/A * coordinate space of the model.
0N/A *
0N/A * @param x the X coordinate >= 0
0N/A * @param y the Y coordinate >= 0
0N/A * @param a the allocated region to render into
0N/A * @param biasReturn either <code>Position.Bias.Forward</code>
0N/A * or <code>Position.Bias.Backward</code> is returned as the
0N/A * zero-th element of this array
0N/A * @return the location within the model that best represents the
0N/A * given point of view >= 0
0N/A * @see View#viewToModel
0N/A */
0N/A public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
0N/A checkPainter();
0N/A return painter.viewToModel(this, x, y, a, biasReturn);
0N/A }
0N/A
0N/A /**
0N/A * Determines how attractive a break opportunity in
0N/A * this view is. This can be used for determining which
0N/A * view is the most attractive to call <code>breakView</code>
0N/A * on in the process of formatting. The
0N/A * higher the weight, the more attractive the break. A
0N/A * value equal to or lower than <code>View.BadBreakWeight</code>
0N/A * should not be considered for a break. A value greater
0N/A * than or equal to <code>View.ForcedBreakWeight</code> should
0N/A * be broken.
0N/A * <p>
0N/A * This is implemented to forward to the superclass for
0N/A * the Y_AXIS. Along the X_AXIS the following values
0N/A * may be returned.
0N/A * <dl>
0N/A * <dt><b>View.ExcellentBreakWeight</b>
0N/A * <dd>if there is whitespace proceeding the desired break
0N/A * location.
0N/A * <dt><b>View.BadBreakWeight</b>
0N/A * <dd>if the desired break location results in a break
0N/A * location of the starting offset.
0N/A * <dt><b>View.GoodBreakWeight</b>
0N/A * <dd>if the other conditions don't occur.
0N/A * </dl>
0N/A * This will normally result in the behavior of breaking
0N/A * on a whitespace location if one can be found, otherwise
0N/A * breaking between characters.
0N/A *
0N/A * @param axis may be either View.X_AXIS or View.Y_AXIS
0N/A * @param pos the potential location of the start of the
0N/A * broken view >= 0. This may be useful for calculating tab
0N/A * positions.
0N/A * @param len specifies the relative length from <em>pos</em>
0N/A * where a potential break is desired >= 0.
0N/A * @return the weight, which should be a value between
0N/A * View.ForcedBreakWeight and View.BadBreakWeight.
0N/A * @see LabelView
0N/A * @see ParagraphView
0N/A * @see View#BadBreakWeight
0N/A * @see View#GoodBreakWeight
0N/A * @see View#ExcellentBreakWeight
0N/A * @see View#ForcedBreakWeight
0N/A */
0N/A public int getBreakWeight(int axis, float pos, float len) {
0N/A if (axis == View.X_AXIS) {
0N/A checkPainter();
0N/A int p0 = getStartOffset();
0N/A int p1 = painter.getBoundedPosition(this, p0, pos, len);
1447N/A return p1 == p0 ? View.BadBreakWeight :
1447N/A getBreakSpot(p0, p1) != BreakIterator.DONE ?
1447N/A View.ExcellentBreakWeight : View.GoodBreakWeight;
0N/A }
0N/A return super.getBreakWeight(axis, pos, len);
0N/A }
0N/A
0N/A /**
0N/A * Breaks this view on the given axis at the given length.
0N/A * This is implemented to attempt to break on a whitespace
0N/A * location, and returns a fragment with the whitespace at
0N/A * the end. If a whitespace location can't be found, the
0N/A * nearest character is used.
0N/A *
0N/A * @param axis may be either View.X_AXIS or View.Y_AXIS
0N/A * @param p0 the location in the model where the
0N/A * fragment should start it's representation >= 0.
0N/A * @param pos the position along the axis that the
0N/A * broken view would occupy >= 0. This may be useful for
0N/A * things like tab calculations.
0N/A * @param len specifies the distance along the axis
0N/A * where a potential break is desired >= 0.
0N/A * @return the fragment of the view that represents the
0N/A * given span, if the view can be broken. If the view
0N/A * doesn't support breaking behavior, the view itself is
0N/A * returned.
0N/A * @see View#breakView
0N/A */
0N/A public View breakView(int axis, int p0, float pos, float len) {
0N/A if (axis == View.X_AXIS) {
0N/A checkPainter();
0N/A int p1 = painter.getBoundedPosition(this, p0, pos, len);
0N/A int breakSpot = getBreakSpot(p0, p1);
0N/A
0N/A if (breakSpot != -1) {
0N/A p1 = breakSpot;
0N/A }
0N/A // else, no break in the region, return a fragment of the
0N/A // bounded region.
0N/A if (p0 == getStartOffset() && p1 == getEndOffset()) {
0N/A return this;
0N/A }
0N/A GlyphView v = (GlyphView) createFragment(p0, p1);
0N/A v.x = (int) pos;
0N/A return v;
0N/A }
0N/A return this;
0N/A }
0N/A
0N/A /**
0N/A * Returns a location to break at in the passed in region, or
0N/A * BreakIterator.DONE if there isn't a good location to break at
0N/A * in the specified region.
0N/A */
0N/A private int getBreakSpot(int p0, int p1) {
0N/A if (breakSpots == null) {
0N/A // Re-calculate breakpoints for the whole view
0N/A int start = getStartOffset();
0N/A int end = getEndOffset();
0N/A int[] bs = new int[end + 1 - start];
0N/A int ix = 0;
0N/A
0N/A // Breaker should work on the parent element because there may be
0N/A // a valid breakpoint at the end edge of the view (space, etc.)
0N/A Element parent = getElement().getParentElement();
0N/A int pstart = (parent == null ? start : parent.getStartOffset());
0N/A int pend = (parent == null ? end : parent.getEndOffset());
0N/A
0N/A Segment s = getText(pstart, pend);
0N/A s.first();
0N/A BreakIterator breaker = getBreaker();
0N/A breaker.setText(s);
0N/A
0N/A // Backward search should start from end+1 unless there's NO end+1
0N/A int startFrom = end + (pend > end ? 1 : 0);
0N/A for (;;) {
0N/A startFrom = breaker.preceding(s.offset + (startFrom - pstart))
0N/A + (pstart - s.offset);
0N/A if (startFrom > start) {
0N/A // The break spot is within the view
0N/A bs[ix++] = startFrom;
0N/A } else {
0N/A break;
0N/A }
0N/A }
0N/A
0N/A SegmentCache.releaseSharedSegment(s);
0N/A breakSpots = new int[ix];
0N/A System.arraycopy(bs, 0, breakSpots, 0, ix);
0N/A }
0N/A
0N/A int breakSpot = BreakIterator.DONE;
0N/A for (int i = 0; i < breakSpots.length; i++) {
0N/A int bsp = breakSpots[i];
0N/A if (bsp <= p1) {
0N/A if (bsp > p0) {
0N/A breakSpot = bsp;
0N/A }
0N/A break;
0N/A }
0N/A }
0N/A return breakSpot;
0N/A }
0N/A
0N/A /**
0N/A * Return break iterator appropriate for the current document.
0N/A *
0N/A * For non-i18n documents a fast whitespace-based break iterator is used.
0N/A */
0N/A private BreakIterator getBreaker() {
0N/A Document doc = getDocument();
0N/A if ((doc != null) && Boolean.TRUE.equals(
0N/A doc.getProperty(AbstractDocument.MultiByteProperty))) {
0N/A Container c = getContainer();
0N/A Locale locale = (c == null ? Locale.getDefault() : c.getLocale());
0N/A return BreakIterator.getLineInstance(locale);
0N/A } else {
0N/A return new WhitespaceBasedBreakIterator();
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Creates a view that represents a portion of the element.
0N/A * This is potentially useful during formatting operations
0N/A * for taking measurements of fragments of the view. If
0N/A * the view doesn't support fragmenting (the default), it
0N/A * should return itself.
0N/A * <p>
0N/A * This view does support fragmenting. It is implemented
0N/A * to return a nested class that shares state in this view
0N/A * representing only a portion of the view.
0N/A *
0N/A * @param p0 the starting offset >= 0. This should be a value
0N/A * greater or equal to the element starting offset and
0N/A * less than the element ending offset.
0N/A * @param p1 the ending offset > p0. This should be a value
0N/A * less than or equal to the elements end offset and
0N/A * greater than the elements starting offset.
0N/A * @return the view fragment, or itself if the view doesn't
0N/A * support breaking into fragments
0N/A * @see LabelView
0N/A */
0N/A public View createFragment(int p0, int p1) {
0N/A checkPainter();
0N/A Element elem = getElement();
0N/A GlyphView v = (GlyphView) clone();
0N/A v.offset = p0 - elem.getStartOffset();
0N/A v.length = p1 - p0;
0N/A v.painter = painter.getPainter(v, p0, p1);
0N/A v.justificationInfo = null;
0N/A return v;
0N/A }
0N/A
0N/A /**
0N/A * Provides a way to determine the next visually represented model
0N/A * location that one might place a caret. Some views may not be
0N/A * visible, they might not be in the same order found in the model, or
0N/A * they just might not allow access to some of the locations in the
0N/A * model.
0N/A *
0N/A * @param pos the position to convert >= 0
0N/A * @param a the allocated region to render into
0N/A * @param direction the direction from the current position that can
0N/A * be thought of as the arrow keys typically found on a keyboard.
0N/A * This may be SwingConstants.WEST, SwingConstants.EAST,
0N/A * SwingConstants.NORTH, or SwingConstants.SOUTH.
0N/A * @return the location within the model that best represents the next
0N/A * location visual position.
0N/A * @exception BadLocationException
0N/A * @exception IllegalArgumentException for an invalid direction
0N/A */
0N/A public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
0N/A int direction,
0N/A Position.Bias[] biasRet)
0N/A throws BadLocationException {
0N/A
0N/A return painter.getNextVisualPositionFrom(this, pos, b, a, direction, biasRet);
0N/A }
0N/A
0N/A /**
0N/A * Gives notification that something was inserted into
0N/A * the document in a location that this view is responsible for.
0N/A * This is implemented to call preferenceChanged along the
0N/A * axis the glyphs are rendered.
0N/A *
0N/A * @param e the change information from the associated document
0N/A * @param a the current allocation of the view
0N/A * @param f the factory to use to rebuild if the view has children
0N/A * @see View#insertUpdate
0N/A */
0N/A public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
0N/A justificationInfo = null;
0N/A breakSpots = null;
0N/A minimumSpan = -1;
0N/A syncCR();
0N/A preferenceChanged(null, true, false);
0N/A }
0N/A
0N/A /**
0N/A * Gives notification that something was removed from the document
0N/A * in a location that this view is responsible for.
0N/A * This is implemented to call preferenceChanged along the
0N/A * axis the glyphs are rendered.
0N/A *
0N/A * @param e the change information from the associated document
0N/A * @param a the current allocation of the view
0N/A * @param f the factory to use to rebuild if the view has children
0N/A * @see View#removeUpdate
0N/A */
0N/A public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
0N/A justificationInfo = null;
0N/A breakSpots = null;
0N/A minimumSpan = -1;
0N/A syncCR();
0N/A preferenceChanged(null, true, false);
0N/A }
0N/A
0N/A /**
0N/A * Gives notification from the document that attributes were changed
0N/A * in a location that this view is responsible for.
0N/A * This is implemented to call preferenceChanged along both the
0N/A * horizontal and vertical axis.
0N/A *
0N/A * @param e the change information from the associated document
0N/A * @param a the current allocation of the view
0N/A * @param f the factory to use to rebuild if the view has children
0N/A * @see View#changedUpdate
0N/A */
0N/A public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
0N/A minimumSpan = -1;
0N/A syncCR();
0N/A preferenceChanged(null, true, true);
0N/A }
0N/A
0N/A // checks if the paragraph is empty and updates impliedCR flag
0N/A // accordingly
0N/A private void syncCR() {
0N/A if (impliedCR) {
0N/A Element parent = getElement().getParentElement();
0N/A impliedCR = (parent != null && parent.getElementCount() > 1);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Class to hold data needed to justify this GlyphView in a PargraphView.Row
0N/A */
0N/A static class JustificationInfo {
0N/A //justifiable content start
0N/A final int start;
0N/A //justifiable content end
0N/A final int end;
0N/A final int leadingSpaces;
0N/A final int contentSpaces;
0N/A final int trailingSpaces;
0N/A final boolean hasTab;
0N/A final BitSet spaceMap;
0N/A JustificationInfo(int start, int end,
0N/A int leadingSpaces,
0N/A int contentSpaces,
0N/A int trailingSpaces,
0N/A boolean hasTab,
0N/A BitSet spaceMap) {
0N/A this.start = start;
0N/A this.end = end;
0N/A this.leadingSpaces = leadingSpaces;
0N/A this.contentSpaces = contentSpaces;
0N/A this.trailingSpaces = trailingSpaces;
0N/A this.hasTab = hasTab;
0N/A this.spaceMap = spaceMap;
0N/A }
0N/A }
0N/A
0N/A
0N/A
0N/A JustificationInfo getJustificationInfo(int rowStartOffset) {
0N/A if (justificationInfo != null) {
0N/A return justificationInfo;
0N/A }
0N/A //states for the parsing
0N/A final int TRAILING = 0;
0N/A final int CONTENT = 1;
0N/A final int SPACES = 2;
0N/A int startOffset = getStartOffset();
0N/A int endOffset = getEndOffset();
0N/A Segment segment = getText(startOffset, endOffset);
0N/A int txtOffset = segment.offset;
0N/A int txtEnd = segment.offset + segment.count - 1;
0N/A int startContentPosition = txtEnd + 1;
0N/A int endContentPosition = txtOffset - 1;
0N/A int lastTabPosition = txtOffset - 1;
0N/A int trailingSpaces = 0;
0N/A int contentSpaces = 0;
0N/A int leadingSpaces = 0;
0N/A boolean hasTab = false;
0N/A BitSet spaceMap = new BitSet(endOffset - startOffset + 1);
0N/A
0N/A //we parse conent to the right of the rightmost TAB only.
0N/A //we are looking for the trailing and leading spaces.
0N/A //position after the leading spaces (startContentPosition)
0N/A //position before the trailing spaces (endContentPosition)
0N/A for (int i = txtEnd, state = TRAILING; i >= txtOffset; i--) {
0N/A if (' ' == segment.array[i]) {
0N/A spaceMap.set(i - txtOffset);
0N/A if (state == TRAILING) {
0N/A trailingSpaces++;
0N/A } else if (state == CONTENT) {
0N/A state = SPACES;
0N/A leadingSpaces = 1;
0N/A } else if (state == SPACES) {
0N/A leadingSpaces++;
0N/A }
0N/A } else if ('\t' == segment.array[i]) {
0N/A hasTab = true;
0N/A break;
0N/A } else {
0N/A if (state == TRAILING) {
0N/A if ('\n' != segment.array[i]
0N/A && '\r' != segment.array[i]) {
0N/A state = CONTENT;
0N/A endContentPosition = i;
0N/A }
0N/A } else if (state == CONTENT) {
0N/A //do nothing
0N/A } else if (state == SPACES) {
0N/A contentSpaces += leadingSpaces;
0N/A leadingSpaces = 0;
0N/A }
0N/A startContentPosition = i;
0N/A }
0N/A }
0N/A
0N/A SegmentCache.releaseSharedSegment(segment);
0N/A
0N/A int startJustifiableContent = -1;
0N/A if (startContentPosition < txtEnd) {
0N/A startJustifiableContent =
0N/A startContentPosition - txtOffset;
0N/A }
0N/A int endJustifiableContent = -1;
0N/A if (endContentPosition > txtOffset) {
0N/A endJustifiableContent =
0N/A endContentPosition - txtOffset;
0N/A }
0N/A justificationInfo =
0N/A new JustificationInfo(startJustifiableContent,
0N/A endJustifiableContent,
0N/A leadingSpaces,
0N/A contentSpaces,
0N/A trailingSpaces,
0N/A hasTab,
0N/A spaceMap);
0N/A return justificationInfo;
0N/A }
0N/A
0N/A // --- variables ------------------------------------------------
0N/A
0N/A /**
0N/A * Used by paint() to store highlighted view positions
0N/A */
0N/A private byte[] selections = null;
0N/A
0N/A int offset;
0N/A int length;
0N/A // if it is an implied newline character
0N/A boolean impliedCR;
0N/A boolean skipWidth;
0N/A
0N/A /**
0N/A * how to expand tabs
0N/A */
0N/A TabExpander expander;
0N/A
0N/A /** Cached minimum x-span value */
0N/A private float minimumSpan = -1;
0N/A
0N/A /** Cached breakpoints within the view */
0N/A private int[] breakSpots = null;
0N/A
0N/A /**
0N/A * location for determining tab expansion against.
0N/A */
0N/A int x;
0N/A
0N/A /**
0N/A * Glyph rendering functionality.
0N/A */
0N/A GlyphPainter painter;
0N/A
0N/A /**
0N/A * The prototype painter used by default.
0N/A */
0N/A static GlyphPainter defaultPainter;
0N/A
0N/A private JustificationInfo justificationInfo = null;
0N/A
0N/A /**
0N/A * A class to perform rendering of the glyphs.
0N/A * This can be implemented to be stateless, or
0N/A * to hold some information as a cache to
0N/A * facilitate faster rendering and model/view
0N/A * translation. At a minimum, the GlyphPainter
0N/A * allows a View implementation to perform its
0N/A * duties independant of a particular version
0N/A * of JVM and selection of capabilities (i.e.
0N/A * shaping for i18n, etc).
0N/A *
0N/A * @since 1.3
0N/A */
0N/A public static abstract class GlyphPainter {
0N/A
0N/A /**
0N/A * Determine the span the glyphs given a start location
0N/A * (for tab expansion).
0N/A */
0N/A public abstract float getSpan(GlyphView v, int p0, int p1, TabExpander e, float x);
0N/A
0N/A public abstract float getHeight(GlyphView v);
0N/A
0N/A public abstract float getAscent(GlyphView v);
0N/A
0N/A public abstract float getDescent(GlyphView v);
0N/A
0N/A /**
0N/A * Paint the glyphs representing the given range.
0N/A */
0N/A public abstract void paint(GlyphView v, Graphics g, Shape a, int p0, int p1);
0N/A
0N/A /**
0N/A * Provides a mapping from the document model coordinate space
0N/A * to the coordinate space of the view mapped to it.
0N/A * This is shared by the broken views.
0N/A *
0N/A * @param v the <code>GlyphView</code> containing the
0N/A * destination coordinate space
0N/A * @param pos the position to convert
0N/A * @param bias either <code>Position.Bias.Forward</code>
0N/A * or <code>Position.Bias.Backward</code>
0N/A * @param a Bounds of the View
0N/A * @return the bounding box of the given position
0N/A * @exception BadLocationException if the given position does not represent a
0N/A * valid location in the associated document
0N/A * @see View#modelToView
0N/A */
0N/A public abstract Shape modelToView(GlyphView v,
0N/A int pos, Position.Bias bias,
0N/A Shape a) throws BadLocationException;
0N/A
0N/A /**
0N/A * Provides a mapping from the view coordinate space to the logical
0N/A * coordinate space of the model.
0N/A *
0N/A * @param v the <code>GlyphView</code> to provide a mapping for
0N/A * @param x the X coordinate
0N/A * @param y the Y coordinate
0N/A * @param a the allocated region to render into
0N/A * @param biasReturn either <code>Position.Bias.Forward</code>
0N/A * or <code>Position.Bias.Backward</code>
0N/A * is returned as the zero-th element of this array
0N/A * @return the location within the model that best represents the
0N/A * given point of view
0N/A * @see View#viewToModel
0N/A */
0N/A public abstract int viewToModel(GlyphView v,
0N/A float x, float y, Shape a,
0N/A Position.Bias[] biasReturn);
0N/A
0N/A /**
0N/A * Determines the model location that represents the
0N/A * maximum advance that fits within the given span.
0N/A * This could be used to break the given view. The result
0N/A * should be a location just shy of the given advance. This
0N/A * differs from viewToModel which returns the closest
0N/A * position which might be proud of the maximum advance.
0N/A *
0N/A * @param v the view to find the model location to break at.
0N/A * @param p0 the location in the model where the
0N/A * fragment should start it's representation >= 0.
0N/A * @param x the graphic location along the axis that the
0N/A * broken view would occupy >= 0. This may be useful for
0N/A * things like tab calculations.
0N/A * @param len specifies the distance into the view
0N/A * where a potential break is desired >= 0.
0N/A * @return the maximum model location possible for a break.
0N/A * @see View#breakView
0N/A */
0N/A public abstract int getBoundedPosition(GlyphView v, int p0, float x, float len);
0N/A
0N/A /**
0N/A * Create a painter to use for the given GlyphView. If
0N/A * the painter carries state it can create another painter
0N/A * to represent a new GlyphView that is being created. If
0N/A * the painter doesn't hold any significant state, it can
0N/A * return itself. The default behavior is to return itself.
0N/A * @param v the <code>GlyphView</code> to provide a painter for
0N/A * @param p0 the starting document offset >= 0
0N/A * @param p1 the ending document offset >= p0
0N/A */
0N/A public GlyphPainter getPainter(GlyphView v, int p0, int p1) {
0N/A return this;
0N/A }
0N/A
0N/A /**
0N/A * Provides a way to determine the next visually represented model
0N/A * location that one might place a caret. Some views may not be
0N/A * visible, they might not be in the same order found in the model, or
0N/A * they just might not allow access to some of the locations in the
0N/A * model.
0N/A *
0N/A * @param v the view to use
0N/A * @param pos the position to convert >= 0
0N/A * @param b either <code>Position.Bias.Forward</code>
0N/A * or <code>Position.Bias.Backward</code>
0N/A * @param a the allocated region to render into
0N/A * @param direction the direction from the current position that can
0N/A * be thought of as the arrow keys typically found on a keyboard.
0N/A * This may be SwingConstants.WEST, SwingConstants.EAST,
0N/A * SwingConstants.NORTH, or SwingConstants.SOUTH.
0N/A * @param biasRet either <code>Position.Bias.Forward</code>
0N/A * or <code>Position.Bias.Backward</code>
0N/A * is returned as the zero-th element of this array
0N/A * @return the location within the model that best represents the next
0N/A * location visual position.
0N/A * @exception BadLocationException
0N/A * @exception IllegalArgumentException for an invalid direction
0N/A */
0N/A public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b, Shape a,
0N/A int direction,
0N/A Position.Bias[] biasRet)
0N/A throws BadLocationException {
0N/A
0N/A int startOffset = v.getStartOffset();
0N/A int endOffset = v.getEndOffset();
0N/A Segment text;
0N/A
0N/A switch (direction) {
0N/A case View.NORTH:
0N/A case View.SOUTH:
0N/A if (pos != -1) {
0N/A // Presumably pos is between startOffset and endOffset,
0N/A // since GlyphView is only one line, we won't contain
0N/A // the position to the nort/south, therefore return -1.
0N/A return -1;
0N/A }
0N/A Container container = v.getContainer();
0N/A
0N/A if (container instanceof JTextComponent) {
0N/A Caret c = ((JTextComponent)container).getCaret();
0N/A Point magicPoint;
0N/A magicPoint = (c != null) ? c.getMagicCaretPosition() :null;
0N/A
0N/A if (magicPoint == null) {
0N/A biasRet[0] = Position.Bias.Forward;
0N/A return startOffset;
0N/A }
0N/A int value = v.viewToModel(magicPoint.x, 0f, a, biasRet);
0N/A return value;
0N/A }
0N/A break;
0N/A case View.EAST:
0N/A if(startOffset == v.getDocument().getLength()) {
0N/A if(pos == -1) {
0N/A biasRet[0] = Position.Bias.Forward;
0N/A return startOffset;
0N/A }
0N/A // End case for bidi text where newline is at beginning
0N/A // of line.
0N/A return -1;
0N/A }
0N/A if(pos == -1) {
0N/A biasRet[0] = Position.Bias.Forward;
0N/A return startOffset;
0N/A }
0N/A if(pos == endOffset) {
0N/A return -1;
0N/A }
0N/A if(++pos == endOffset) {
0N/A // Assumed not used in bidi text, GlyphPainter2 will
0N/A // override as necessary, therefore return -1.
0N/A return -1;
0N/A }
0N/A else {
0N/A biasRet[0] = Position.Bias.Forward;
0N/A }
0N/A return pos;
0N/A case View.WEST:
0N/A if(startOffset == v.getDocument().getLength()) {
0N/A if(pos == -1) {
0N/A biasRet[0] = Position.Bias.Forward;
0N/A return startOffset;
0N/A }
0N/A // End case for bidi text where newline is at beginning
0N/A // of line.
0N/A return -1;
0N/A }
0N/A if(pos == -1) {
0N/A // Assumed not used in bidi text, GlyphPainter2 will
0N/A // override as necessary, therefore return -1.
0N/A biasRet[0] = Position.Bias.Forward;
0N/A return endOffset - 1;
0N/A }
0N/A if(pos == startOffset) {
0N/A return -1;
0N/A }
0N/A biasRet[0] = Position.Bias.Forward;
0N/A return (pos - 1);
0N/A default:
0N/A throw new IllegalArgumentException("Bad direction: " + direction);
0N/A }
0N/A return pos;
0N/A
0N/A }
0N/A }
0N/A}