1 /*
2  * Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package javax.swing.text;
26 
27 import java.awt.*;
28 import java.text.BreakIterator;
29 import javax.swing.event.*;
30 import java.util.BitSet;
31 import java.util.Locale;
32 
33 import javax.swing.UIManager;
34 import sun.swing.SwingUtilities2;
35 import static sun.swing.SwingUtilities2.IMPLIED_CR;
36 
37 /**
38  * A GlyphView is a styled chunk of text that represents a view
39  * mapped over an element in the text model. This view is generally
40  * responsible for displaying text glyphs using character level
41  * attributes in some way.
42  * An implementation of the GlyphPainter class is used to do the
43  * actual rendering and model/view translations.  This separates
44  * rendering from layout and management of the association with
45  * the model.
46  * <p>
47  * The view supports breaking for the purpose of formatting.
48  * The fragments produced by breaking share the view that has
49  * primary responsibility for the element (i.e. they are nested
50  * classes and carry only a small amount of state of their own)
51  * so they can share its resources.
52  * <p>
53  * Since this view
54  * represents text that may have tabs embedded in it, it implements the
55  * <code>TabableView</code> interface.  Tabs will only be
56  * expanded if this view is embedded in a container that does
57  * tab expansion.  ParagraphView is an example of a container
58  * that does tab expansion.
59  *
60  * @since 1.3
61  *
62  * @author  Timothy Prinzing
63  */
64 public class GlyphView extends View implements TabableView, Cloneable {
65 
66     /**
67      * Constructs a new view wrapped on an element.
68      *
69      * @param elem the element
70      */
GlyphView(Element elem)71     public GlyphView(Element elem) {
72         super(elem);
73         offset = 0;
74         length = 0;
75         Element parent = elem.getParentElement();
76         AttributeSet attr = elem.getAttributes();
77 
78         //         if there was an implied CR
79         impliedCR = (attr != null && attr.getAttribute(IMPLIED_CR) != null &&
80         //         if this is non-empty paragraph
81                    parent != null && parent.getElementCount() > 1);
82         skipWidth = elem.getName().equals("br");
83     }
84 
85     /**
86      * Creates a shallow copy.  This is used by the
87      * createFragment and breakView methods.
88      *
89      * @return the copy
90      */
clone()91     protected final Object clone() {
92         Object o;
93         try {
94             o = super.clone();
95         } catch (CloneNotSupportedException cnse) {
96             o = null;
97         }
98         return o;
99     }
100 
101     /**
102      * Fetch the currently installed glyph painter.
103      * If a painter has not yet been installed, and
104      * a default was not yet needed, null is returned.
105      * @return the currently installed glyph painter
106      */
getGlyphPainter()107     public GlyphPainter getGlyphPainter() {
108         return painter;
109     }
110 
111     /**
112      * Sets the painter to use for rendering glyphs.
113      * @param p the painter to use for rendering glyphs
114      */
setGlyphPainter(GlyphPainter p)115     public void setGlyphPainter(GlyphPainter p) {
116         painter = p;
117     }
118 
119     /**
120      * Fetch a reference to the text that occupies
121      * the given range.  This is normally used by
122      * the GlyphPainter to determine what characters
123      * it should render glyphs for.
124      *
125      * @param p0  the starting document offset &gt;= 0
126      * @param p1  the ending document offset &gt;= p0
127      * @return    the <code>Segment</code> containing the text
128      */
getText(int p0, int p1)129      public Segment getText(int p0, int p1) {
130          // When done with the returned Segment it should be released by
131          // invoking:
132          //    SegmentCache.releaseSharedSegment(segment);
133          Segment text = SegmentCache.getSharedSegment();
134          try {
135              Document doc = getDocument();
136              doc.getText(p0, p1 - p0, text);
137          } catch (BadLocationException bl) {
138              throw new StateInvariantError("GlyphView: Stale view: " + bl);
139          }
140          return text;
141      }
142 
143     /**
144      * Fetch the background color to use to render the
145      * glyphs.  If there is no background color, null should
146      * be returned.  This is implemented to call
147      * <code>StyledDocument.getBackground</code> if the associated
148      * document is a styled document, otherwise it returns null.
149      * @return the background color to use to render the glyphs
150      */
getBackground()151     public Color getBackground() {
152         Document doc = getDocument();
153         if (doc instanceof StyledDocument) {
154             AttributeSet attr = getAttributes();
155             if (attr.isDefined(StyleConstants.Background)) {
156                 return ((StyledDocument)doc).getBackground(attr);
157             }
158         }
159         return null;
160     }
161 
162     /**
163      * Fetch the foreground color to use to render the
164      * glyphs.  If there is no foreground color, null should
165      * be returned.  This is implemented to call
166      * <code>StyledDocument.getBackground</code> if the associated
167      * document is a StyledDocument.  If the associated document
168      * is not a StyledDocument, the associated components foreground
169      * color is used.  If there is no associated component, null
170      * is returned.
171      * @return the foreground color to use to render the glyphs
172      */
getForeground()173     public Color getForeground() {
174         Document doc = getDocument();
175         if (doc instanceof StyledDocument) {
176             AttributeSet attr = getAttributes();
177             return ((StyledDocument)doc).getForeground(attr);
178         }
179         Component c = getContainer();
180         if (c != null) {
181             return c.getForeground();
182         }
183         return null;
184     }
185 
186     /**
187      * Fetch the font that the glyphs should be based
188      * upon.  This is implemented to call
189      * <code>StyledDocument.getFont</code> if the associated
190      * document is a StyledDocument.  If the associated document
191      * is not a StyledDocument, the associated components font
192      * is used.  If there is no associated component, null
193      * is returned.
194      * @return the font that the glyphs should be based upon
195      */
getFont()196     public Font getFont() {
197         Document doc = getDocument();
198         if (doc instanceof StyledDocument) {
199             AttributeSet attr = getAttributes();
200             return ((StyledDocument)doc).getFont(attr);
201         }
202         Component c = getContainer();
203         if (c != null) {
204             return c.getFont();
205         }
206         return null;
207     }
208 
209     /**
210      * Determine if the glyphs should be underlined.  If true,
211      * an underline should be drawn through the baseline.
212      * @return if the glyphs should be underlined
213      */
isUnderline()214     public boolean isUnderline() {
215         AttributeSet attr = getAttributes();
216         return StyleConstants.isUnderline(attr);
217     }
218 
219     /**
220      * Determine if the glyphs should have a strikethrough
221      * line.  If true, a line should be drawn through the center
222      * of the glyphs.
223      * @return if the glyphs should have a strikethrough line
224      */
isStrikeThrough()225     public boolean isStrikeThrough() {
226         AttributeSet attr = getAttributes();
227         return StyleConstants.isStrikeThrough(attr);
228     }
229 
230     /**
231      * Determine if the glyphs should be rendered as superscript.
232      * @return if the glyphs should be rendered as superscript
233      */
isSubscript()234     public boolean isSubscript() {
235         AttributeSet attr = getAttributes();
236         return StyleConstants.isSubscript(attr);
237     }
238 
239     /**
240      * Determine if the glyphs should be rendered as subscript.
241      * @return if the glyphs should be rendered as subscript
242      */
isSuperscript()243     public boolean isSuperscript() {
244         AttributeSet attr = getAttributes();
245         return StyleConstants.isSuperscript(attr);
246     }
247 
248     /**
249      * Fetch the TabExpander to use if tabs are present in this view.
250      * @return the TabExpander to use if tabs are present in this view
251      */
getTabExpander()252     public TabExpander getTabExpander() {
253         return expander;
254     }
255 
256     /**
257      * Check to see that a glyph painter exists.  If a painter
258      * doesn't exist, a default glyph painter will be installed.
259      */
checkPainter()260     protected void checkPainter() {
261         if (painter == null) {
262             if (defaultPainter == null) {
263                 // the classname should probably come from a property file.
264                 defaultPainter = new GlyphPainter1();
265             }
266             setGlyphPainter(defaultPainter.getPainter(this, getStartOffset(),
267                                                       getEndOffset()));
268         }
269     }
270 
271     // --- TabableView methods --------------------------------------
272 
273     /**
274      * Determines the desired span when using the given
275      * tab expansion implementation.
276      *
277      * @param x the position the view would be located
278      *  at for the purpose of tab expansion &gt;= 0.
279      * @param e how to expand the tabs when encountered.
280      * @return the desired span &gt;= 0
281      * @see TabableView#getTabbedSpan
282      */
getTabbedSpan(float x, TabExpander e)283     public float getTabbedSpan(float x, TabExpander e) {
284         checkPainter();
285 
286         TabExpander old = expander;
287         expander = e;
288 
289         if (expander != old) {
290             // setting expander can change horizontal span of the view,
291             // so we have to call preferenceChanged()
292             preferenceChanged(null, true, false);
293         }
294 
295         this.x = (int) x;
296         int p0 = getStartOffset();
297         int p1 = getEndOffset();
298         float width = painter.getSpan(this, p0, p1, expander, x);
299         return width;
300     }
301 
302     /**
303      * Determines the span along the same axis as tab
304      * expansion for a portion of the view.  This is
305      * intended for use by the TabExpander for cases
306      * where the tab expansion involves aligning the
307      * portion of text that doesn't have whitespace
308      * relative to the tab stop.  There is therefore
309      * an assumption that the range given does not
310      * contain tabs.
311      * <p>
312      * This method can be called while servicing the
313      * getTabbedSpan or getPreferredSize.  It has to
314      * arrange for its own text buffer to make the
315      * measurements.
316      *
317      * @param p0 the starting document offset &gt;= 0
318      * @param p1 the ending document offset &gt;= p0
319      * @return the span &gt;= 0
320      */
getPartialSpan(int p0, int p1)321     public float getPartialSpan(int p0, int p1) {
322         checkPainter();
323         float width = painter.getSpan(this, p0, p1, expander, x);
324         return width;
325     }
326 
327     // --- View methods ---------------------------------------------
328 
329     /**
330      * Fetches the portion of the model that this view is responsible for.
331      *
332      * @return the starting offset into the model
333      * @see View#getStartOffset
334      */
getStartOffset()335     public int getStartOffset() {
336         Element e = getElement();
337         return (length > 0) ? e.getStartOffset() + offset : e.getStartOffset();
338     }
339 
340     /**
341      * Fetches the portion of the model that this view is responsible for.
342      *
343      * @return the ending offset into the model
344      * @see View#getEndOffset
345      */
getEndOffset()346     public int getEndOffset() {
347         Element e = getElement();
348         return (length > 0) ? e.getStartOffset() + offset + length : e.getEndOffset();
349     }
350 
351     /**
352      * Lazily initializes the selections field
353      */
initSelections(int p0, int p1)354     private void initSelections(int p0, int p1) {
355         int viewPosCount = p1 - p0 + 1;
356         if (selections == null || viewPosCount > selections.length) {
357             selections = new byte[viewPosCount];
358             return;
359         }
360         for (int i = 0; i < viewPosCount; selections[i++] = 0);
361     }
362 
363     /**
364      * Renders a portion of a text style run.
365      *
366      * @param g the rendering surface to use
367      * @param a the allocated region to render into
368      */
paint(Graphics g, Shape a)369     public void paint(Graphics g, Shape a) {
370         checkPainter();
371 
372         boolean paintedText = false;
373         Component c = getContainer();
374         int p0 = getStartOffset();
375         int p1 = getEndOffset();
376         Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
377         Color bg = getBackground();
378         Color fg = getForeground();
379 
380         if (c != null && ! c.isEnabled()) {
381             fg = (c instanceof JTextComponent ?
382                 ((JTextComponent)c).getDisabledTextColor() :
383                 UIManager.getColor("textInactiveText"));
384         }
385         if (bg != null) {
386             g.setColor(bg);
387             g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
388         }
389         if (c instanceof JTextComponent) {
390             JTextComponent tc = (JTextComponent) c;
391             Highlighter h = tc.getHighlighter();
392             if (h instanceof LayeredHighlighter) {
393                 ((LayeredHighlighter)h).paintLayeredHighlights
394                     (g, p0, p1, a, tc, this);
395             }
396         }
397 
398         if (Utilities.isComposedTextElement(getElement())) {
399             Utilities.paintComposedText(g, a.getBounds(), this);
400             paintedText = true;
401         } else if(c instanceof JTextComponent) {
402             JTextComponent tc = (JTextComponent) c;
403             Color selFG = tc.getSelectedTextColor();
404 
405             if (// there's a highlighter (bug 4532590), and
406                 (tc.getHighlighter() != null) &&
407                 // selected text color is different from regular foreground
408                 (selFG != null) && !selFG.equals(fg)) {
409 
410                 Highlighter.Highlight[] h = tc.getHighlighter().getHighlights();
411                 if(h.length != 0) {
412                     boolean initialized = false;
413                     int viewSelectionCount = 0;
414                     for (int i = 0; i < h.length; i++) {
415                         Highlighter.Highlight highlight = h[i];
416                         int hStart = highlight.getStartOffset();
417                         int hEnd = highlight.getEndOffset();
418                         if (hStart > p1 || hEnd < p0) {
419                             // the selection is out of this view
420                             continue;
421                         }
422                         if (!SwingUtilities2.useSelectedTextColor(highlight, tc)) {
423                             continue;
424                         }
425                         if (hStart <= p0 && hEnd >= p1){
426                             // the whole view is selected
427                             paintTextUsingColor(g, a, selFG, p0, p1);
428                             paintedText = true;
429                             break;
430                         }
431                         // the array is lazily created only when the view
432                         // is partially selected
433                         if (!initialized) {
434                             initSelections(p0, p1);
435                             initialized = true;
436                         }
437                         hStart = Math.max(p0, hStart);
438                         hEnd = Math.min(p1, hEnd);
439                         paintTextUsingColor(g, a, selFG, hStart, hEnd);
440                         // the array represents view positions [0, p1-p0+1]
441                         // later will iterate this array and sum its
442                         // elements. Positions with sum == 0 are not selected.
443                         selections[hStart-p0]++;
444                         selections[hEnd-p0]--;
445 
446                         viewSelectionCount++;
447                     }
448 
449                     if (!paintedText && viewSelectionCount > 0) {
450                         // the view is partially selected
451                         int curPos = -1;
452                         int startPos = 0;
453                         int viewLen = p1 - p0;
454                         while (curPos++ < viewLen) {
455                             // searching for the next selection start
456                             while(curPos < viewLen &&
457                                     selections[curPos] == 0) curPos++;
458                             if (startPos != curPos) {
459                                 // paint unselected text
460                                 paintTextUsingColor(g, a, fg,
461                                         p0 + startPos, p0 + curPos);
462                             }
463                             int checkSum = 0;
464                             // searching for next start position of unselected text
465                             while (curPos < viewLen &&
466                                     (checkSum += selections[curPos]) != 0) curPos++;
467                             startPos = curPos;
468                         }
469                         paintedText = true;
470                     }
471                 }
472             }
473         }
474         if(!paintedText)
475             paintTextUsingColor(g, a, fg, p0, p1);
476     }
477 
478     /**
479      * Paints the specified region of text in the specified color.
480      */
paintTextUsingColor(Graphics g, Shape a, Color c, int p0, int p1)481     final void paintTextUsingColor(Graphics g, Shape a, Color c, int p0, int p1) {
482         // render the glyphs
483         g.setColor(c);
484         painter.paint(this, g, a, p0, p1);
485 
486         // render underline or strikethrough if set.
487         boolean underline = isUnderline();
488         boolean strike = isStrikeThrough();
489         if (underline || strike) {
490             // calculate x coordinates
491             Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
492             View parent = getParent();
493             if ((parent != null) && (parent.getEndOffset() == p1)) {
494                 // strip whitespace on end
495                 Segment s = getText(p0, p1);
496                 while (Character.isWhitespace(s.last())) {
497                     p1 -= 1;
498                     s.count -= 1;
499                 }
500                 SegmentCache.releaseSharedSegment(s);
501             }
502             int x0 = alloc.x;
503             int p = getStartOffset();
504             if (p != p0) {
505                 x0 += (int) painter.getSpan(this, p, p0, getTabExpander(), x0);
506             }
507             int x1 = x0 + (int) painter.getSpan(this, p0, p1, getTabExpander(), x0);
508 
509             // calculate y coordinate
510             int y = alloc.y + (int)(painter.getHeight(this) - painter.getDescent(this));
511             if (underline) {
512                 int yTmp = y + 1;
513                 g.drawLine(x0, yTmp, x1, yTmp);
514             }
515             if (strike) {
516                 // move y coordinate above baseline
517                 int yTmp = y - (int) (painter.getAscent(this) * 0.3f);
518                 g.drawLine(x0, yTmp, x1, yTmp);
519             }
520 
521         }
522     }
523 
524     /**
525      * Determines the minimum span for this view along an axis.
526      *
527      * <p>This implementation returns the longest non-breakable area within
528      * the view as a minimum span for {@code View.X_AXIS}.</p>
529      *
530      * @param axis  may be either {@code View.X_AXIS} or {@code View.Y_AXIS}
531      * @return      the minimum span the view can be rendered into
532      * @throws IllegalArgumentException if the {@code axis} parameter is invalid
533      * @see         javax.swing.text.View#getMinimumSpan
534      */
535     @Override
getMinimumSpan(int axis)536     public float getMinimumSpan(int axis) {
537         switch (axis) {
538             case View.X_AXIS:
539                 if (minimumSpan < 0) {
540                     minimumSpan = 0;
541                     int p0 = getStartOffset();
542                     int p1 = getEndOffset();
543                     while (p1 > p0) {
544                         int breakSpot = getBreakSpot(p0, p1);
545                         if (breakSpot == BreakIterator.DONE) {
546                             // the rest of the view is non-breakable
547                             breakSpot = p0;
548                         }
549                         minimumSpan = Math.max(minimumSpan,
550                                 getPartialSpan(breakSpot, p1));
551                         // Note: getBreakSpot returns the *last* breakspot
552                         p1 = breakSpot - 1;
553                     }
554                 }
555                 return minimumSpan;
556             case View.Y_AXIS:
557                 return super.getMinimumSpan(axis);
558             default:
559                 throw new IllegalArgumentException("Invalid axis: " + axis);
560         }
561     }
562 
563     /**
564      * Determines the preferred span for this view along an
565      * axis.
566      *
567      * @param axis may be either View.X_AXIS or View.Y_AXIS
568      * @return   the span the view would like to be rendered into &gt;= 0.
569      *           Typically the view is told to render into the span
570      *           that is returned, although there is no guarantee.
571      *           The parent may choose to resize or break the view.
572      */
getPreferredSpan(int axis)573     public float getPreferredSpan(int axis) {
574         if (impliedCR) {
575             return 0;
576         }
577         checkPainter();
578         int p0 = getStartOffset();
579         int p1 = getEndOffset();
580         switch (axis) {
581         case View.X_AXIS:
582             if (skipWidth) {
583                 return 0;
584             }
585             return painter.getSpan(this, p0, p1, expander, this.x);
586         case View.Y_AXIS:
587             float h = painter.getHeight(this);
588             if (isSuperscript()) {
589                 h += h/3;
590             }
591             return h;
592         default:
593             throw new IllegalArgumentException("Invalid axis: " + axis);
594         }
595     }
596 
597     /**
598      * Determines the desired alignment for this view along an
599      * axis.  For the label, the alignment is along the font
600      * baseline for the y axis, and the superclasses alignment
601      * along the x axis.
602      *
603      * @param axis may be either View.X_AXIS or View.Y_AXIS
604      * @return the desired alignment.  This should be a value
605      *   between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
606      *   origin and 1.0 indicates alignment to the full span
607      *   away from the origin.  An alignment of 0.5 would be the
608      *   center of the view.
609      */
getAlignment(int axis)610     public float getAlignment(int axis) {
611         checkPainter();
612         if (axis == View.Y_AXIS) {
613             boolean sup = isSuperscript();
614             boolean sub = isSubscript();
615             float h = painter.getHeight(this);
616             float d = painter.getDescent(this);
617             float a = painter.getAscent(this);
618             float align;
619             if (sup) {
620                 align = 1.0f;
621             } else if (sub) {
622                 align = (h > 0) ? (h - (d + (a / 2))) / h : 0;
623             } else {
624                 align = (h > 0) ? (h - d) / h : 0;
625             }
626             return align;
627         }
628         return super.getAlignment(axis);
629     }
630 
631     /**
632      * Provides a mapping from the document model coordinate space
633      * to the coordinate space of the view mapped to it.
634      *
635      * @param pos the position to convert &gt;= 0
636      * @param a   the allocated region to render into
637      * @param b   either <code>Position.Bias.Forward</code>
638      *                or <code>Position.Bias.Backward</code>
639      * @return the bounding box of the given position
640      * @exception BadLocationException  if the given position does not represent a
641      *   valid location in the associated document
642      * @see View#modelToView
643      */
modelToView(int pos, Shape a, Position.Bias b)644     public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
645         checkPainter();
646         return painter.modelToView(this, pos, b, a);
647     }
648 
649     /**
650      * Provides a mapping from the view coordinate space to the logical
651      * coordinate space of the model.
652      *
653      * @param x the X coordinate &gt;= 0
654      * @param y the Y coordinate &gt;= 0
655      * @param a the allocated region to render into
656      * @param biasReturn either <code>Position.Bias.Forward</code>
657      *  or <code>Position.Bias.Backward</code> is returned as the
658      *  zero-th element of this array
659      * @return the location within the model that best represents the
660      *  given point of view &gt;= 0
661      * @see View#viewToModel
662      */
viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn)663     public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
664         checkPainter();
665         return painter.viewToModel(this, x, y, a, biasReturn);
666     }
667 
668     /**
669      * Determines how attractive a break opportunity in
670      * this view is.  This can be used for determining which
671      * view is the most attractive to call <code>breakView</code>
672      * on in the process of formatting.  The
673      * higher the weight, the more attractive the break.  A
674      * value equal to or lower than <code>View.BadBreakWeight</code>
675      * should not be considered for a break.  A value greater
676      * than or equal to <code>View.ForcedBreakWeight</code> should
677      * be broken.
678      * <p>
679      * This is implemented to forward to the superclass for
680      * the Y_AXIS.  Along the X_AXIS the following values
681      * may be returned.
682      * <dl>
683      * <dt><b>View.ExcellentBreakWeight</b>
684      * <dd>if there is whitespace proceeding the desired break
685      *   location.
686      * <dt><b>View.BadBreakWeight</b>
687      * <dd>if the desired break location results in a break
688      *   location of the starting offset.
689      * <dt><b>View.GoodBreakWeight</b>
690      * <dd>if the other conditions don't occur.
691      * </dl>
692      * This will normally result in the behavior of breaking
693      * on a whitespace location if one can be found, otherwise
694      * breaking between characters.
695      *
696      * @param axis may be either View.X_AXIS or View.Y_AXIS
697      * @param pos the potential location of the start of the
698      *   broken view &gt;= 0.  This may be useful for calculating tab
699      *   positions.
700      * @param len specifies the relative length from <em>pos</em>
701      *   where a potential break is desired &gt;= 0.
702      * @return the weight, which should be a value between
703      *   View.ForcedBreakWeight and View.BadBreakWeight.
704      * @see LabelView
705      * @see ParagraphView
706      * @see View#BadBreakWeight
707      * @see View#GoodBreakWeight
708      * @see View#ExcellentBreakWeight
709      * @see View#ForcedBreakWeight
710      */
getBreakWeight(int axis, float pos, float len)711     public int getBreakWeight(int axis, float pos, float len) {
712         if (axis == View.X_AXIS) {
713             checkPainter();
714             int p0 = getStartOffset();
715             int p1 = painter.getBoundedPosition(this, p0, pos, len);
716             return p1 == p0 ? View.BadBreakWeight :
717                    getBreakSpot(p0, p1) != BreakIterator.DONE ?
718                             View.ExcellentBreakWeight : View.GoodBreakWeight;
719         }
720         return super.getBreakWeight(axis, pos, len);
721     }
722 
723     /**
724      * Breaks this view on the given axis at the given length.
725      * This is implemented to attempt to break on a whitespace
726      * location, and returns a fragment with the whitespace at
727      * the end.  If a whitespace location can't be found, the
728      * nearest character is used.
729      *
730      * @param axis may be either View.X_AXIS or View.Y_AXIS
731      * @param p0 the location in the model where the
732      *  fragment should start it's representation &gt;= 0.
733      * @param pos the position along the axis that the
734      *  broken view would occupy &gt;= 0.  This may be useful for
735      *  things like tab calculations.
736      * @param len specifies the distance along the axis
737      *  where a potential break is desired &gt;= 0.
738      * @return the fragment of the view that represents the
739      *  given span, if the view can be broken.  If the view
740      *  doesn't support breaking behavior, the view itself is
741      *  returned.
742      * @see View#breakView
743      */
breakView(int axis, int p0, float pos, float len)744     public View breakView(int axis, int p0, float pos, float len) {
745         if (axis == View.X_AXIS) {
746             checkPainter();
747             int p1 = painter.getBoundedPosition(this, p0, pos, len);
748             int breakSpot = getBreakSpot(p0, p1);
749 
750             if (breakSpot != -1) {
751                 p1 = breakSpot;
752             }
753             // else, no break in the region, return a fragment of the
754             // bounded region.
755             if (p0 == getStartOffset() && p1 == getEndOffset()) {
756                 return this;
757             }
758             GlyphView v = (GlyphView) createFragment(p0, p1);
759             v.x = (int) pos;
760             return v;
761         }
762         return this;
763     }
764 
765     /**
766      * Returns a location to break at in the passed in region, or
767      * BreakIterator.DONE if there isn't a good location to break at
768      * in the specified region.
769      */
getBreakSpot(int p0, int p1)770     private int getBreakSpot(int p0, int p1) {
771         if (breakSpots == null) {
772             // Re-calculate breakpoints for the whole view
773             int start = getStartOffset();
774             int end = getEndOffset();
775             int[] bs = new int[end + 1 - start];
776             int ix = 0;
777 
778             // Breaker should work on the parent element because there may be
779             // a valid breakpoint at the end edge of the view (space, etc.)
780             Element parent = getElement().getParentElement();
781             int pstart = (parent == null ? start : parent.getStartOffset());
782             int pend = (parent == null ? end : parent.getEndOffset());
783 
784             Segment s = getText(pstart, pend);
785             s.first();
786             BreakIterator breaker = getBreaker();
787             breaker.setText(s);
788 
789             // Backward search should start from end+1 unless there's NO end+1
790             int startFrom = end + (pend > end ? 1 : 0);
791             for (;;) {
792                 startFrom = breaker.preceding(s.offset + (startFrom - pstart))
793                           + (pstart - s.offset);
794                 if (startFrom > start) {
795                     // The break spot is within the view
796                     bs[ix++] = startFrom;
797                 } else {
798                     break;
799                 }
800             }
801 
802             SegmentCache.releaseSharedSegment(s);
803             breakSpots = new int[ix];
804             System.arraycopy(bs, 0, breakSpots, 0, ix);
805         }
806 
807         int breakSpot = BreakIterator.DONE;
808         for (int i = 0; i < breakSpots.length; i++) {
809             int bsp = breakSpots[i];
810             if (bsp <= p1) {
811                 if (bsp > p0) {
812                     breakSpot = bsp;
813                 }
814                 break;
815             }
816         }
817         return breakSpot;
818     }
819 
820     /**
821      * Return break iterator appropriate for the current document.
822      *
823      * For non-i18n documents a fast whitespace-based break iterator is used.
824      */
getBreaker()825     private BreakIterator getBreaker() {
826         Document doc = getDocument();
827         if ((doc != null) && Boolean.TRUE.equals(
828                     doc.getProperty(AbstractDocument.MultiByteProperty))) {
829             Container c = getContainer();
830             Locale locale = (c == null ? Locale.getDefault() : c.getLocale());
831             return BreakIterator.getLineInstance(locale);
832         } else {
833             return new WhitespaceBasedBreakIterator();
834         }
835     }
836 
837     /**
838      * Creates a view that represents a portion of the element.
839      * This is potentially useful during formatting operations
840      * for taking measurements of fragments of the view.  If
841      * the view doesn't support fragmenting (the default), it
842      * should return itself.
843      * <p>
844      * This view does support fragmenting.  It is implemented
845      * to return a nested class that shares state in this view
846      * representing only a portion of the view.
847      *
848      * @param p0 the starting offset &gt;= 0.  This should be a value
849      *   greater or equal to the element starting offset and
850      *   less than the element ending offset.
851      * @param p1 the ending offset &gt; p0.  This should be a value
852      *   less than or equal to the elements end offset and
853      *   greater than the elements starting offset.
854      * @return the view fragment, or itself if the view doesn't
855      *   support breaking into fragments
856      * @see LabelView
857      */
createFragment(int p0, int p1)858     public View createFragment(int p0, int p1) {
859         checkPainter();
860         Element elem = getElement();
861         GlyphView v = (GlyphView) clone();
862         v.offset = p0 - elem.getStartOffset();
863         v.length = p1 - p0;
864         v.painter = painter.getPainter(v, p0, p1);
865         v.justificationInfo = null;
866         return v;
867     }
868 
869     /**
870      * Provides a way to determine the next visually represented model
871      * location that one might place a caret.  Some views may not be
872      * visible, they might not be in the same order found in the model, or
873      * they just might not allow access to some of the locations in the
874      * model.
875      * This method enables specifying a position to convert
876      * within the range of &gt;=0.  If the value is -1, a position
877      * will be calculated automatically.  If the value &lt; -1,
878      * the {@code BadLocationException} will be thrown.
879      *
880      * @param pos the position to convert
881      * @param a the allocated region to render into
882      * @param direction the direction from the current position that can
883      *  be thought of as the arrow keys typically found on a keyboard.
884      *  This may be SwingConstants.WEST, SwingConstants.EAST,
885      *  SwingConstants.NORTH, or SwingConstants.SOUTH.
886      * @return the location within the model that best represents the next
887      *  location visual position.
888      * @exception BadLocationException the given position is not a valid
889      *                                 position within the document
890      * @exception IllegalArgumentException for an invalid direction
891      */
getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet)892     public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
893                                          int direction,
894                                          Position.Bias[] biasRet)
895         throws BadLocationException {
896 
897         if (pos < -1 || pos > getDocument().getLength()) {
898             throw new BadLocationException("invalid position", pos);
899         }
900         return painter.getNextVisualPositionFrom(this, pos, b, a, direction, biasRet);
901     }
902 
903     /**
904      * Gives notification that something was inserted into
905      * the document in a location that this view is responsible for.
906      * This is implemented to call preferenceChanged along the
907      * axis the glyphs are rendered.
908      *
909      * @param e the change information from the associated document
910      * @param a the current allocation of the view
911      * @param f the factory to use to rebuild if the view has children
912      * @see View#insertUpdate
913      */
insertUpdate(DocumentEvent e, Shape a, ViewFactory f)914     public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
915         justificationInfo = null;
916         breakSpots = null;
917         minimumSpan = -1;
918         syncCR();
919         preferenceChanged(null, true, false);
920     }
921 
922     /**
923      * Gives notification that something was removed from the document
924      * in a location that this view is responsible for.
925      * This is implemented to call preferenceChanged along the
926      * axis the glyphs are rendered.
927      *
928      * @param e the change information from the associated document
929      * @param a the current allocation of the view
930      * @param f the factory to use to rebuild if the view has children
931      * @see View#removeUpdate
932      */
removeUpdate(DocumentEvent e, Shape a, ViewFactory f)933     public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
934         justificationInfo = null;
935         breakSpots = null;
936         minimumSpan = -1;
937         syncCR();
938         preferenceChanged(null, true, false);
939     }
940 
941     /**
942      * Gives notification from the document that attributes were changed
943      * in a location that this view is responsible for.
944      * This is implemented to call preferenceChanged along both the
945      * horizontal and vertical axis.
946      *
947      * @param e the change information from the associated document
948      * @param a the current allocation of the view
949      * @param f the factory to use to rebuild if the view has children
950      * @see View#changedUpdate
951      */
changedUpdate(DocumentEvent e, Shape a, ViewFactory f)952     public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
953         minimumSpan = -1;
954         syncCR();
955         preferenceChanged(null, true, true);
956     }
957 
958     // checks if the paragraph is empty and updates impliedCR flag
959     // accordingly
syncCR()960     private void syncCR() {
961         if (impliedCR) {
962             Element parent = getElement().getParentElement();
963             impliedCR = (parent != null && parent.getElementCount() > 1);
964         }
965     }
966 
967     /** {@inheritDoc} */
968     @Override
updateAfterChange()969     void updateAfterChange() {
970         // Drop the break spots. They will be re-calculated during
971         // layout. It is necessary for proper line break calculation.
972         breakSpots = null;
973     }
974 
975     /**
976      * Class to hold data needed to justify this GlyphView in a PargraphView.Row
977      */
978     static class JustificationInfo {
979         //justifiable content start
980         final int start;
981         //justifiable content end
982         final int end;
983         final int leadingSpaces;
984         final int contentSpaces;
985         final int trailingSpaces;
986         final boolean hasTab;
987         final BitSet spaceMap;
JustificationInfo(int start, int end, int leadingSpaces, int contentSpaces, int trailingSpaces, boolean hasTab, BitSet spaceMap)988         JustificationInfo(int start, int end,
989                           int leadingSpaces,
990                           int contentSpaces,
991                           int trailingSpaces,
992                           boolean hasTab,
993                           BitSet spaceMap) {
994             this.start = start;
995             this.end = end;
996             this.leadingSpaces = leadingSpaces;
997             this.contentSpaces = contentSpaces;
998             this.trailingSpaces = trailingSpaces;
999             this.hasTab = hasTab;
1000             this.spaceMap = spaceMap;
1001         }
1002     }
1003 
1004 
1005 
getJustificationInfo(int rowStartOffset)1006     JustificationInfo getJustificationInfo(int rowStartOffset) {
1007         if (justificationInfo != null) {
1008             return justificationInfo;
1009         }
1010         //states for the parsing
1011         final int TRAILING = 0;
1012         final int CONTENT  = 1;
1013         final int SPACES   = 2;
1014         int startOffset = getStartOffset();
1015         int endOffset = getEndOffset();
1016         Segment segment = getText(startOffset, endOffset);
1017         int txtOffset = segment.offset;
1018         int txtEnd = segment.offset + segment.count - 1;
1019         int startContentPosition = txtEnd + 1;
1020         int endContentPosition = txtOffset - 1;
1021         int lastTabPosition = txtOffset - 1;
1022         int trailingSpaces = 0;
1023         int contentSpaces = 0;
1024         int leadingSpaces = 0;
1025         boolean hasTab = false;
1026         BitSet spaceMap = new BitSet(endOffset - startOffset + 1);
1027 
1028         //we parse conent to the right of the rightmost TAB only.
1029         //we are looking for the trailing and leading spaces.
1030         //position after the leading spaces (startContentPosition)
1031         //position before the trailing spaces (endContentPosition)
1032         for (int i = txtEnd, state = TRAILING; i >= txtOffset; i--) {
1033             if (' ' == segment.array[i]) {
1034                 spaceMap.set(i - txtOffset);
1035                 if (state == TRAILING) {
1036                     trailingSpaces++;
1037                 } else if (state == CONTENT) {
1038                     state = SPACES;
1039                     leadingSpaces = 1;
1040                 } else if (state == SPACES) {
1041                     leadingSpaces++;
1042                 }
1043             } else if ('\t' == segment.array[i]) {
1044                 hasTab = true;
1045                 break;
1046             } else {
1047                 if (state == TRAILING) {
1048                     if ('\n' != segment.array[i]
1049                           && '\r' != segment.array[i]) {
1050                         state = CONTENT;
1051                         endContentPosition = i;
1052                     }
1053                 } else if (state == CONTENT) {
1054                     //do nothing
1055                 } else if (state == SPACES) {
1056                     contentSpaces += leadingSpaces;
1057                     leadingSpaces = 0;
1058                 }
1059                 startContentPosition = i;
1060             }
1061         }
1062 
1063         SegmentCache.releaseSharedSegment(segment);
1064 
1065         int startJustifiableContent = -1;
1066         if (startContentPosition < txtEnd) {
1067             startJustifiableContent =
1068                 startContentPosition - txtOffset;
1069         }
1070         int endJustifiableContent = -1;
1071         if (endContentPosition > txtOffset) {
1072             endJustifiableContent =
1073                 endContentPosition - txtOffset;
1074         }
1075         justificationInfo =
1076             new JustificationInfo(startJustifiableContent,
1077                                   endJustifiableContent,
1078                                   leadingSpaces,
1079                                   contentSpaces,
1080                                   trailingSpaces,
1081                                   hasTab,
1082                                   spaceMap);
1083         return justificationInfo;
1084     }
1085 
1086     // --- variables ------------------------------------------------
1087 
1088     /**
1089     * Used by paint() to store highlighted view positions
1090     */
1091     private byte[] selections = null;
1092 
1093     int offset;
1094     int length;
1095     // if it is an implied newline character
1096     boolean impliedCR;
1097     boolean skipWidth;
1098 
1099     /**
1100      * how to expand tabs
1101      */
1102     TabExpander expander;
1103 
1104     /** Cached minimum x-span value  */
1105     private float minimumSpan = -1;
1106 
1107     /** Cached breakpoints within the view  */
1108     private int[] breakSpots = null;
1109 
1110     /**
1111      * location for determining tab expansion against.
1112      */
1113     int x;
1114 
1115     /**
1116      * Glyph rendering functionality.
1117      */
1118     GlyphPainter painter;
1119 
1120     /**
1121      * The prototype painter used by default.
1122      */
1123     static GlyphPainter defaultPainter;
1124 
1125     private JustificationInfo justificationInfo = null;
1126 
1127     /**
1128      * A class to perform rendering of the glyphs.
1129      * This can be implemented to be stateless, or
1130      * to hold some information as a cache to
1131      * facilitate faster rendering and model/view
1132      * translation.  At a minimum, the GlyphPainter
1133      * allows a View implementation to perform its
1134      * duties independent of a particular version
1135      * of JVM and selection of capabilities (i.e.
1136      * shaping for i18n, etc).
1137      *
1138      * @since 1.3
1139      */
1140     public abstract static class GlyphPainter {
1141 
1142         /**
1143          * Determine the span the glyphs given a start location
1144          * (for tab expansion).
1145          * @param v  the {@code GlyphView}
1146          * @param p0 the beginning position
1147          * @param p1 the ending position
1148          * @param e  how to expand the tabs when encountered
1149          * @param x the X coordinate
1150          * @return the span the glyphs given a start location
1151          */
getSpan(GlyphView v, int p0, int p1, TabExpander e, float x)1152         public abstract float getSpan(GlyphView v, int p0, int p1, TabExpander e, float x);
1153 
1154         /**
1155          * Returns of the height.
1156          * @param v  the {@code GlyphView}
1157          * @return of the height
1158          */
getHeight(GlyphView v)1159         public abstract float getHeight(GlyphView v);
1160 
1161         /**
1162          * Returns of the ascent.
1163          * @param v  the {@code GlyphView}
1164          * @return of the ascent
1165          */
getAscent(GlyphView v)1166         public abstract float getAscent(GlyphView v);
1167 
1168         /**
1169          * Returns of the descent.
1170          * @param v  the {@code GlyphView}
1171          * @return of the descent
1172          */
getDescent(GlyphView v)1173         public abstract float getDescent(GlyphView v);
1174 
1175         /**
1176          * Paint the glyphs representing the given range.
1177          * @param v the {@code GlyphView}
1178          * @param g the graphics context
1179          * @param a the current allocation of the view
1180          * @param p0 the beginning position
1181          * @param p1 the ending position
1182          */
paint(GlyphView v, Graphics g, Shape a, int p0, int p1)1183         public abstract void paint(GlyphView v, Graphics g, Shape a, int p0, int p1);
1184 
1185         /**
1186          * Provides a mapping from the document model coordinate space
1187          * to the coordinate space of the view mapped to it.
1188          * This is shared by the broken views.
1189          *
1190          * @param v     the <code>GlyphView</code> containing the
1191          *              destination coordinate space
1192          * @param pos   the position to convert
1193          * @param bias  either <code>Position.Bias.Forward</code>
1194          *                  or <code>Position.Bias.Backward</code>
1195          * @param a     Bounds of the View
1196          * @return      the bounding box of the given position
1197          * @exception BadLocationException  if the given position does not represent a
1198          *   valid location in the associated document
1199          * @see View#modelToView
1200          */
modelToView(GlyphView v, int pos, Position.Bias bias, Shape a)1201         public abstract Shape modelToView(GlyphView v,
1202                                           int pos, Position.Bias bias,
1203                                           Shape a) throws BadLocationException;
1204 
1205         /**
1206          * Provides a mapping from the view coordinate space to the logical
1207          * coordinate space of the model.
1208          *
1209          * @param v          the <code>GlyphView</code> to provide a mapping for
1210          * @param x          the X coordinate
1211          * @param y          the Y coordinate
1212          * @param a          the allocated region to render into
1213          * @param biasReturn either <code>Position.Bias.Forward</code>
1214          *                   or <code>Position.Bias.Backward</code>
1215          *                   is returned as the zero-th element of this array
1216          * @return the location within the model that best represents the
1217          *         given point of view
1218          * @see View#viewToModel
1219          */
viewToModel(GlyphView v, float x, float y, Shape a, Position.Bias[] biasReturn)1220         public abstract int viewToModel(GlyphView v,
1221                                         float x, float y, Shape a,
1222                                         Position.Bias[] biasReturn);
1223 
1224         /**
1225          * Determines the model location that represents the
1226          * maximum advance that fits within the given span.
1227          * This could be used to break the given view.  The result
1228          * should be a location just shy of the given advance.  This
1229          * differs from viewToModel which returns the closest
1230          * position which might be proud of the maximum advance.
1231          *
1232          * @param v the view to find the model location to break at.
1233          * @param p0 the location in the model where the
1234          *  fragment should start it's representation &gt;= 0.
1235          * @param x  the graphic location along the axis that the
1236          *  broken view would occupy &gt;= 0.  This may be useful for
1237          *  things like tab calculations.
1238          * @param len specifies the distance into the view
1239          *  where a potential break is desired &gt;= 0.
1240          * @return the maximum model location possible for a break.
1241          * @see View#breakView
1242          */
getBoundedPosition(GlyphView v, int p0, float x, float len)1243         public abstract int getBoundedPosition(GlyphView v, int p0, float x, float len);
1244 
1245         /**
1246          * Create a painter to use for the given GlyphView.  If
1247          * the painter carries state it can create another painter
1248          * to represent a new GlyphView that is being created.  If
1249          * the painter doesn't hold any significant state, it can
1250          * return itself.  The default behavior is to return itself.
1251          * @param v  the <code>GlyphView</code> to provide a painter for
1252          * @param p0 the starting document offset &gt;= 0
1253          * @param p1 the ending document offset &gt;= p0
1254          * @return a painter to use for the given GlyphView
1255          */
getPainter(GlyphView v, int p0, int p1)1256         public GlyphPainter getPainter(GlyphView v, int p0, int p1) {
1257             return this;
1258         }
1259 
1260         /**
1261          * Provides a way to determine the next visually represented model
1262          * location that one might place a caret.  Some views may not be
1263          * visible, they might not be in the same order found in the model, or
1264          * they just might not allow access to some of the locations in the
1265          * model.
1266          *
1267          * @param v the view to use
1268          * @param pos the position to convert &gt;= 0
1269          * @param b   either <code>Position.Bias.Forward</code>
1270          *                or <code>Position.Bias.Backward</code>
1271          * @param a the allocated region to render into
1272          * @param direction the direction from the current position that can
1273          *  be thought of as the arrow keys typically found on a keyboard.
1274          *  This may be SwingConstants.WEST, SwingConstants.EAST,
1275          *  SwingConstants.NORTH, or SwingConstants.SOUTH.
1276          * @param biasRet  either <code>Position.Bias.Forward</code>
1277          *                 or <code>Position.Bias.Backward</code>
1278          *                 is returned as the zero-th element of this array
1279          * @return the location within the model that best represents the next
1280          *  location visual position.
1281          * @exception BadLocationException for a bad location within a document model
1282          * @exception IllegalArgumentException for an invalid direction
1283          */
getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet)1284         public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b, Shape a,
1285                                              int direction,
1286                                              Position.Bias[] biasRet)
1287             throws BadLocationException {
1288 
1289             int startOffset = v.getStartOffset();
1290             int endOffset = v.getEndOffset();
1291             Segment text;
1292 
1293             switch (direction) {
1294             case View.NORTH:
1295             case View.SOUTH:
1296                 if (pos != -1) {
1297                     // Presumably pos is between startOffset and endOffset,
1298                     // since GlyphView is only one line, we won't contain
1299                     // the position to the nort/south, therefore return -1.
1300                     return -1;
1301                 }
1302                 Container container = v.getContainer();
1303 
1304                 if (container instanceof JTextComponent) {
1305                     Caret c = ((JTextComponent)container).getCaret();
1306                     Point magicPoint;
1307                     magicPoint = (c != null) ? c.getMagicCaretPosition() :null;
1308 
1309                     if (magicPoint == null) {
1310                         biasRet[0] = Position.Bias.Forward;
1311                         return startOffset;
1312                     }
1313                     int value = v.viewToModel(magicPoint.x, 0f, a, biasRet);
1314                     return value;
1315                 }
1316                 break;
1317             case View.EAST:
1318                 if(startOffset == v.getDocument().getLength()) {
1319                     if(pos == -1) {
1320                         biasRet[0] = Position.Bias.Forward;
1321                         return startOffset;
1322                     }
1323                     // End case for bidi text where newline is at beginning
1324                     // of line.
1325                     return -1;
1326                 }
1327                 if(pos == -1) {
1328                     biasRet[0] = Position.Bias.Forward;
1329                     return startOffset;
1330                 }
1331                 if(pos == endOffset) {
1332                     return -1;
1333                 }
1334                 if(++pos == endOffset) {
1335                     // Assumed not used in bidi text, GlyphPainter2 will
1336                     // override as necessary, therefore return -1.
1337                     return -1;
1338                 }
1339                 else {
1340                     biasRet[0] = Position.Bias.Forward;
1341                 }
1342                 return pos;
1343             case View.WEST:
1344                 if(startOffset == v.getDocument().getLength()) {
1345                     if(pos == -1) {
1346                         biasRet[0] = Position.Bias.Forward;
1347                         return startOffset;
1348                     }
1349                     // End case for bidi text where newline is at beginning
1350                     // of line.
1351                     return -1;
1352                 }
1353                 if(pos == -1) {
1354                     // Assumed not used in bidi text, GlyphPainter2 will
1355                     // override as necessary, therefore return -1.
1356                     biasRet[0] = Position.Bias.Forward;
1357                     return endOffset - 1;
1358                 }
1359                 if(pos == startOffset) {
1360                     return -1;
1361                 }
1362                 biasRet[0] = Position.Bias.Forward;
1363                 return (pos - 1);
1364             default:
1365                 throw new IllegalArgumentException("Bad direction: " + direction);
1366             }
1367             return pos;
1368 
1369         }
1370     }
1371 }
1372