Use Tree Navigation
public class

GlyphView

extends View
implements Cloneable TabableView
/*
 * Copyright (c) 1999, 2006, Oracle and/or its affiliates. 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.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 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 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);
           
if (p1 == p0) {
               
// can't even fit a single character
               
return View.BadBreakWeight;
           
}
           
if (getBreakSpot(p0, p1) != -1) {
               
return View.ExcellentBreakWeight;
           
}
           
// Nothing good to break on.
           
// breaking on the View boundary is better than splitting it
           
if (p1 == getEndOffset()) {
               
return View.GoodBreakWeight;
           
} else {
               
return View.GoodBreakWeight - 1;
           
}
       
}
       
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 -1 if
     * there isn't a good location to break at in the specified region.
     */

   
private int getBreakSpot(int p0, int p1) {
       
Document doc = getDocument();

       
if (doc != null && Boolean.TRUE.equals(doc.getProperty(
                                   
AbstractDocument.MultiByteProperty))) {
           
return getBreakSpotUseBreakIterator(p0, p1);
       
}
       
return getBreakSpotUseWhitespace(p0, p1);
   
}

   
/**
     * Returns the appropriate place to break based on the last whitespace
     * character encountered.
     */

   
private int getBreakSpotUseWhitespace(int p0, int p1) {
       
Segment s = getText(p0, p1);

       
for (char ch = s.last(); ch != Segment.DONE; ch = s.previous()) {
           
if (Character.isWhitespace(ch)) {
               
// found whitespace
               
SegmentCache.releaseSharedSegment(s);
               
return s.getIndex() - s.getBeginIndex() + 1 + p0;
           
}
       
}
       
SegmentCache.releaseSharedSegment(s);
       
return -1;
   
}

   
/**
     * Returns the appropriate place to break based on BreakIterator.
     */

   
private int getBreakSpotUseBreakIterator(int p0, int p1) {
       
// Certain regions require context for BreakIterator, start from
       
// our parents start offset.
       
Element parent = getElement().getParentElement();
       
int parent0;
       
int parent1;
       
Container c = getContainer();
       
BreakIterator breaker;

       
if (parent == null) {
            parent0
= p0;
            parent1
= p1;
       
}
       
else {
            parent0
= parent.getStartOffset();
            parent1
= parent.getEndOffset();
       
}
       
if (c != null) {
            breaker
= BreakIterator.getLineInstance(c.getLocale());
       
}
       
else {
            breaker
= BreakIterator.getLineInstance();
       
}

       
Segment s = getText(parent0, parent1);
       
int breakPoint;

       
// Needed to initialize the Segment.
        s
.first();
        breaker
.setText(s);

       
if (p1 == parent1) {
           
// This will most likely return the end, the assumption is
           
// that if parent1 == p1, then we are the last portion of
           
// a paragraph
            breakPoint
= breaker.last();
       
}
       
else if (p1 + 1 == parent1) {
           
// assert(s.count > 1)
            breakPoint
= breaker.following(s.offset + s.count - 2);
           
if (breakPoint >= s.count + s.offset) {
                breakPoint
= breaker.preceding(s.offset + s.count - 1);
           
}
       
}
       
else {
            breakPoint
= breaker.preceding(p1 - parent0 + s.offset + 1);
       
}

       
int retValue = -1;

       
if (breakPoint != BreakIterator.DONE) {
            breakPoint
= breakPoint - s.offset + parent0;
           
if (breakPoint > p0) {
               
if (p0 == parent0 && breakPoint == p0) {
                    retValue
= -1;
               
}
               
else if (breakPoint <= p1) {
                    retValue
= breakPoint;
               
}
           
}
       
}
       
SegmentCache.releaseSharedSegment(s);
       
return retValue;
   
}

   
/**
     * 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;
        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;
        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) {
        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;

   
/**
     * 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;

       
}
   
}
}