Source for javax.swing.text.GlyphView

   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 java.awt.Color;
  42: import java.awt.Font;
  43: import java.awt.FontMetrics;
  44: import java.awt.Graphics;
  45: import java.awt.Rectangle;
  46: import java.awt.Shape;
  47: import java.awt.Toolkit;
  48: import java.text.BreakIterator;
  49: 
  50: import javax.swing.SwingConstants;
  51: import javax.swing.event.DocumentEvent;
  52: import javax.swing.text.Position.Bias;
  53: 
  54: /**
  55:  * Renders a run of styled text. This {@link View} subclass paints the
  56:  * characters of the <code>Element</code> it is responsible for using
  57:  * the style information from that <code>Element</code>.
  58:  *
  59:  * @author Roman Kennke (roman@kennke.org)
  60:  */
  61: public class GlyphView extends View implements TabableView, Cloneable
  62: {
  63: 
  64:   /**
  65:    * An abstract base implementation for a glyph painter for
  66:    * <code>GlyphView</code>.
  67:    */
  68:   public abstract static class GlyphPainter
  69:   {
  70:     /**
  71:      * Creates a new <code>GlyphPainer</code>.
  72:      */
  73:     public GlyphPainter()
  74:     {
  75:       // Nothing to do here.
  76:     }
  77: 
  78:     /**
  79:      * Returns the ascent of the font that is used by this glyph painter.
  80:      *
  81:      * @param v the glyph view
  82:      *
  83:      * @return the ascent of the font that is used by this glyph painter
  84:      */
  85:     public abstract float getAscent(GlyphView v);
  86: 
  87:     /**
  88:      * Returns the descent of the font that is used by this glyph painter.
  89:      *
  90:      * @param v the glyph view
  91:      *
  92:      * @return the descent of the font that is used by this glyph painter
  93:      */
  94:     public abstract float getDescent(GlyphView v);
  95: 
  96:     /**
  97:      * Returns the full height of the rendered text.
  98:      *
  99:      * @return the full height of the rendered text
 100:      */
 101:     public abstract float getHeight(GlyphView view);
 102: 
 103:     /**
 104:      * Determines the model offset, so that the text between <code>p0</code>
 105:      * and this offset fits within the span starting at <code>x</code> with
 106:      * the length of <code>len</code>. 
 107:      *
 108:      * @param v the glyph view
 109:      * @param p0 the starting offset in the model
 110:      * @param x the start location in the view
 111:      * @param len the length of the span in the view
 112:      */
 113:     public abstract int getBoundedPosition(GlyphView v, int p0, float x,
 114:                                            float len);
 115: 
 116:     /**
 117:      * Paints the glyphs.
 118:      *
 119:      * @param view the glyph view to paint
 120:      * @param g the graphics context to use for painting
 121:      * @param a the allocation of the glyph view
 122:      * @param p0 the start position (in the model) from which to paint
 123:      * @param p1 the end position (in the model) to which to paint
 124:      */
 125:     public abstract void paint(GlyphView view, Graphics g, Shape a, int p0,
 126:                                int p1);
 127: 
 128:     /**
 129:      * Maps a position in the document into the coordinate space of the View.
 130:      * The output rectangle usually reflects the font height but has a width
 131:      * of zero.
 132:      *
 133:      * @param view the glyph view
 134:      * @param pos the position of the character in the model
 135:      * @param a the area that is occupied by the view
 136:      * @param b either {@link Position.Bias#Forward} or
 137:      *        {@link Position.Bias#Backward} depending on the preferred
 138:      *        direction bias. If <code>null</code> this defaults to
 139:      *        <code>Position.Bias.Forward</code>
 140:      *
 141:      * @return a rectangle that gives the location of the document position
 142:      *         inside the view coordinate space
 143:      *
 144:      * @throws BadLocationException if <code>pos</code> is invalid
 145:      * @throws IllegalArgumentException if b is not one of the above listed
 146:      *         valid values
 147:      */
 148:     public abstract Shape modelToView(GlyphView view, int pos, Position.Bias b,
 149:                                       Shape a)
 150:       throws BadLocationException;
 151: 
 152:     /**
 153:      * Maps a visual position into a document location.
 154:      *
 155:      * @param v the glyph view
 156:      * @param x the X coordinate of the visual position
 157:      * @param y the Y coordinate of the visual position
 158:      * @param a the allocated region
 159:      * @param biasRet filled with the bias of the model location on method exit
 160:      *
 161:      * @return the model location that represents the specified view location
 162:      */
 163:     public abstract int viewToModel(GlyphView v, float x, float y, Shape a,
 164:                                     Position.Bias[] biasRet);
 165: 
 166:     /**
 167:      * Determine the span of the glyphs from location <code>p0</code> to
 168:      * location <code>p1</code>. If <code>te</code> is not <code>null</code>,
 169:      * then TABs are expanded using this <code>TabExpander</code>.
 170:      * The parameter <code>x</code> is the location at which the view is
 171:      * located (this is important when using TAB expansion).
 172:      *
 173:      * @param view the glyph view
 174:      * @param p0 the starting location in the document model
 175:      * @param p1 the end location in the document model
 176:      * @param te the tab expander to use
 177:      * @param x the location at which the view is located
 178:      *
 179:      * @return the span of the glyphs from location <code>p0</code> to
 180:      *         location <code>p1</code>, possibly using TAB expansion
 181:      */
 182:     public abstract float getSpan(GlyphView view, int p0, int p1,
 183:                                   TabExpander te, float x);
 184: 
 185: 
 186:     /**
 187:      * Returns the model location that should be used to place a caret when
 188:      * moving the caret through the document.
 189:      *
 190:      * @param v the glyph view
 191:      * @param pos the current model location
 192:      * @param b the bias for <code>p</code>
 193:      * @param a the allocated region for the glyph view
 194:      * @param direction the direction from the current position; Must be one of
 195:      *        {@link SwingConstants#EAST}, {@link SwingConstants#WEST},
 196:      *        {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH}
 197:      * @param biasRet filled with the bias of the resulting location when method
 198:      *        returns
 199:      *
 200:      * @return the location within the document that should be used to place the
 201:      *         caret when moving the caret around the document
 202:      *
 203:      * @throws BadLocationException if <code>pos</code> is an invalid model
 204:      *         location
 205:      * @throws IllegalArgumentException if <code>d</code> is invalid
 206:      */
 207:     public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b,
 208:                                          Shape a, int direction,
 209:                                          Position.Bias[] biasRet)
 210:       throws BadLocationException
 211: 
 212:     {
 213:       int result = pos;
 214:       switch (direction)
 215:       {
 216:         case SwingConstants.EAST:
 217:           result = pos + 1;
 218:           break;
 219:         case SwingConstants.WEST:
 220:           result = pos - 1;
 221:           break;
 222:         case SwingConstants.NORTH:
 223:         case SwingConstants.SOUTH:
 224:         default:
 225:           // This should be handled in enclosing view, since the glyph view
 226:           // does not layout vertically.
 227:           break;
 228:       }
 229:       return result;
 230:     }
 231: 
 232:     /**
 233:      * Returns a painter that can be used to render the specified glyph view.
 234:      * If this glyph painter is stateful, then it should return a new instance.
 235:      * However, if this painter is stateless it should return itself. The
 236:      * default behaviour is to return itself.
 237:      *
 238:      * @param v the glyph view for which to create a painter
 239:      * @param p0 the start offset of the rendered area
 240:      * @param p1 the end offset of the rendered area
 241:      *
 242:      * @return a painter that can be used to render the specified glyph view
 243:      */
 244:     public GlyphPainter getPainter(GlyphView v, int p0, int p1)
 245:     {
 246:       return this;
 247:     }
 248:   }
 249: 
 250:   /**
 251:    * The default <code>GlyphPainter</code> used in <code>GlyphView</code>.
 252:    */
 253:   static class DefaultGlyphPainter extends GlyphPainter
 254:   {
 255:     /**
 256:      * Returns the full height of the rendered text.
 257:      *
 258:      * @return the full height of the rendered text
 259:      */
 260:     public float getHeight(GlyphView view)
 261:     {
 262:       Font font = view.getFont();
 263:       FontMetrics metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
 264:       float height = metrics.getHeight();
 265:       return height;
 266:     }
 267:     
 268:     /**
 269:      * Paints the glyphs.
 270:      *
 271:      * @param view the glyph view to paint
 272:      * @param g the graphics context to use for painting
 273:      * @param a the allocation of the glyph view
 274:      * @param p0 the start position (in the model) from which to paint
 275:      * @param p1 the end position (in the model) to which to paint
 276:      */
 277:     public void paint(GlyphView view, Graphics g, Shape a, int p0,
 278:                       int p1)
 279:     {
 280:       int height = (int) getHeight(view);
 281:       Segment txt = view.getText(p0, p1);
 282:       Rectangle bounds = a.getBounds();
 283: 
 284:       TabExpander tabEx = null;
 285:       View parent = view.getParent();
 286:       if (parent instanceof TabExpander)
 287:         tabEx = (TabExpander) parent;
 288: 
 289:       // Fill the background of the text run.
 290:       Color background = view.getBackground();
 291:       g.setColor(background);
 292:       int width = Utilities.getTabbedTextWidth(txt, g.getFontMetrics(),
 293:                                                bounds.x, tabEx, txt.offset);
 294:       g.fillRect(bounds.x, bounds.y, width, height);
 295: 
 296:       // Draw the actual text.
 297:       g.setColor(view.getForeground());
 298:       g.setFont(view.getFont());
 299:       if (view.isSuperscript())
 300:         // TODO: Adjust font for superscripting.
 301:         Utilities.drawTabbedText(txt, bounds.x, bounds.y - height / 2, g, tabEx,
 302:                                    txt.offset);
 303:       else if (view.isSubscript())
 304:         // TODO: Adjust font for subscripting.
 305:         Utilities.drawTabbedText(txt, bounds.x, bounds.y + height / 2, g, tabEx,
 306:                                  txt.offset);
 307:       else
 308:         Utilities.drawTabbedText(txt, bounds.x, bounds.y, g, tabEx,
 309:                                  txt.offset);
 310: 
 311:       if (view.isStikeThrough())
 312:         {
 313:           int strikeHeight = (int) (getAscent(view) / 2);
 314:           g.drawLine(bounds.x, bounds.y + strikeHeight, bounds.height + width,
 315:                      bounds.y + strikeHeight);
 316:         }
 317:       if (view.isUnderline())
 318:         {
 319:           int lineHeight = (int) getAscent(view);
 320:           g.drawLine(bounds.x, bounds.y + lineHeight, bounds.height + width,
 321:                      bounds.y + lineHeight);
 322:         }
 323:     }
 324: 
 325:     /**
 326:      * Maps a position in the document into the coordinate space of the View.
 327:      * The output rectangle usually reflects the font height but has a width
 328:      * of zero.
 329:      *
 330:      * @param view the glyph view
 331:      * @param pos the position of the character in the model
 332:      * @param a the area that is occupied by the view
 333:      * @param b either {@link Position.Bias#Forward} or
 334:      *        {@link Position.Bias#Backward} depending on the preferred
 335:      *        direction bias. If <code>null</code> this defaults to
 336:      *        <code>Position.Bias.Forward</code>
 337:      *
 338:      * @return a rectangle that gives the location of the document position
 339:      *         inside the view coordinate space
 340:      *
 341:      * @throws BadLocationException if <code>pos</code> is invalid
 342:      * @throws IllegalArgumentException if b is not one of the above listed
 343:      *         valid values
 344:      */
 345:     public Shape modelToView(GlyphView view, int pos, Position.Bias b,
 346:                              Shape a)
 347:       throws BadLocationException
 348:     {
 349:       Element el = view.getElement();
 350:       Font font = view.getFont();
 351:       FontMetrics fm = view.getContainer().getFontMetrics(font);
 352:       Segment txt = view.getText(el.getStartOffset(), pos);
 353:       int width = fm.charsWidth(txt.array, txt.offset, txt.count);
 354:       int height = fm.getHeight();
 355:       Rectangle bounds = a.getBounds();
 356:       Rectangle result = new Rectangle(bounds.x + width, bounds.y,
 357:                                        bounds.x + width, height);
 358:       return result;
 359:     }
 360: 
 361:     /**
 362:      * Determine the span of the glyphs from location <code>p0</code> to
 363:      * location <code>p1</code>. If <code>te</code> is not <code>null</code>,
 364:      * then TABs are expanded using this <code>TabExpander</code>.
 365:      * The parameter <code>x</code> is the location at which the view is
 366:      * located (this is important when using TAB expansion).
 367:      *
 368:      * @param view the glyph view
 369:      * @param p0 the starting location in the document model
 370:      * @param p1 the end location in the document model
 371:      * @param te the tab expander to use
 372:      * @param x the location at which the view is located
 373:      *
 374:      * @return the span of the glyphs from location <code>p0</code> to
 375:      *         location <code>p1</code>, possibly using TAB expansion
 376:      */
 377:     public float getSpan(GlyphView view, int p0, int p1,
 378:                          TabExpander te, float x)
 379:     {
 380:       Element el = view.getElement();
 381:       Font font = view.getFont();
 382:       FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
 383:       Segment txt = view.getText(p0, p1);
 384:       int span = Utilities.getTabbedTextWidth(txt, fm, (int) x, te, p0);
 385:       return span;
 386:     }
 387: 
 388:     /**
 389:      * Returns the ascent of the text run that is rendered by this
 390:      * <code>GlyphPainter</code>.
 391:      *
 392:      * @param v the glyph view
 393:      *
 394:      * @return the ascent of the text run that is rendered by this
 395:      *         <code>GlyphPainter</code>
 396:      *
 397:      * @see FontMetrics#getAscent()
 398:      */
 399:     public float getAscent(GlyphView v)
 400:     {
 401:       Font font = v.getFont();
 402:       FontMetrics fm = v.getContainer().getFontMetrics(font);
 403:       return fm.getAscent();
 404:     }
 405: 
 406:     /**
 407:      * Returns the descent of the text run that is rendered by this
 408:      * <code>GlyphPainter</code>.
 409:      *
 410:      * @param v the glyph view
 411:      *
 412:      * @return the descent of the text run that is rendered by this
 413:      *         <code>GlyphPainter</code>
 414:      *
 415:      * @see FontMetrics#getDescent()
 416:      */
 417:     public float getDescent(GlyphView v)
 418:     {
 419:       Font font = v.getFont();
 420:       FontMetrics fm = v.getContainer().getFontMetrics(font);
 421:       return fm.getDescent();
 422:     }
 423: 
 424:     /**
 425:      * Determines the model offset, so that the text between <code>p0</code>
 426:      * and this offset fits within the span starting at <code>x</code> with
 427:      * the length of <code>len</code>. 
 428:      *
 429:      * @param v the glyph view
 430:      * @param p0 the starting offset in the model
 431:      * @param x the start location in the view
 432:      * @param len the length of the span in the view
 433:      */
 434:     public int getBoundedPosition(GlyphView v, int p0, float x, float len)
 435:     {
 436:       TabExpander te = v.getTabExpander();
 437:       Segment txt = v.getText(p0, v.getEndOffset());
 438:       Font font = v.getFont();
 439:       FontMetrics fm = v.getContainer().getFontMetrics(font);
 440:       int pos = Utilities.getTabbedTextOffset(txt, fm, (int) x,
 441:                                               (int) (x + len), te, p0, false);
 442:       return pos;
 443:     }
 444: 
 445:     /**
 446:      * Maps a visual position into a document location.
 447:      *
 448:      * @param v the glyph view
 449:      * @param x the X coordinate of the visual position
 450:      * @param y the Y coordinate of the visual position
 451:      * @param a the allocated region
 452:      * @param biasRet filled with the bias of the model location on method exit
 453:      *
 454:      * @return the model location that represents the specified view location
 455:      */
 456:     public int viewToModel(GlyphView v, float x, float y, Shape a,
 457:                            Bias[] biasRet)
 458:     {
 459:       Rectangle b = a.getBounds();
 460:       assert b.contains(x, y) : "The coordinates are expected to be within the "
 461:                                 + "view's bounds: x=" + x + ", y=" + y
 462:                                 + "a=" + a;
 463:       int pos = getBoundedPosition(v, v.getStartOffset(), b.x, x - b.x);
 464:       return pos;
 465:     }
 466:   }
 467: 
 468:   /**
 469:    * The GlyphPainer used for painting the glyphs.
 470:    */
 471:   GlyphPainter glyphPainter;
 472: 
 473:   /**
 474:    * The start offset within the document for this view.
 475:    */
 476:   int startOffset;
 477: 
 478:   /**
 479:    * The end offset within the document for this view.
 480:    */
 481:   int endOffset;
 482: 
 483:   /**
 484:    * Creates a new <code>GlyphView</code> for the given <code>Element</code>.
 485:    *
 486:    * @param element the element that is rendered by this GlyphView
 487:    */
 488:   public GlyphView(Element element)
 489:   {
 490:     super(element);
 491:     startOffset = element.getStartOffset();
 492:     endOffset = element.getEndOffset();
 493:   }
 494: 
 495:   /**
 496:    * Returns the <code>GlyphPainter</code> that is used by this
 497:    * <code>GlyphView</code>. If no <code>GlyphPainer</code> has been installed
 498:    * <code>null</code> is returned.
 499:    *
 500:    * @return the glyph painter that is used by this
 501:    *         glyph view or <code>null</code> if no glyph painter has been
 502:    *         installed
 503:    */
 504:   public GlyphPainter getGlyphPainter()
 505:   {
 506:     return glyphPainter;
 507:   }
 508: 
 509:   /**
 510:    * Sets the {@link GlyphPainter} to be used for this <code>GlyphView</code>.
 511:    *
 512:    * @param painter the glyph painter to be used for this glyph view
 513:    */
 514:   public void setGlyphPainter(GlyphPainter painter)
 515:   {
 516:     glyphPainter = painter;
 517:   }
 518: 
 519:   /**
 520:    * Checks if a <code>GlyphPainer</code> is installed. If this is not the
 521:    * case, a default painter is installed.
 522:    */
 523:   protected void checkPainter()
 524:   {
 525:     if (glyphPainter == null)
 526:       glyphPainter = new DefaultGlyphPainter();
 527:   }
 528: 
 529:   /**
 530:    * Renders the <code>Element</code> that is associated with this
 531:    * <code>View</code>.
 532:    *
 533:    * @param g the <code>Graphics</code> context to render to
 534:    * @param a the allocated region for the <code>Element</code>
 535:    */
 536:   public void paint(Graphics g, Shape a)
 537:   {
 538:     Element el = getElement();
 539:     checkPainter();
 540:     getGlyphPainter().paint(this, g, a, el.getStartOffset(),
 541:                             el.getEndOffset());
 542:   }
 543: 
 544: 
 545:   /**
 546:    * Returns the preferred span of the content managed by this
 547:    * <code>View</code> along the specified <code>axis</code>.
 548:    *
 549:    * @param axis the axis
 550:    *
 551:    * @return the preferred span of this <code>View</code>.
 552:    */
 553:   public float getPreferredSpan(int axis)
 554:   {
 555:     float span = 0;
 556:     checkPainter();
 557:     GlyphPainter painter = getGlyphPainter();
 558:     if (axis == X_AXIS)
 559:       {
 560:         Element el = getElement();
 561:         TabExpander tabEx = null;
 562:         View parent = getParent();
 563:         if (parent instanceof TabExpander)
 564:           tabEx = (TabExpander) parent;
 565:         span = painter.getSpan(this, getStartOffset(), getEndOffset(),
 566:                                tabEx, 0.F);
 567:       }
 568:     else
 569:       span = painter.getHeight(this); 
 570:     return span;
 571:   }
 572: 
 573:   /**
 574:    * Maps a position in the document into the coordinate space of the View.
 575:    * The output rectangle usually reflects the font height but has a width
 576:    * of zero.
 577:    *
 578:    * @param pos the position of the character in the model
 579:    * @param a the area that is occupied by the view
 580:    * @param b either {@link Position.Bias#Forward} or
 581:    *        {@link Position.Bias#Backward} depending on the preferred
 582:    *        direction bias. If <code>null</code> this defaults to
 583:    *        <code>Position.Bias.Forward</code>
 584:    *
 585:    * @return a rectangle that gives the location of the document position
 586:    *         inside the view coordinate space
 587:    *
 588:    * @throws BadLocationException if <code>pos</code> is invalid
 589:    * @throws IllegalArgumentException if b is not one of the above listed
 590:    *         valid values
 591:    */
 592:   public Shape modelToView(int pos, Shape a, Position.Bias b)
 593:     throws BadLocationException
 594:   {
 595:     GlyphPainter p = getGlyphPainter();
 596:     return p.modelToView(this, pos, b, a);
 597:   }
 598: 
 599:   /**
 600:    * Maps coordinates from the <code>View</code>'s space into a position
 601:    * in the document model.
 602:    *
 603:    * @param x the x coordinate in the view space
 604:    * @param y the y coordinate in the view space
 605:    * @param a the allocation of this <code>View</code>
 606:    * @param b the bias to use
 607:    *
 608:    * @return the position in the document that corresponds to the screen
 609:    *         coordinates <code>x, y</code>
 610:    */
 611:   public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
 612:   {
 613:     checkPainter();
 614:     GlyphPainter painter = getGlyphPainter();
 615:     return painter.viewToModel(this, x, y, a, b);
 616:   }
 617: 
 618:   /**
 619:    * Return the {@link TabExpander} to use.
 620:    *
 621:    * @return the {@link TabExpander} to use
 622:    */
 623:   public TabExpander getTabExpander()
 624:   {
 625:     TabExpander te = null;
 626:     View parent = getParent();
 627: 
 628:     if (parent instanceof TabExpander)
 629:       te = (TabExpander) parent;
 630:     
 631:     return te;
 632:   }
 633: 
 634:   /**
 635:    * Returns the preferred span of this view for tab expansion.
 636:    *
 637:    * @param x the location of the view
 638:    * @param te the tab expander to use
 639:    *
 640:    * @return the preferred span of this view for tab expansion
 641:    */
 642:   public float getTabbedSpan(float x, TabExpander te)
 643:   {
 644:     Element el = getElement();
 645:     return getGlyphPainter().getSpan(this, el.getStartOffset(),
 646:                                      el.getEndOffset(), te, x);
 647:   }
 648: 
 649:   /**
 650:    * Returns the span of a portion of the view. This is used in TAB expansion
 651:    * for fragments that don't contain TABs.
 652:    *
 653:    * @param p0 the start index
 654:    * @param p1 the end index
 655:    *
 656:    * @return the span of the specified portion of the view
 657:    */
 658:   public float getPartialSpan(int p0, int p1)
 659:   {
 660:     Element el = getElement();
 661:     Document doc = el.getDocument();
 662:     Segment seg = new Segment();
 663:     try
 664:       {
 665:         doc.getText(p0, p1 - p0, seg);
 666:       }
 667:     catch (BadLocationException ex)
 668:       {
 669:     AssertionError ae;
 670:         ae = new AssertionError("BadLocationException must not be thrown "
 671:                 + "here");
 672:     ae.initCause(ex);
 673:     throw ae;
 674:       }
 675:     FontMetrics fm = null; // Fetch font metrics somewhere.
 676:     return Utilities.getTabbedTextWidth(seg, fm, 0, null, p0);
 677:   }
 678: 
 679:   /**
 680:    * Returns the start offset in the document model of the portion
 681:    * of text that this view is responsible for.
 682:    *
 683:    * @return the start offset in the document model of the portion
 684:    *         of text that this view is responsible for
 685:    */
 686:   public int getStartOffset()
 687:   {
 688:     return startOffset;
 689:   }
 690: 
 691:   /**
 692:    * Returns the end offset in the document model of the portion
 693:    * of text that this view is responsible for.
 694:    *
 695:    * @return the end offset in the document model of the portion
 696:    *         of text that this view is responsible for
 697:    */
 698:   public int getEndOffset()
 699:   {
 700:     return endOffset;
 701:   }
 702: 
 703:   /**
 704:    * Returns the text segment that this view is responsible for.
 705:    *
 706:    * @param p0 the start index in the document model
 707:    * @param p1 the end index in the document model
 708:    *
 709:    * @return the text segment that this view is responsible for
 710:    */
 711:   public Segment getText(int p0, int p1)
 712:   {
 713:     Segment txt = new Segment();
 714:     try
 715:       {
 716:         getDocument().getText(p0, p1 - p0, txt);
 717:       }
 718:     catch (BadLocationException ex)
 719:       {
 720:     AssertionError ae;
 721:         ae = new AssertionError("BadLocationException should not be "
 722:                 + "thrown here. p0 = " + p0 + ", p1 = " + p1);
 723:     ae.initCause(ex);
 724:     throw ae;
 725:       }
 726: 
 727:     return txt;
 728:   }
 729: 
 730:   /**
 731:    * Returns the font for the text run for which this <code>GlyphView</code>
 732:    * is responsible.
 733:    *
 734:    * @return the font for the text run for which this <code>GlyphView</code>
 735:    *         is responsible
 736:    */
 737:   public Font getFont()
 738:   {
 739:     Element el = getElement();
 740:     AttributeSet atts = el.getAttributes();
 741:     String family = StyleConstants.getFontFamily(atts);
 742:     int size = StyleConstants.getFontSize(atts);
 743:     int style = Font.PLAIN;
 744:     if (StyleConstants.isBold(atts))
 745:         style |= Font.BOLD;
 746:     if (StyleConstants.isItalic(atts))
 747:       style |= Font.ITALIC;
 748:     Font font = new Font(family, style, size);
 749:     return font;
 750:   }
 751: 
 752:   /**
 753:    * Returns the foreground color which should be used to paint the text.
 754:    * This is fetched from the associated element's text attributes using
 755:    * {@link StyleConstants#getForeground}.
 756:    *
 757:    * @return the foreground color which should be used to paint the text
 758:    */
 759:   public Color getForeground()
 760:   {
 761:     Element el = getElement();
 762:     AttributeSet atts = el.getAttributes();
 763:     return StyleConstants.getForeground(atts);
 764:   }
 765: 
 766:   /**
 767:    * Returns the background color which should be used to paint the text.
 768:    * This is fetched from the associated element's text attributes using
 769:    * {@link StyleConstants#getBackground}.
 770:    *
 771:    * @return the background color which should be used to paint the text
 772:    */
 773:   public Color getBackground()
 774:   {
 775:     Element el = getElement();
 776:     AttributeSet atts = el.getAttributes();
 777:     return StyleConstants.getBackground(atts);
 778:   }
 779: 
 780:   /**
 781:    * Determines whether the text should be rendered strike-through or not. This
 782:    * is determined using the method
 783:    * {@link StyleConstants#isStrikeThrough(AttributeSet)} on the element of
 784:    * this view.
 785:    *
 786:    * @return whether the text should be rendered strike-through or not
 787:    */
 788:   public boolean isStikeThrough()
 789:   {
 790:     Element el = getElement();
 791:     AttributeSet atts = el.getAttributes();
 792:     return StyleConstants.isStrikeThrough(atts);
 793:   }
 794: 
 795:   /**
 796:    * Determines whether the text should be rendered as subscript or not. This
 797:    * is determined using the method
 798:    * {@link StyleConstants#isSubscript(AttributeSet)} on the element of
 799:    * this view.
 800:    *
 801:    * @return whether the text should be rendered as subscript or not
 802:    */
 803:   public boolean isSubscript()
 804:   {
 805:     Element el = getElement();
 806:     AttributeSet atts = el.getAttributes();
 807:     return StyleConstants.isSubscript(atts);
 808:   }
 809: 
 810:   /**
 811:    * Determines whether the text should be rendered as superscript or not. This
 812:    * is determined using the method
 813:    * {@link StyleConstants#isSuperscript(AttributeSet)} on the element of
 814:    * this view.
 815:    *
 816:    * @return whether the text should be rendered as superscript or not
 817:    */
 818:   public boolean isSuperscript()
 819:   {
 820:     Element el = getElement();
 821:     AttributeSet atts = el.getAttributes();
 822:     return StyleConstants.isSuperscript(atts);
 823:   }
 824: 
 825:   /**
 826:    * Determines whether the text should be rendered as underlined or not. This
 827:    * is determined using the method
 828:    * {@link StyleConstants#isUnderline(AttributeSet)} on the element of
 829:    * this view.
 830:    *
 831:    * @return whether the text should be rendered as underlined or not
 832:    */
 833:   public boolean isUnderline()
 834:   {
 835:     Element el = getElement();
 836:     AttributeSet atts = el.getAttributes();
 837:     return StyleConstants.isUnderline(atts);
 838:   }
 839: 
 840:   /**
 841:    * Creates and returns a shallow clone of this GlyphView. This is used by
 842:    * the {@link #createFragment} and {@link #breakView} methods.
 843:    *
 844:    * @return a shallow clone of this GlyphView
 845:    */
 846:   protected final Object clone()
 847:   {
 848:     try
 849:       {
 850:         return super.clone();
 851:       }
 852:     catch (CloneNotSupportedException ex)
 853:       {
 854:         AssertionError err = new AssertionError("CloneNotSupportedException "
 855:                                                 + "must not be thrown here");
 856:         err.initCause(ex);
 857:         throw err;
 858:       }
 859:   }
 860: 
 861:   /**
 862:    * Tries to break the view near the specified view span <code>len</code>.
 863:    * The glyph view can only be broken in the X direction. For Y direction it
 864:    * returns itself.
 865:    *
 866:    * @param axis the axis for breaking, may be {@link View#X_AXIS} or
 867:    *        {@link View#Y_AXIS}
 868:    * @param p0 the model location where the fragment should start
 869:    * @param pos the view position along the axis where the fragment starts
 870:    * @param len the desired length of the fragment view
 871:    *
 872:    * @return the fragment view, or <code>this</code> if breaking was not
 873:    *         possible
 874:    */
 875:   public View breakView(int axis, int p0, float pos, float len)
 876:   {
 877:     if (axis == Y_AXIS)
 878:       return this;
 879: 
 880:     checkPainter();
 881:     GlyphPainter painter = getGlyphPainter();
 882:     int breakLocation = painter.getBoundedPosition(this, p0, pos, len);
 883:     // Try to find a suitable line break.
 884:     BreakIterator lineBreaker = BreakIterator.getLineInstance();
 885:     Segment txt = new Segment();
 886:     try
 887:       {
 888:         getDocument().getText(getStartOffset(), getEndOffset(), txt);
 889:       }
 890:     catch (BadLocationException ex)
 891:       {
 892:         AssertionError err = new AssertionError("BadLocationException must not "
 893:                                                 + "be thrown here.");
 894:         err.initCause(ex);
 895:         throw err;
 896:       }
 897:     lineBreaker.setText(txt);
 898:     int goodBreakLocation = lineBreaker.previous();
 899:     if (goodBreakLocation != BreakIterator.DONE)
 900:       breakLocation = goodBreakLocation;
 901: 
 902:     View brokenView = createFragment(p0, breakLocation);
 903:     return brokenView;
 904:   }
 905: 
 906:   /**
 907:    * Determines how well the specified view location is suitable for inserting
 908:    * a line break. If <code>axis</code> is <code>View.Y_AXIS</code>, then
 909:    * this method forwards to the superclass, if <code>axis</code> is
 910:    * <code>View.X_AXIS</code> then this method returns
 911:    * {@link View#ExcellentBreakWeight} if there is a suitable break location
 912:    * (usually whitespace) within the specified view span, or
 913:    * {@link View#GoodBreakWeight} if not.
 914:    *
 915:    * @param axis the axis along which the break weight is requested
 916:    * @param pos the starting view location
 917:    * @param len the length of the span at which the view should be broken
 918:    *
 919:    * @return the break weight
 920:    */
 921:   public int getBreakWeight(int axis, float pos, float len)
 922:   {
 923:     int weight;
 924:     if (axis == Y_AXIS)
 925:       weight = super.getBreakWeight(axis, pos, len);
 926:     else
 927:       {
 928:         // Determine the model locations at pos and pos + len.
 929:         int spanX = (int) getPreferredSpan(X_AXIS);
 930:         int spanY = (int) getPreferredSpan(Y_AXIS);
 931:         Rectangle dummyAlloc = new Rectangle(0, 0, spanX, spanY);
 932:         Position.Bias[] biasRet = new Position.Bias[1];
 933:         int offset1 = viewToModel(pos, spanY / 2, dummyAlloc, biasRet);
 934:         int offset2 = viewToModel(pos, spanY / 2, dummyAlloc, biasRet);
 935:         Segment txt = getText(offset1, offset2);
 936:         BreakIterator lineBreaker = BreakIterator.getLineInstance();
 937:         lineBreaker.setText(txt);
 938:         int breakLoc = lineBreaker.previous();
 939:         if (breakLoc == offset1)
 940:           weight = View.BadBreakWeight;
 941:         else if(breakLoc ==  BreakIterator.DONE)
 942:           weight = View.GoodBreakWeight;
 943:         else
 944:           weight = View.ExcellentBreakWeight;
 945:       }
 946:     return weight;
 947:   }
 948: 
 949:   /**
 950:    * Receives notification that some text attributes have changed within the
 951:    * text fragment that this view is responsible for. This calls
 952:    * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
 953:    * both width and height.
 954:    *
 955:    * @param e the document event describing the change; not used here
 956:    * @param a the view allocation on screen; not used here
 957:    * @param vf the view factory; not used here
 958:    */
 959:   public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf)
 960:   {
 961:     getParent().preferenceChanged(this, true, true);
 962:   }
 963: 
 964:   /**
 965:    * Receives notification that some text has been inserted within the
 966:    * text fragment that this view is responsible for. This calls
 967:    * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
 968:    * width.
 969:    *
 970:    * @param e the document event describing the change; not used here
 971:    * @param a the view allocation on screen; not used here
 972:    * @param vf the view factory; not used here
 973:    */
 974:   public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
 975:   {
 976:     getParent().preferenceChanged(this, true, false);
 977:   }
 978: 
 979:   /**
 980:    * Receives notification that some text has been removed within the
 981:    * text fragment that this view is responsible for. This calls
 982:    * {@link View#preferenceChanged(View, boolean, boolean)} on the parent for
 983:    * width.
 984:    *
 985:    * @param e the document event describing the change; not used here
 986:    * @param a the view allocation on screen; not used here
 987:    * @param vf the view factory; not used here
 988:    */
 989:   public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
 990:   {
 991:     getParent().preferenceChanged(this, true, false);
 992:   }
 993: 
 994:   /**
 995:    * Creates a fragment view of this view that starts at <code>p0</code> and
 996:    * ends at <code>p1</code>.
 997:    *
 998:    * @param p0 the start location for the fragment view
 999:    * @param p1 the end location for the fragment view
1000:    *
1001:    * @return the fragment view
1002:    */
1003:   public View createFragment(int p0, int p1)
1004:   {
1005:     GlyphView fragment = (GlyphView) clone();
1006:     fragment.startOffset = p0;
1007:     fragment.endOffset = p1;
1008:     return fragment;
1009:   }
1010: 
1011:   /**
1012:    * Returns the alignment of this view along the specified axis. For the Y
1013:    * axis this is <code>(height - descent) / height</code> for the used font,
1014:    * so that it is aligned along the baseline.
1015:    * For the X axis the superclass is called.
1016:    */
1017:   public float getAlignment(int axis)
1018:   {
1019:     float align;
1020:     if (axis == Y_AXIS)
1021:       {
1022:         checkPainter();
1023:         GlyphPainter painter = getGlyphPainter();
1024:         float height = painter.getHeight(this);
1025:         float descent = painter.getDescent(this);
1026:         align = (height - descent) / height; 
1027:       }
1028:     else
1029:       align = super.getAlignment(axis);
1030: 
1031:     return align;
1032:   }
1033: 
1034:   /**
1035:    * Returns the model location that should be used to place a caret when
1036:    * moving the caret through the document.
1037:    *
1038:    * @param pos the current model location
1039:    * @param bias the bias for <code>p</code>
1040:    * @param a the allocated region for the glyph view
1041:    * @param direction the direction from the current position; Must be one of
1042:    *        {@link SwingConstants#EAST}, {@link SwingConstants#WEST},
1043:    *        {@link SwingConstants#NORTH} or {@link SwingConstants#SOUTH}
1044:    * @param biasRet filled with the bias of the resulting location when method
1045:    *        returns
1046:    *
1047:    * @return the location within the document that should be used to place the
1048:    *         caret when moving the caret around the document
1049:    *
1050:    * @throws BadLocationException if <code>pos</code> is an invalid model
1051:    *         location
1052:    * @throws IllegalArgumentException if <code>d</code> is invalid
1053:    */
1054:   public int getNextVisualPositionFrom(int pos, Position.Bias bias, Shape a,
1055:                                        int direction, Position.Bias[] biasRet)
1056:     throws BadLocationException
1057:   {
1058:     checkPainter();
1059:     GlyphPainter painter = getGlyphPainter();
1060:     return painter.getNextVisualPositionFrom(this, pos, bias, a, direction,
1061:                                              biasRet);
1062:   }
1063: }