OSDN Git Service

libjava/
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / text / GlyphView.java
1 /* GlyphView.java -- A view to render styled text
2    Copyright (C) 2005  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing.text;
40
41 import gnu.classpath.SystemProperties;
42
43 import java.awt.Color;
44 import java.awt.Container;
45 import java.awt.Font;
46 import java.awt.FontMetrics;
47 import java.awt.Graphics;
48 import java.awt.Graphics2D;
49 import java.awt.Rectangle;
50 import java.awt.Shape;
51 import java.awt.Toolkit;
52 import java.awt.font.FontRenderContext;
53 import java.awt.font.TextHitInfo;
54 import java.awt.font.TextLayout;
55 import java.awt.geom.Rectangle2D;
56
57 import javax.swing.SwingConstants;
58 import javax.swing.event.DocumentEvent;
59 import javax.swing.text.Position.Bias;
60
61 /**
62  * Renders a run of styled text. This {@link View} subclass paints the
63  * characters of the <code>Element</code> it is responsible for using
64  * the style information from that <code>Element</code>.
65  *
66  * @author Roman Kennke (roman@kennke.org)
67  */
68 public class GlyphView extends View implements TabableView, Cloneable
69 {
70
71   /**
72    * An abstract base implementation for a glyph painter for
73    * <code>GlyphView</code>.
74    */
75   public abstract static class GlyphPainter
76   {
77     /**
78      * Creates a new <code>GlyphPainer</code>.
79      */
80     public GlyphPainter()
81     {
82       // Nothing to do here.
83     }
84
85     /**
86      * Returns the ascent of the font that is used by this glyph painter.
87      *
88      * @param v the glyph view
89      *
90      * @return the ascent of the font that is used by this glyph painter
91      */
92     public abstract float getAscent(GlyphView v);
93
94     /**
95      * Returns the descent of the font that is used by this glyph painter.
96      *
97      * @param v the glyph view
98      *
99      * @return the descent of the font that is used by this glyph painter
100      */
101     public abstract float getDescent(GlyphView v);
102
103     /**
104      * Returns the full height of the rendered text.
105      *
106      * @return the full height of the rendered text
107      */
108     public abstract float getHeight(GlyphView view);
109
110     /**
111      * Determines the model offset, so that the text between <code>p0</code>
112      * and this offset fits within the span starting at <code>x</code> with
113      * the length of <code>len</code>. 
114      *
115      * @param v the glyph view
116      * @param p0 the starting offset in the model
117      * @param x the start location in the view
118      * @param len the length of the span in the view
119      */
120     public abstract int getBoundedPosition(GlyphView v, int p0, float x,
121                                            float len);
122
123     /**
124      * Paints the glyphs.
125      *
126      * @param view the glyph view to paint
127      * @param g the graphics context to use for painting
128      * @param a the allocation of the glyph view
129      * @param p0 the start position (in the model) from which to paint
130      * @param p1 the end position (in the model) to which to paint
131      */
132     public abstract void paint(GlyphView view, Graphics g, Shape a, int p0,
133                                int p1);
134
135     /**
136      * Maps a position in the document into the coordinate space of the View.
137      * The output rectangle usually reflects the font height but has a width
138      * of zero.
139      *
140      * @param view the glyph view
141      * @param pos the position of the character in the model
142      * @param a the area that is occupied by the view
143      * @param b either {@link Position.Bias#Forward} or
144      *        {@link Position.Bias#Backward} depending on the preferred
145      *        direction bias. If <code>null</code> this defaults to
146      *        <code>Position.Bias.Forward</code>
147      *
148      * @return a rectangle that gives the location of the document position
149      *         inside the view coordinate space
150      *
151      * @throws BadLocationException if <code>pos</code> is invalid
152      * @throws IllegalArgumentException if b is not one of the above listed
153      *         valid values
154      */
155     public abstract Shape modelToView(GlyphView view, int pos, Position.Bias b,
156                                       Shape a)
157       throws BadLocationException;
158
159     /**
160      * Maps a visual position into a document location.
161      *
162      * @param v the glyph view
163      * @param x the X coordinate of the visual position
164      * @param y the Y coordinate of the visual position
165      * @param a the allocated region
166      * @param biasRet filled with the bias of the model location on method exit
167      *
168      * @return the model location that represents the specified view location
169      */
170     public abstract int viewToModel(GlyphView v, float x, float y, Shape a,
171                                     Position.Bias[] biasRet);
172
173     /**
174      * Determine the span of the glyphs from location <code>p0</code> to
175      * location <code>p1</code>. If <code>te</code> is not <code>null</code>,
176      * then TABs are expanded using this <code>TabExpander</code>.
177      * The parameter <code>x</code> is the location at which the view is
178      * located (this is important when using TAB expansion).
179      *
180      * @param view the glyph view
181      * @param p0 the starting location in the document model
182      * @param p1 the end location in the document model
183      * @param te the tab expander to use
184      * @param x the location at which the view is located
185      *
186      * @return the span of the glyphs from location <code>p0</code> to
187      *         location <code>p1</code>, possibly using TAB expansion
188      */
189     public abstract float getSpan(GlyphView view, int p0, int p1,
190                                   TabExpander te, float x);
191
192
193     /**
194      * Returns the model location that should be used to place a caret when
195      * moving the caret through the document.
196      *
197      * @param v the glyph view
198      * @param pos the current model location
199      * @param b the bias for <code>p</code>
200      * @param a the allocated region for the glyph view
201      * @param direction the direction from the current position; Must be one of
202      *        {@link SwingConstants#EAST}, {@link SwingConstants#WEST},
203      *        {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH}
204      * @param biasRet filled with the bias of the resulting location when method
205      *        returns
206      *
207      * @return the location within the document that should be used to place the
208      *         caret when moving the caret around the document
209      *
210      * @throws BadLocationException if <code>pos</code> is an invalid model
211      *         location
212      * @throws IllegalArgumentException if <code>d</code> is invalid
213      */
214     public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b,
215                                          Shape a, int direction,
216                                          Position.Bias[] biasRet)
217       throws BadLocationException
218
219     {
220       int result = pos;
221       switch (direction)
222       {
223         case SwingConstants.EAST:
224           result = pos + 1;
225           break;
226         case SwingConstants.WEST:
227           result = pos - 1;
228           break;
229         case SwingConstants.NORTH:
230         case SwingConstants.SOUTH:
231         default:
232           // This should be handled in enclosing view, since the glyph view
233           // does not layout vertically.
234           break;
235       }
236       return result;
237     }
238
239     /**
240      * Returns a painter that can be used to render the specified glyph view.
241      * If this glyph painter is stateful, then it should return a new instance.
242      * However, if this painter is stateless it should return itself. The
243      * default behaviour is to return itself.
244      *
245      * @param v the glyph view for which to create a painter
246      * @param p0 the start offset of the rendered area
247      * @param p1 the end offset of the rendered area
248      *
249      * @return a painter that can be used to render the specified glyph view
250      */
251     public GlyphPainter getPainter(GlyphView v, int p0, int p1)
252     {
253       return this;
254     }
255   }
256
257   /**
258    * A GlyphPainter implementation based on TextLayout. This should give
259    * better performance in Java2D environments.
260    */
261   private static class J2DGlyphPainter
262     extends GlyphPainter
263   {
264
265     /**
266      * The text layout.
267      */
268     TextLayout textLayout;
269
270     /**
271      * Creates a new J2DGlyphPainter.
272      *
273      * @param str the string
274      * @param font the font
275      * @param frc the font render context
276      */
277     J2DGlyphPainter(String str, Font font, FontRenderContext frc)
278     {
279       textLayout = new TextLayout(str, font, frc);
280     }
281
282     /**
283      * Returns null so that GlyphView.checkPainter() creates a new instance.
284      */
285     public GlyphPainter getPainter(GlyphView v, int p0, int p1)
286     {
287       return null;
288     }
289
290     /**
291      * Delegates to the text layout.
292      */
293     public float getAscent(GlyphView v)
294     {
295       return textLayout.getAscent();
296     }
297
298     /**
299      * Delegates to the text layout.
300      */
301     public int getBoundedPosition(GlyphView v, int p0, float x, float len)
302     {
303       int pos;
304       TextHitInfo hit = textLayout.hitTestChar(len, 0);
305       if (hit.getCharIndex() == -1 && ! textLayout.isLeftToRight())
306         pos = v.getEndOffset();
307       else
308         {
309           pos = hit.isLeadingEdge() ? hit.getInsertionIndex()
310                                     : hit.getInsertionIndex() - 1;
311           pos += v.getStartOffset();
312         }
313       return pos;
314     }
315
316     /**
317      * Delegates to the text layout.
318      */
319     public float getDescent(GlyphView v)
320     {
321       return textLayout.getDescent();
322     }
323
324     /**
325      * Delegates to the text layout.
326      */
327     public float getHeight(GlyphView view)
328     {
329       return textLayout.getAscent() + textLayout.getDescent()
330              + textLayout.getLeading();
331     }
332
333     /**
334      * Delegates to the text layout.
335      */
336     public float getSpan(GlyphView v, int p0, int p1, TabExpander te, float x)
337     {
338       float span;
339       if (p0 == v.getStartOffset() && p1 == v.getEndOffset())
340         span = textLayout.getAdvance();
341       else
342         {
343           int start = v.getStartOffset();
344           int i0 = p0 - start;
345           int i1 = p1 - start;
346           TextHitInfo hit0 = TextHitInfo.afterOffset(i0);
347           TextHitInfo hit1 = TextHitInfo.afterOffset(i1);
348           float x0 = textLayout.getCaretInfo(hit0)[0];
349           float x1 = textLayout.getCaretInfo(hit1)[0];
350           span = Math.abs(x1 - x0);
351         }
352       return span;
353     }
354
355     /**
356      * Delegates to the text layout.
357      */
358     public Shape modelToView(GlyphView v, int pos, Bias b, Shape a)
359       throws BadLocationException
360     {
361       int offs = pos - v.getStartOffset();
362       // Create copy here to protect original shape.
363       Rectangle2D bounds = a.getBounds2D();
364       TextHitInfo hit =
365         b == Position.Bias.Forward ? TextHitInfo.afterOffset(offs)
366                                    : TextHitInfo.beforeOffset(offs);
367       float[] loc = textLayout.getCaretInfo(hit);
368       bounds.setRect(bounds.getX() + loc[0], bounds.getY(), 1,
369                      bounds.getHeight());
370       return bounds;
371     }
372
373     /**
374      * Delegates to the text layout.
375      */
376     public void paint(GlyphView view, Graphics g, Shape a, int p0, int p1)
377     {
378       // Can't paint this with plain graphics.
379       if (g instanceof Graphics2D)
380         {
381           Graphics2D g2d = (Graphics2D) g;
382           Rectangle2D b = a instanceof Rectangle2D ? (Rectangle2D) a
383                                                    : a.getBounds2D();
384           float x = (float) b.getX();
385           float y = (float) b.getY() + textLayout.getAscent()
386                     + textLayout.getLeading();
387           // TODO: Try if clipping makes things faster for narrow views.
388           textLayout.draw(g2d, x, y);
389         }
390     }
391
392     /**
393      * Delegates to the text layout.
394      */
395     public int viewToModel(GlyphView v, float x, float y, Shape a,
396                            Bias[] biasRet)
397     {
398       Rectangle2D bounds = a instanceof Rectangle2D ? (Rectangle2D) a
399                                                     : a.getBounds2D();
400       TextHitInfo hit = textLayout.hitTestChar(x - (float) bounds.getX(), 0);
401       int pos = hit.getInsertionIndex();
402       biasRet[0] = hit.isLeadingEdge() ? Position.Bias.Forward
403                                        : Position.Bias.Backward;
404       return pos + v.getStartOffset();
405     }
406     
407   }
408
409   /**
410    * The default <code>GlyphPainter</code> used in <code>GlyphView</code>.
411    */
412   static class DefaultGlyphPainter extends GlyphPainter
413   {
414     FontMetrics fontMetrics;
415
416     /**
417      * Returns the full height of the rendered text.
418      *
419      * @return the full height of the rendered text
420      */
421     public float getHeight(GlyphView view)
422     {
423       updateFontMetrics(view);
424       float height = fontMetrics.getHeight();
425       return height;
426     }
427     
428     /**
429      * Paints the glyphs.
430      *
431      * @param view the glyph view to paint
432      * @param g the graphics context to use for painting
433      * @param a the allocation of the glyph view
434      * @param p0 the start position (in the model) from which to paint
435      * @param p1 the end position (in the model) to which to paint
436      */
437     public void paint(GlyphView view, Graphics g, Shape a, int p0,
438                       int p1)
439     {
440       updateFontMetrics(view);
441       Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
442       TabExpander tabEx = view.getTabExpander();
443       Segment txt = view.getText(p0, p1);
444
445       // Find out the X location at which we have to paint.
446       int x = r.x;
447       int p = view.getStartOffset();
448       if (p != p0)
449         {
450           int width = Utilities.getTabbedTextWidth(txt, fontMetrics,x, tabEx,
451                                                    p);
452           x += width;
453         }
454       // Find out Y location.
455       int y = r.y + fontMetrics.getHeight() - fontMetrics.getDescent();
456
457       // Render the thing.
458       g.setFont(fontMetrics.getFont());
459       Utilities.drawTabbedText(txt, x, y, g, tabEx, p0);
460
461     }
462
463     /**
464      * Maps a position in the document into the coordinate space of the View.
465      * The output rectangle usually reflects the font height but has a width
466      * of zero.
467      *
468      * @param view the glyph view
469      * @param pos the position of the character in the model
470      * @param a the area that is occupied by the view
471      * @param b either {@link Position.Bias#Forward} or
472      *        {@link Position.Bias#Backward} depending on the preferred
473      *        direction bias. If <code>null</code> this defaults to
474      *        <code>Position.Bias.Forward</code>
475      *
476      * @return a rectangle that gives the location of the document position
477      *         inside the view coordinate space
478      *
479      * @throws BadLocationException if <code>pos</code> is invalid
480      * @throws IllegalArgumentException if b is not one of the above listed
481      *         valid values
482      */
483     public Shape modelToView(GlyphView view, int pos, Position.Bias b,
484                              Shape a)
485       throws BadLocationException
486     {
487       updateFontMetrics(view);
488       Element el = view.getElement();
489       Segment txt = view.getText(el.getStartOffset(), pos);
490       Rectangle bounds = a instanceof Rectangle ? (Rectangle) a
491                                                 : a.getBounds();
492       TabExpander expander = view.getTabExpander();
493       int width = Utilities.getTabbedTextWidth(txt, fontMetrics, bounds.x,
494                                                expander,
495                                                view.getStartOffset());
496       int height = fontMetrics.getHeight();
497       Rectangle result = new Rectangle(bounds.x + width, bounds.y,
498                                        0, height);
499       return result;
500     }
501
502     /**
503      * Determine the span of the glyphs from location <code>p0</code> to
504      * location <code>p1</code>. If <code>te</code> is not <code>null</code>,
505      * then TABs are expanded using this <code>TabExpander</code>.
506      * The parameter <code>x</code> is the location at which the view is
507      * located (this is important when using TAB expansion).
508      *
509      * @param view the glyph view
510      * @param p0 the starting location in the document model
511      * @param p1 the end location in the document model
512      * @param te the tab expander to use
513      * @param x the location at which the view is located
514      *
515      * @return the span of the glyphs from location <code>p0</code> to
516      *         location <code>p1</code>, possibly using TAB expansion
517      */
518     public float getSpan(GlyphView view, int p0, int p1,
519                          TabExpander te, float x)
520     {
521       updateFontMetrics(view);
522       Segment txt = view.getText(p0, p1);
523       int span = Utilities.getTabbedTextWidth(txt, fontMetrics, (int) x, te,
524                                               p0);
525       return span;
526     }
527
528     /**
529      * Returns the ascent of the text run that is rendered by this
530      * <code>GlyphPainter</code>.
531      *
532      * @param v the glyph view
533      *
534      * @return the ascent of the text run that is rendered by this
535      *         <code>GlyphPainter</code>
536      *
537      * @see FontMetrics#getAscent()
538      */
539     public float getAscent(GlyphView v)
540     {
541       updateFontMetrics(v);
542       return fontMetrics.getAscent();
543     }
544
545     /**
546      * Returns the descent of the text run that is rendered by this
547      * <code>GlyphPainter</code>.
548      *
549      * @param v the glyph view
550      *
551      * @return the descent of the text run that is rendered by this
552      *         <code>GlyphPainter</code>
553      *
554      * @see FontMetrics#getDescent()
555      */
556     public float getDescent(GlyphView v)
557     {
558       updateFontMetrics(v);
559       return fontMetrics.getDescent();
560     }
561
562     /**
563      * Determines the model offset, so that the text between <code>p0</code>
564      * and this offset fits within the span starting at <code>x</code> with
565      * the length of <code>len</code>. 
566      *
567      * @param v the glyph view
568      * @param p0 the starting offset in the model
569      * @param x the start location in the view
570      * @param len the length of the span in the view
571      */
572     public int getBoundedPosition(GlyphView v, int p0, float x, float len)
573     {
574       updateFontMetrics(v);
575       TabExpander te = v.getTabExpander();
576       Segment txt = v.getText(p0, v.getEndOffset());
577       int pos = Utilities.getTabbedTextOffset(txt, fontMetrics, (int) x,
578                                               (int) (x + len), te, p0, false);
579       return pos + p0;
580     }
581
582     /**
583      * Maps a visual position into a document location.
584      *
585      * @param v the glyph view
586      * @param x the X coordinate of the visual position
587      * @param y the Y coordinate of the visual position
588      * @param a the allocated region
589      * @param biasRet filled with the bias of the model location on method exit
590      *
591      * @return the model location that represents the specified view location
592      */
593     public int viewToModel(GlyphView v, float x, float y, Shape a,
594                            Bias[] biasRet)
595     {
596       Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
597       int p0 = v.getStartOffset();
598       int p1 = v.getEndOffset();
599       TabExpander te = v.getTabExpander();
600       Segment s = v.getText(p0, p1);
601       int offset = Utilities.getTabbedTextOffset(s, fontMetrics, r.x, (int) x,
602                                                  te, p0);
603       int ret = p0 + offset;
604       if (ret == p1)
605         ret--;
606       biasRet[0] = Position.Bias.Forward;
607       return ret;
608     }
609
610     private void updateFontMetrics(GlyphView v)
611     {
612       Font font = v.getFont();
613       if (fontMetrics == null || ! font.equals(fontMetrics.getFont()))
614         {
615           Container c = v.getContainer();
616           FontMetrics fm;
617           if (c != null)
618             fm = c.getFontMetrics(font);
619           else
620             fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
621           fontMetrics = fm;
622         }
623     }
624   }
625
626   /**
627    * The GlyphPainer used for painting the glyphs.
628    */
629   GlyphPainter glyphPainter;
630
631   /**
632    * The start offset within the document for this view.
633    */
634   private int offset;
635
636   /**
637    * The end offset within the document for this view.
638    */
639   private int length;
640
641   /**
642    * The x location against which the tab expansion is done.
643    */
644   private float tabX;
645
646   /**
647    * The tab expander that is used in this view.
648    */
649   private TabExpander tabExpander;
650
651   /**
652    * Creates a new <code>GlyphView</code> for the given <code>Element</code>.
653    *
654    * @param element the element that is rendered by this GlyphView
655    */
656   public GlyphView(Element element)
657   {
658     super(element);
659     offset = 0;
660     length = 0;
661   }
662
663   /**
664    * Returns the <code>GlyphPainter</code> that is used by this
665    * <code>GlyphView</code>. If no <code>GlyphPainer</code> has been installed
666    * <code>null</code> is returned.
667    *
668    * @return the glyph painter that is used by this
669    *         glyph view or <code>null</code> if no glyph painter has been
670    *         installed
671    */
672   public GlyphPainter getGlyphPainter()
673   {
674     return glyphPainter;
675   }
676
677   /**
678    * Sets the {@link GlyphPainter} to be used for this <code>GlyphView</code>.
679    *
680    * @param painter the glyph painter to be used for this glyph view
681    */
682   public void setGlyphPainter(GlyphPainter painter)
683   {
684     glyphPainter = painter;
685   }
686
687   /**
688    * Checks if a <code>GlyphPainer</code> is installed. If this is not the
689    * case, a default painter is installed.
690    */
691   protected void checkPainter()
692   {
693     if (glyphPainter == null)
694       {
695         if ("true".equals(
696                  SystemProperties.getProperty("gnu.javax.swing.noGraphics2D")))
697           {
698             glyphPainter = new DefaultGlyphPainter();
699           }
700         else
701           {
702             Segment s = getText(getStartOffset(), getEndOffset());
703             glyphPainter = new J2DGlyphPainter(s.toString(), getFont(),
704                                                new FontRenderContext(null,
705                                                                      false,
706                                                                      false));
707           }
708       }
709   }
710
711   /**
712    * Renders the <code>Element</code> that is associated with this
713    * <code>View</code>.
714    *
715    * @param g the <code>Graphics</code> context to render to
716    * @param a the allocated region for the <code>Element</code>
717    */
718   public void paint(Graphics g, Shape a)
719   {
720     checkPainter();
721     int p0 = getStartOffset();
722     int p1 = getEndOffset();
723
724     Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
725     Container c = getContainer();
726
727     Color fg = getForeground();
728     JTextComponent tc = null;
729     if (c instanceof JTextComponent)
730       {
731         tc = (JTextComponent) c;
732         if (! tc.isEnabled())
733           fg = tc.getDisabledTextColor();
734       }
735     Color bg = getBackground();
736     if (bg != null)
737       {
738         g.setColor(bg);
739         g.fillRect(r.x, r.y, r.width, r.height);
740       }
741
742     
743     // Paint layered highlights if there are any.
744     if (tc != null)
745       {
746         Highlighter h = tc.getHighlighter();
747         if (h instanceof LayeredHighlighter)
748           {
749             LayeredHighlighter lh = (LayeredHighlighter) h;
750             lh.paintLayeredHighlights(g, p0, p1, a, tc, this);
751           }
752       }
753
754     g.setColor(fg);
755     glyphPainter.paint(this, g, a, p0, p1);
756     boolean underline = isUnderline();
757     boolean striked = isStrikeThrough();
758     if (underline || striked)
759       {
760         View parent = getParent();
761         // X coordinate.
762         if (parent != null && parent.getEndOffset() == p1)
763           {
764             // Strip whitespace.
765             Segment s = getText(p0, p1);
766             while (s.count > 0 && Character.isWhitespace(s.array[s.count - 1]))
767               {
768                 p1--;
769                 s.count--;
770               }
771           }
772         int x0 = r.x;
773         int p = getStartOffset();
774         TabExpander tabEx = getTabExpander();
775         if (p != p0)
776           x0 += (int) glyphPainter.getSpan(this, p, p0, tabEx, x0);
777         int x1 = x0 + (int) glyphPainter.getSpan(this, p0, p1, tabEx, x0);
778         // Y coordinate.
779         int y = r.y + r.height - (int) glyphPainter.getDescent(this);
780         if (underline)
781           {
782             int yTmp = y;
783             yTmp += 1;
784             g.drawLine(x0, yTmp, x1, yTmp);
785           }
786         if (striked)
787           {
788             int yTmp = y;
789             yTmp -= (int) glyphPainter.getAscent(this);
790             g.drawLine(x0, yTmp, x1, yTmp);
791           }
792       }
793   }
794
795
796   /**
797    * Returns the preferred span of the content managed by this
798    * <code>View</code> along the specified <code>axis</code>.
799    *
800    * @param axis the axis
801    *
802    * @return the preferred span of this <code>View</code>.
803    */
804   public float getPreferredSpan(int axis)
805   {
806     float span = 0;
807     checkPainter();
808     GlyphPainter painter = getGlyphPainter();
809     switch (axis)
810       {
811       case X_AXIS:
812         TabExpander tabEx = null;
813         View parent = getParent();
814         if (parent instanceof TabExpander)
815           tabEx = (TabExpander) parent;
816         span = painter.getSpan(this, getStartOffset(), getEndOffset(),
817                                tabEx, 0.F);
818         break;
819       case Y_AXIS:
820         span = painter.getHeight(this);
821         if (isSuperscript())
822           span += span / 3;
823         break;
824       default:
825         throw new IllegalArgumentException("Illegal axis");
826       }
827     return span;
828   }
829
830   /**
831    * Maps a position in the document into the coordinate space of the View.
832    * The output rectangle usually reflects the font height but has a width
833    * of zero.
834    *
835    * @param pos the position of the character in the model
836    * @param a the area that is occupied by the view
837    * @param b either {@link Position.Bias#Forward} or
838    *        {@link Position.Bias#Backward} depending on the preferred
839    *        direction bias. If <code>null</code> this defaults to
840    *        <code>Position.Bias.Forward</code>
841    *
842    * @return a rectangle that gives the location of the document position
843    *         inside the view coordinate space
844    *
845    * @throws BadLocationException if <code>pos</code> is invalid
846    * @throws IllegalArgumentException if b is not one of the above listed
847    *         valid values
848    */
849   public Shape modelToView(int pos, Shape a, Position.Bias b)
850     throws BadLocationException
851   {
852     GlyphPainter p = getGlyphPainter();
853     return p.modelToView(this, pos, b, a);
854   }
855
856   /**
857    * Maps coordinates from the <code>View</code>'s space into a position
858    * in the document model.
859    *
860    * @param x the x coordinate in the view space
861    * @param y the y coordinate in the view space
862    * @param a the allocation of this <code>View</code>
863    * @param b the bias to use
864    *
865    * @return the position in the document that corresponds to the screen
866    *         coordinates <code>x, y</code>
867    */
868   public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
869   {
870     checkPainter();
871     GlyphPainter painter = getGlyphPainter();
872     return painter.viewToModel(this, x, y, a, b);
873   }
874
875   /**
876    * Return the {@link TabExpander} to use.
877    *
878    * @return the {@link TabExpander} to use
879    */
880   public TabExpander getTabExpander()
881   {
882     return tabExpander;
883   }
884
885   /**
886    * Returns the preferred span of this view for tab expansion.
887    *
888    * @param x the location of the view
889    * @param te the tab expander to use
890    *
891    * @return the preferred span of this view for tab expansion
892    */
893   public float getTabbedSpan(float x, TabExpander te)
894   {
895     checkPainter();
896     TabExpander old = tabExpander;
897     tabExpander = te;
898     if (tabExpander != old)
899       {
900         // Changing the tab expander will lead to a relayout in the X_AXIS.
901         preferenceChanged(null, true, false);
902       }
903     tabX = x;
904     return getGlyphPainter().getSpan(this, getStartOffset(),
905                                      getEndOffset(), tabExpander, x);
906   }
907
908   /**
909    * Returns the span of a portion of the view. This is used in TAB expansion
910    * for fragments that don't contain TABs.
911    *
912    * @param p0 the start index
913    * @param p1 the end index
914    *
915    * @return the span of the specified portion of the view
916    */
917   public float getPartialSpan(int p0, int p1)
918   {
919     checkPainter();
920     return glyphPainter.getSpan(this, p0, p1, tabExpander, tabX);
921   }
922
923   /**
924    * Returns the start offset in the document model of the portion
925    * of text that this view is responsible for.
926    *
927    * @return the start offset in the document model of the portion
928    *         of text that this view is responsible for
929    */
930   public int getStartOffset()
931   {
932     Element el = getElement();
933     int offs = el.getStartOffset();
934     if (length > 0)
935       offs += offset;
936     return offs;
937   }
938
939   /**
940    * Returns the end offset in the document model of the portion
941    * of text that this view is responsible for.
942    *
943    * @return the end offset in the document model of the portion
944    *         of text that this view is responsible for
945    */
946   public int getEndOffset()
947   {
948     Element el = getElement();
949     int offs;
950     if (length > 0)
951       offs = el.getStartOffset() + offset + length;
952     else
953       offs = el.getEndOffset();
954     return offs;
955   }
956
957   private Segment cached = new Segment();
958
959   /**
960    * Returns the text segment that this view is responsible for.
961    *
962    * @param p0 the start index in the document model
963    * @param p1 the end index in the document model
964    *
965    * @return the text segment that this view is responsible for
966    */
967   public Segment getText(int p0, int p1)
968   {
969     try
970       {
971         getDocument().getText(p0, p1 - p0, cached);
972       }
973     catch (BadLocationException ex)
974       {
975         AssertionError ae;
976         ae = new AssertionError("BadLocationException should not be "
977                                 + "thrown here. p0 = " + p0 + ", p1 = " + p1);
978         ae.initCause(ex);
979         throw ae;
980       }
981
982     return cached;
983   }
984
985   /**
986    * Returns the font for the text run for which this <code>GlyphView</code>
987    * is responsible.
988    *
989    * @return the font for the text run for which this <code>GlyphView</code>
990    *         is responsible
991    */
992   public Font getFont()
993   {
994     Document doc = getDocument();
995     Font font = null;
996     if (doc instanceof StyledDocument)
997       {
998         StyledDocument styledDoc = (StyledDocument) doc;
999         font = styledDoc.getFont(getAttributes());
1000       }
1001     else
1002       {
1003         Container c = getContainer();
1004         if (c != null)
1005           font = c.getFont();
1006       }
1007     return font;
1008   }
1009
1010   /**
1011    * Returns the foreground color which should be used to paint the text.
1012    * This is fetched from the associated element's text attributes using
1013    * {@link StyleConstants#getForeground}.
1014    *
1015    * @return the foreground color which should be used to paint the text
1016    */
1017   public Color getForeground()
1018   {
1019     Element el = getElement();
1020     AttributeSet atts = el.getAttributes();
1021     return StyleConstants.getForeground(atts);
1022   }
1023
1024   /**
1025    * Returns the background color which should be used to paint the text.
1026    * This is fetched from the associated element's text attributes using
1027    * {@link StyleConstants#getBackground}.
1028    *
1029    * @return the background color which should be used to paint the text
1030    */
1031   public Color getBackground()
1032   {
1033     Element el = getElement();
1034     AttributeSet atts = el.getAttributes();
1035     // We cannot use StyleConstants.getBackground() here, because that returns
1036     // BLACK as default (when background == null). What we need is the
1037     // background setting of the text component instead, which is what we get
1038     // when background == null anyway.
1039     return (Color) atts.getAttribute(StyleConstants.Background);
1040   }
1041
1042   /**
1043    * Determines whether the text should be rendered strike-through or not. This
1044    * is determined using the method
1045    * {@link StyleConstants#isStrikeThrough(AttributeSet)} on the element of
1046    * this view.
1047    *
1048    * @return whether the text should be rendered strike-through or not
1049    */
1050   public boolean isStrikeThrough()
1051   {
1052     Element el = getElement();
1053     AttributeSet atts = el.getAttributes();
1054     return StyleConstants.isStrikeThrough(atts);
1055   }
1056
1057   /**
1058    * Determines whether the text should be rendered as subscript or not. This
1059    * is determined using the method
1060    * {@link StyleConstants#isSubscript(AttributeSet)} on the element of
1061    * this view.
1062    *
1063    * @return whether the text should be rendered as subscript or not
1064    */
1065   public boolean isSubscript()
1066   {
1067     Element el = getElement();
1068     AttributeSet atts = el.getAttributes();
1069     return StyleConstants.isSubscript(atts);
1070   }
1071
1072   /**
1073    * Determines whether the text should be rendered as superscript or not. This
1074    * is determined using the method
1075    * {@link StyleConstants#isSuperscript(AttributeSet)} on the element of
1076    * this view.
1077    *
1078    * @return whether the text should be rendered as superscript or not
1079    */
1080   public boolean isSuperscript()
1081   {
1082     Element el = getElement();
1083     AttributeSet atts = el.getAttributes();
1084     return StyleConstants.isSuperscript(atts);
1085   }
1086
1087   /**
1088    * Determines whether the text should be rendered as underlined or not. This
1089    * is determined using the method
1090    * {@link StyleConstants#isUnderline(AttributeSet)} on the element of
1091    * this view.
1092    *
1093    * @return whether the text should be rendered as underlined or not
1094    */
1095   public boolean isUnderline()
1096   {
1097     Element el = getElement();
1098     AttributeSet atts = el.getAttributes();
1099     return StyleConstants.isUnderline(atts);
1100   }
1101
1102   /**
1103    * Creates and returns a shallow clone of this GlyphView. This is used by
1104    * the {@link #createFragment} and {@link #breakView} methods.
1105    *
1106    * @return a shallow clone of this GlyphView
1107    */
1108   protected final Object clone()
1109   {
1110     try
1111       {
1112         return super.clone();
1113       }
1114     catch (CloneNotSupportedException ex)
1115       {
1116         AssertionError err = new AssertionError("CloneNotSupportedException "
1117                                                 + "must not be thrown here");
1118         err.initCause(ex);
1119         throw err;
1120       }
1121   }
1122
1123   /**
1124    * Tries to break the view near the specified view span <code>len</code>.
1125    * The glyph view can only be broken in the X direction. For Y direction it
1126    * returns itself.
1127    *
1128    * @param axis the axis for breaking, may be {@link View#X_AXIS} or
1129    *        {@link View#Y_AXIS}
1130    * @param p0 the model location where the fragment should start
1131    * @param pos the view position along the axis where the fragment starts
1132    * @param len the desired length of the fragment view
1133    *
1134    * @return the fragment view, or <code>this</code> if breaking was not
1135    *         possible
1136    */
1137   public View breakView(int axis, int p0, float pos, float len)
1138   {
1139     View brokenView = this;
1140     if (axis == X_AXIS)
1141       {
1142         checkPainter();
1143         int end = glyphPainter.getBoundedPosition(this, p0, pos, len);
1144         int breakLoc = getBreakLocation(p0, end);
1145         if (breakLoc != -1)
1146           end = breakLoc;
1147         if (p0 != getStartOffset() || end != getEndOffset())
1148           {
1149             brokenView = createFragment(p0, end);
1150             if (brokenView instanceof GlyphView)
1151               ((GlyphView) brokenView).tabX = pos;
1152           }
1153       }
1154     return brokenView;
1155   }
1156
1157   /**
1158    * Determines how well the specified view location is suitable for inserting
1159    * a line break. If <code>axis</code> is <code>View.Y_AXIS</code>, then
1160    * this method forwards to the superclass, if <code>axis</code> is
1161    * <code>View.X_AXIS</code> then this method returns
1162    * {@link View#ExcellentBreakWeight} if there is a suitable break location
1163    * (usually whitespace) within the specified view span, or
1164    * {@link View#GoodBreakWeight} if not.
1165    *
1166    * @param axis the axis along which the break weight is requested
1167    * @param pos the starting view location
1168    * @param len the length of the span at which the view should be broken
1169    *
1170    * @return the break weight
1171    */
1172   public int getBreakWeight(int axis, float pos, float len)
1173   {
1174     int weight;
1175     if (axis == Y_AXIS)
1176       weight = super.getBreakWeight(axis, pos, len);
1177     else
1178       {
1179         checkPainter();
1180         int start = getStartOffset();
1181         int end = glyphPainter.getBoundedPosition(this, start, pos, len);
1182         if (end == 0)
1183           weight = BadBreakWeight;
1184         else
1185           {
1186             if (getBreakLocation(start, end) != -1)
1187               weight = ExcellentBreakWeight;
1188             else
1189               weight = GoodBreakWeight;
1190           }
1191       }
1192     return weight;
1193   }
1194
1195   private int getBreakLocation(int start, int end)
1196   {
1197     int loc = -1;
1198     Segment s = getText(start, end);
1199     for (char c = s.last(); c != Segment.DONE && loc == -1; c = s.previous())
1200       {
1201         if (Character.isWhitespace(c))
1202           {
1203             loc = s.getIndex() - s.getBeginIndex() + 1 + start;
1204           }
1205       }
1206     return loc;
1207   }
1208
1209   /**
1210    * Receives notification that some text attributes have changed within the
1211    * text fragment that this view is responsible for. This calls
1212    * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
1213    * both width and height.
1214    *
1215    * @param e the document event describing the change; not used here
1216    * @param a the view allocation on screen; not used here
1217    * @param vf the view factory; not used here
1218    */
1219   public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf)
1220   {
1221     preferenceChanged(null, true, true);
1222   }
1223
1224   /**
1225    * Receives notification that some text has been inserted within the
1226    * text fragment that this view is responsible for. This calls
1227    * {@link View#preferenceChanged(View, boolean, boolean)} for the
1228    * direction in which the glyphs are rendered.
1229    *
1230    * @param e the document event describing the change; not used here
1231    * @param a the view allocation on screen; not used here
1232    * @param vf the view factory; not used here
1233    */
1234   public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
1235   {
1236     preferenceChanged(null, true, false);
1237   }
1238
1239   /**
1240    * Receives notification that some text has been removed within the
1241    * text fragment that this view is responsible for. This calls
1242    * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
1243    * width.
1244    *
1245    * @param e the document event describing the change; not used here
1246    * @param a the view allocation on screen; not used here
1247    * @param vf the view factory; not used here
1248    */
1249   public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
1250   {
1251     preferenceChanged(null, true, false);
1252   }
1253
1254   /**
1255    * Creates a fragment view of this view that starts at <code>p0</code> and
1256    * ends at <code>p1</code>.
1257    *
1258    * @param p0 the start location for the fragment view
1259    * @param p1 the end location for the fragment view
1260    *
1261    * @return the fragment view
1262    */
1263   public View createFragment(int p0, int p1)
1264   {
1265     checkPainter();
1266     Element el = getElement();
1267     GlyphView fragment = (GlyphView) clone();
1268     fragment.offset = p0 - el.getStartOffset();
1269     fragment.length = p1 - p0;
1270     fragment.glyphPainter = glyphPainter.getPainter(fragment, p0, p1);
1271     return fragment;
1272   }
1273
1274   /**
1275    * Returns the alignment of this view along the specified axis. For the Y
1276    * axis this is <code>(height - descent) / height</code> for the used font,
1277    * so that it is aligned along the baseline.
1278    * For the X axis the superclass is called.
1279    */
1280   public float getAlignment(int axis)
1281   {
1282     checkPainter();
1283     float align;
1284     if (axis == Y_AXIS)
1285       {
1286         GlyphPainter painter = getGlyphPainter();
1287         float height = painter.getHeight(this);
1288         float descent = painter.getDescent(this);
1289         float ascent = painter.getAscent(this);
1290         if (isSuperscript())
1291           align = 1.0F;
1292         else if (isSubscript())
1293           align = height > 0 ? (height - (descent + (ascent / 2))) / height
1294                              : 0;
1295         else
1296           align = height > 0 ? (height - descent) / height : 0;
1297       }
1298     else
1299       align = super.getAlignment(axis);
1300
1301     return align;
1302   }
1303
1304   /**
1305    * Returns the model location that should be used to place a caret when
1306    * moving the caret through the document.
1307    *
1308    * @param pos the current model location
1309    * @param bias the bias for <code>p</code>
1310    * @param a the allocated region for the glyph view
1311    * @param direction the direction from the current position; Must be one of
1312    *        {@link SwingConstants#EAST}, {@link SwingConstants#WEST},
1313    *        {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH}
1314    * @param biasRet filled with the bias of the resulting location when method
1315    *        returns
1316    *
1317    * @return the location within the document that should be used to place the
1318    *         caret when moving the caret around the document
1319    *
1320    * @throws BadLocationException if <code>pos</code> is an invalid model
1321    *         location
1322    * @throws IllegalArgumentException if <code>d</code> is invalid
1323    */
1324   public int getNextVisualPositionFrom(int pos, Position.Bias bias, Shape a,
1325                                        int direction, Position.Bias[] biasRet)
1326     throws BadLocationException
1327   {
1328     checkPainter();
1329     GlyphPainter painter = getGlyphPainter();
1330     return painter.getNextVisualPositionFrom(this, pos, bias, a, direction,
1331                                              biasRet);
1332   }
1333 }