001    /* GlyphView.java -- A view to render styled text
002       Copyright (C) 2005  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.text;
040    
041    import gnu.classpath.SystemProperties;
042    
043    import java.awt.Color;
044    import java.awt.Container;
045    import java.awt.Font;
046    import java.awt.FontMetrics;
047    import java.awt.Graphics;
048    import java.awt.Graphics2D;
049    import java.awt.Rectangle;
050    import java.awt.Shape;
051    import java.awt.Toolkit;
052    import java.awt.font.FontRenderContext;
053    import java.awt.font.TextHitInfo;
054    import java.awt.font.TextLayout;
055    import java.awt.geom.Rectangle2D;
056    
057    import javax.swing.SwingConstants;
058    import javax.swing.event.DocumentEvent;
059    import javax.swing.text.Position.Bias;
060    
061    /**
062     * Renders a run of styled text. This {@link View} subclass paints the
063     * characters of the <code>Element</code> it is responsible for using
064     * the style information from that <code>Element</code>.
065     *
066     * @author Roman Kennke (roman@kennke.org)
067     */
068    public class GlyphView extends View implements TabableView, Cloneable
069    {
070    
071      /**
072       * An abstract base implementation for a glyph painter for
073       * <code>GlyphView</code>.
074       */
075      public abstract static class GlyphPainter
076      {
077        /**
078         * Creates a new <code>GlyphPainer</code>.
079         */
080        public GlyphPainter()
081        {
082          // Nothing to do here.
083        }
084    
085        /**
086         * Returns the ascent of the font that is used by this glyph painter.
087         *
088         * @param v the glyph view
089         *
090         * @return the ascent of the font that is used by this glyph painter
091         */
092        public abstract float getAscent(GlyphView v);
093    
094        /**
095         * Returns the descent of the font that is used by this glyph painter.
096         *
097         * @param v the glyph view
098         *
099         * @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            System.err.println("fill background: " + bg);
740            g.fillRect(r.x, r.y, r.width, r.height);
741          }
742    
743        
744        // Paint layered highlights if there are any.
745        if (tc != null)
746          {
747            Highlighter h = tc.getHighlighter();
748            if (h instanceof LayeredHighlighter)
749              {
750                LayeredHighlighter lh = (LayeredHighlighter) h;
751                lh.paintLayeredHighlights(g, p0, p1, a, tc, this);
752              }
753          }
754    
755        g.setColor(fg);
756        glyphPainter.paint(this, g, a, p0, p1);
757        boolean underline = isUnderline();
758        boolean striked = isStrikeThrough();
759        if (underline || striked)
760          {
761            View parent = getParent();
762            // X coordinate.
763            if (parent != null && parent.getEndOffset() == p1)
764              {
765                // Strip whitespace.
766                Segment s = getText(p0, p1);
767                while (s.count > 0 && Character.isWhitespace(s.array[s.count - 1]))
768                  {
769                    p1--;
770                    s.count--;
771                  }
772              }
773            int x0 = r.x;
774            int p = getStartOffset();
775            TabExpander tabEx = getTabExpander();
776            if (p != p0)
777              x0 += (int) glyphPainter.getSpan(this, p, p0, tabEx, x0);
778            int x1 = x0 + (int) glyphPainter.getSpan(this, p0, p1, tabEx, x0);
779            // Y coordinate.
780            int y = r.y + r.height - (int) glyphPainter.getDescent(this);
781            if (underline)
782              {
783                int yTmp = y;
784                yTmp += 1;
785                g.drawLine(x0, yTmp, x1, yTmp);
786              }
787            if (striked)
788              {
789                int yTmp = y;
790                yTmp -= (int) glyphPainter.getAscent(this);
791                g.drawLine(x0, yTmp, x1, yTmp);
792              }
793          }
794      }
795    
796    
797      /**
798       * Returns the preferred span of the content managed by this
799       * <code>View</code> along the specified <code>axis</code>.
800       *
801       * @param axis the axis
802       *
803       * @return the preferred span of this <code>View</code>.
804       */
805      public float getPreferredSpan(int axis)
806      {
807        float span = 0;
808        checkPainter();
809        GlyphPainter painter = getGlyphPainter();
810        switch (axis)
811          {
812          case X_AXIS:
813            TabExpander tabEx = null;
814            View parent = getParent();
815            if (parent instanceof TabExpander)
816              tabEx = (TabExpander) parent;
817            span = painter.getSpan(this, getStartOffset(), getEndOffset(),
818                                   tabEx, 0.F);
819            break;
820          case Y_AXIS:
821            span = painter.getHeight(this);
822            if (isSuperscript())
823              span += span / 3;
824            break;
825          default:
826            throw new IllegalArgumentException("Illegal axis");
827          }
828        return span;
829      }
830    
831      /**
832       * Maps a position in the document into the coordinate space of the View.
833       * The output rectangle usually reflects the font height but has a width
834       * of zero.
835       *
836       * @param pos the position of the character in the model
837       * @param a the area that is occupied by the view
838       * @param b either {@link Position.Bias#Forward} or
839       *        {@link Position.Bias#Backward} depending on the preferred
840       *        direction bias. If <code>null</code> this defaults to
841       *        <code>Position.Bias.Forward</code>
842       *
843       * @return a rectangle that gives the location of the document position
844       *         inside the view coordinate space
845       *
846       * @throws BadLocationException if <code>pos</code> is invalid
847       * @throws IllegalArgumentException if b is not one of the above listed
848       *         valid values
849       */
850      public Shape modelToView(int pos, Shape a, Position.Bias b)
851        throws BadLocationException
852      {
853        GlyphPainter p = getGlyphPainter();
854        return p.modelToView(this, pos, b, a);
855      }
856    
857      /**
858       * Maps coordinates from the <code>View</code>'s space into a position
859       * in the document model.
860       *
861       * @param x the x coordinate in the view space
862       * @param y the y coordinate in the view space
863       * @param a the allocation of this <code>View</code>
864       * @param b the bias to use
865       *
866       * @return the position in the document that corresponds to the screen
867       *         coordinates <code>x, y</code>
868       */
869      public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
870      {
871        checkPainter();
872        GlyphPainter painter = getGlyphPainter();
873        return painter.viewToModel(this, x, y, a, b);
874      }
875    
876      /**
877       * Return the {@link TabExpander} to use.
878       *
879       * @return the {@link TabExpander} to use
880       */
881      public TabExpander getTabExpander()
882      {
883        return tabExpander;
884      }
885    
886      /**
887       * Returns the preferred span of this view for tab expansion.
888       *
889       * @param x the location of the view
890       * @param te the tab expander to use
891       *
892       * @return the preferred span of this view for tab expansion
893       */
894      public float getTabbedSpan(float x, TabExpander te)
895      {
896        checkPainter();
897        TabExpander old = tabExpander;
898        tabExpander = te;
899        if (tabExpander != old)
900          {
901            // Changing the tab expander will lead to a relayout in the X_AXIS.
902            preferenceChanged(null, true, false);
903          }
904        tabX = x;
905        return getGlyphPainter().getSpan(this, getStartOffset(),
906                                         getEndOffset(), tabExpander, x);
907      }
908    
909      /**
910       * Returns the span of a portion of the view. This is used in TAB expansion
911       * for fragments that don't contain TABs.
912       *
913       * @param p0 the start index
914       * @param p1 the end index
915       *
916       * @return the span of the specified portion of the view
917       */
918      public float getPartialSpan(int p0, int p1)
919      {
920        checkPainter();
921        return glyphPainter.getSpan(this, p0, p1, tabExpander, tabX);
922      }
923    
924      /**
925       * Returns the start offset in the document model of the portion
926       * of text that this view is responsible for.
927       *
928       * @return the start offset in the document model of the portion
929       *         of text that this view is responsible for
930       */
931      public int getStartOffset()
932      {
933        Element el = getElement();
934        int offs = el.getStartOffset();
935        if (length > 0)
936          offs += offset;
937        return offs;
938      }
939    
940      /**
941       * Returns the end offset in the document model of the portion
942       * of text that this view is responsible for.
943       *
944       * @return the end offset in the document model of the portion
945       *         of text that this view is responsible for
946       */
947      public int getEndOffset()
948      {
949        Element el = getElement();
950        int offs;
951        if (length > 0)
952          offs = el.getStartOffset() + offset + length;
953        else
954          offs = el.getEndOffset();
955        return offs;
956      }
957    
958      private Segment cached = new Segment();
959    
960      /**
961       * Returns the text segment that this view is responsible for.
962       *
963       * @param p0 the start index in the document model
964       * @param p1 the end index in the document model
965       *
966       * @return the text segment that this view is responsible for
967       */
968      public Segment getText(int p0, int p1)
969      {
970        try
971          {
972            getDocument().getText(p0, p1 - p0, cached);
973          }
974        catch (BadLocationException ex)
975          {
976            AssertionError ae;
977            ae = new AssertionError("BadLocationException should not be "
978                                    + "thrown here. p0 = " + p0 + ", p1 = " + p1);
979            ae.initCause(ex);
980            throw ae;
981          }
982    
983        return cached;
984      }
985    
986      /**
987       * Returns the font for the text run for which this <code>GlyphView</code>
988       * is responsible.
989       *
990       * @return the font for the text run for which this <code>GlyphView</code>
991       *         is responsible
992       */
993      public Font getFont()
994      {
995        Document doc = getDocument();
996        Font font = null;
997        if (doc instanceof StyledDocument)
998          {
999            StyledDocument styledDoc = (StyledDocument) doc;
1000            font = styledDoc.getFont(getAttributes());
1001          }
1002        else
1003          {
1004            Container c = getContainer();
1005            if (c != null)
1006              font = c.getFont();
1007          }
1008        return font;
1009      }
1010    
1011      /**
1012       * Returns the foreground color which should be used to paint the text.
1013       * This is fetched from the associated element's text attributes using
1014       * {@link StyleConstants#getForeground}.
1015       *
1016       * @return the foreground color which should be used to paint the text
1017       */
1018      public Color getForeground()
1019      {
1020        Element el = getElement();
1021        AttributeSet atts = el.getAttributes();
1022        return StyleConstants.getForeground(atts);
1023      }
1024    
1025      /**
1026       * Returns the background color which should be used to paint the text.
1027       * This is fetched from the associated element's text attributes using
1028       * {@link StyleConstants#getBackground}.
1029       *
1030       * @return the background color which should be used to paint the text
1031       */
1032      public Color getBackground()
1033      {
1034        Element el = getElement();
1035        AttributeSet atts = el.getAttributes();
1036        // We cannot use StyleConstants.getBackground() here, because that returns
1037        // BLACK as default (when background == null). What we need is the
1038        // background setting of the text component instead, which is what we get
1039        // when background == null anyway.
1040        return (Color) atts.getAttribute(StyleConstants.Background);
1041      }
1042    
1043      /**
1044       * Determines whether the text should be rendered strike-through or not. This
1045       * is determined using the method
1046       * {@link StyleConstants#isStrikeThrough(AttributeSet)} on the element of
1047       * this view.
1048       *
1049       * @return whether the text should be rendered strike-through or not
1050       */
1051      public boolean isStrikeThrough()
1052      {
1053        Element el = getElement();
1054        AttributeSet atts = el.getAttributes();
1055        return StyleConstants.isStrikeThrough(atts);
1056      }
1057    
1058      /**
1059       * Determines whether the text should be rendered as subscript or not. This
1060       * is determined using the method
1061       * {@link StyleConstants#isSubscript(AttributeSet)} on the element of
1062       * this view.
1063       *
1064       * @return whether the text should be rendered as subscript or not
1065       */
1066      public boolean isSubscript()
1067      {
1068        Element el = getElement();
1069        AttributeSet atts = el.getAttributes();
1070        return StyleConstants.isSubscript(atts);
1071      }
1072    
1073      /**
1074       * Determines whether the text should be rendered as superscript or not. This
1075       * is determined using the method
1076       * {@link StyleConstants#isSuperscript(AttributeSet)} on the element of
1077       * this view.
1078       *
1079       * @return whether the text should be rendered as superscript or not
1080       */
1081      public boolean isSuperscript()
1082      {
1083        Element el = getElement();
1084        AttributeSet atts = el.getAttributes();
1085        return StyleConstants.isSuperscript(atts);
1086      }
1087    
1088      /**
1089       * Determines whether the text should be rendered as underlined or not. This
1090       * is determined using the method
1091       * {@link StyleConstants#isUnderline(AttributeSet)} on the element of
1092       * this view.
1093       *
1094       * @return whether the text should be rendered as underlined or not
1095       */
1096      public boolean isUnderline()
1097      {
1098        Element el = getElement();
1099        AttributeSet atts = el.getAttributes();
1100        return StyleConstants.isUnderline(atts);
1101      }
1102    
1103      /**
1104       * Creates and returns a shallow clone of this GlyphView. This is used by
1105       * the {@link #createFragment} and {@link #breakView} methods.
1106       *
1107       * @return a shallow clone of this GlyphView
1108       */
1109      protected final Object clone()
1110      {
1111        try
1112          {
1113            return super.clone();
1114          }
1115        catch (CloneNotSupportedException ex)
1116          {
1117            AssertionError err = new AssertionError("CloneNotSupportedException "
1118                                                    + "must not be thrown here");
1119            err.initCause(ex);
1120            throw err;
1121          }
1122      }
1123    
1124      /**
1125       * Tries to break the view near the specified view span <code>len</code>.
1126       * The glyph view can only be broken in the X direction. For Y direction it
1127       * returns itself.
1128       *
1129       * @param axis the axis for breaking, may be {@link View#X_AXIS} or
1130       *        {@link View#Y_AXIS}
1131       * @param p0 the model location where the fragment should start
1132       * @param pos the view position along the axis where the fragment starts
1133       * @param len the desired length of the fragment view
1134       *
1135       * @return the fragment view, or <code>this</code> if breaking was not
1136       *         possible
1137       */
1138      public View breakView(int axis, int p0, float pos, float len)
1139      {
1140        View brokenView = this;
1141        if (axis == X_AXIS)
1142          {
1143            checkPainter();
1144            int end = glyphPainter.getBoundedPosition(this, p0, pos, len);
1145            int breakLoc = getBreakLocation(p0, end);
1146            if (breakLoc != -1)
1147              end = breakLoc;
1148            if (p0 != getStartOffset() || end != getEndOffset())
1149              {
1150                brokenView = createFragment(p0, end);
1151                if (brokenView instanceof GlyphView)
1152                  ((GlyphView) brokenView).tabX = pos;
1153              }
1154          }
1155        return brokenView;
1156      }
1157    
1158      /**
1159       * Determines how well the specified view location is suitable for inserting
1160       * a line break. If <code>axis</code> is <code>View.Y_AXIS</code>, then
1161       * this method forwards to the superclass, if <code>axis</code> is
1162       * <code>View.X_AXIS</code> then this method returns
1163       * {@link View#ExcellentBreakWeight} if there is a suitable break location
1164       * (usually whitespace) within the specified view span, or
1165       * {@link View#GoodBreakWeight} if not.
1166       *
1167       * @param axis the axis along which the break weight is requested
1168       * @param pos the starting view location
1169       * @param len the length of the span at which the view should be broken
1170       *
1171       * @return the break weight
1172       */
1173      public int getBreakWeight(int axis, float pos, float len)
1174      {
1175        int weight;
1176        if (axis == Y_AXIS)
1177          weight = super.getBreakWeight(axis, pos, len);
1178        else
1179          {
1180            checkPainter();
1181            int start = getStartOffset();
1182            int end = glyphPainter.getBoundedPosition(this, start, pos, len);
1183            if (end == 0)
1184              weight = BadBreakWeight;
1185            else
1186              {
1187                if (getBreakLocation(start, end) != -1)
1188                  weight = ExcellentBreakWeight;
1189                else
1190                  weight = GoodBreakWeight;
1191              }
1192          }
1193        return weight;
1194      }
1195    
1196      private int getBreakLocation(int start, int end)
1197      {
1198        int loc = -1;
1199        Segment s = getText(start, end);
1200        for (char c = s.last(); c != Segment.DONE && loc == -1; c = s.previous())
1201          {
1202            if (Character.isWhitespace(c))
1203              {
1204                loc = s.getIndex() - s.getBeginIndex() + 1 + start;
1205              }
1206          }
1207        return loc;
1208      }
1209    
1210      /**
1211       * Receives notification that some text attributes have changed within the
1212       * text fragment that this view is responsible for. This calls
1213       * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
1214       * both width and height.
1215       *
1216       * @param e the document event describing the change; not used here
1217       * @param a the view allocation on screen; not used here
1218       * @param vf the view factory; not used here
1219       */
1220      public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf)
1221      {
1222        preferenceChanged(null, true, true);
1223      }
1224    
1225      /**
1226       * Receives notification that some text has been inserted within the
1227       * text fragment that this view is responsible for. This calls
1228       * {@link View#preferenceChanged(View, boolean, boolean)} for the
1229       * direction in which the glyphs are rendered.
1230       *
1231       * @param e the document event describing the change; not used here
1232       * @param a the view allocation on screen; not used here
1233       * @param vf the view factory; not used here
1234       */
1235      public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
1236      {
1237        preferenceChanged(null, true, false);
1238      }
1239    
1240      /**
1241       * Receives notification that some text has been removed within the
1242       * text fragment that this view is responsible for. This calls
1243       * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
1244       * width.
1245       *
1246       * @param e the document event describing the change; not used here
1247       * @param a the view allocation on screen; not used here
1248       * @param vf the view factory; not used here
1249       */
1250      public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
1251      {
1252        preferenceChanged(null, true, false);
1253      }
1254    
1255      /**
1256       * Creates a fragment view of this view that starts at <code>p0</code> and
1257       * ends at <code>p1</code>.
1258       *
1259       * @param p0 the start location for the fragment view
1260       * @param p1 the end location for the fragment view
1261       *
1262       * @return the fragment view
1263       */
1264      public View createFragment(int p0, int p1)
1265      {
1266        checkPainter();
1267        Element el = getElement();
1268        GlyphView fragment = (GlyphView) clone();
1269        fragment.offset = p0 - el.getStartOffset();
1270        fragment.length = p1 - p0;
1271        fragment.glyphPainter = glyphPainter.getPainter(fragment, p0, p1);
1272        return fragment;
1273      }
1274    
1275      /**
1276       * Returns the alignment of this view along the specified axis. For the Y
1277       * axis this is <code>(height - descent) / height</code> for the used font,
1278       * so that it is aligned along the baseline.
1279       * For the X axis the superclass is called.
1280       */
1281      public float getAlignment(int axis)
1282      {
1283        checkPainter();
1284        float align;
1285        if (axis == Y_AXIS)
1286          {
1287            GlyphPainter painter = getGlyphPainter();
1288            float height = painter.getHeight(this);
1289            float descent = painter.getDescent(this);
1290            float ascent = painter.getAscent(this);
1291            if (isSuperscript())
1292              align = 1.0F;
1293            else if (isSubscript())
1294              align = height > 0 ? (height - (descent + (ascent / 2))) / height
1295                                 : 0;
1296            else
1297              align = height > 0 ? (height - descent) / height : 0;
1298          }
1299        else
1300          align = super.getAlignment(axis);
1301    
1302        return align;
1303      }
1304    
1305      /**
1306       * Returns the model location that should be used to place a caret when
1307       * moving the caret through the document.
1308       *
1309       * @param pos the current model location
1310       * @param bias the bias for <code>p</code>
1311       * @param a the allocated region for the glyph view
1312       * @param direction the direction from the current position; Must be one of
1313       *        {@link SwingConstants#EAST}, {@link SwingConstants#WEST},
1314       *        {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH}
1315       * @param biasRet filled with the bias of the resulting location when method
1316       *        returns
1317       *
1318       * @return the location within the document that should be used to place the
1319       *         caret when moving the caret around the document
1320       *
1321       * @throws BadLocationException if <code>pos</code> is an invalid model
1322       *         location
1323       * @throws IllegalArgumentException if <code>d</code> is invalid
1324       */
1325      public int getNextVisualPositionFrom(int pos, Position.Bias bias, Shape a,
1326                                           int direction, Position.Bias[] biasRet)
1327        throws BadLocationException
1328      {
1329        checkPainter();
1330        GlyphPainter painter = getGlyphPainter();
1331        return painter.getNextVisualPositionFrom(this, pos, bias, a, direction,
1332                                                 biasRet);
1333      }
1334    }