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