Source for gnu.java.awt.font.GNUGlyphVector

   1: /* GNUGlyphVector.java -- The GNU implementation of GlyphVector.
   2:    Copyright (C) 2006 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: package gnu.java.awt.font;
  39: 
  40: import gnu.java.awt.java2d.ShapeWrapper;
  41: 
  42: import java.awt.Font;
  43: import java.awt.font.FontRenderContext;
  44: import java.awt.font.GlyphMetrics;
  45: import java.awt.font.GlyphJustificationInfo;
  46: import java.awt.font.GlyphVector;
  47: 
  48: import java.awt.Shape;
  49: import java.awt.geom.AffineTransform;
  50: import java.awt.geom.GeneralPath;
  51: import java.awt.geom.Point2D;
  52: import java.awt.geom.Rectangle2D;
  53: 
  54: 
  55: /**
  56:  * The GNU implementation of the abstract GlyphVector class, which
  57:  * uses the services provided by a FontDelegate for its functionality.
  58:  *
  59:  * @author Sascha Brawer (brawer@dandelis.ch)
  60:  */
  61: public class GNUGlyphVector
  62:   extends GlyphVector
  63: {
  64:   private FontDelegate fontDelegate;
  65:   private Font font;
  66:   private FontRenderContext renderContext;
  67:   private int[] glyphs;
  68:   private float fontSize;
  69:   private AffineTransform transform;
  70:   private boolean valid;
  71: 
  72: 
  73:   /**
  74:    * The position of each glyph. The horizontal position of the
  75:    * <code>i</code>-th glyph is at <code>pos[i * 2]</code>, its
  76:    * vertical position at <code>pos[i * 2 + 1]</code>. The total
  77:    * advance width of the entire vector is stored at
  78:    * <code>pos[numGlyphs]</code>, the total advance height at
  79:    * <code>pos[numGlyphs + 1]</code>.
  80:    */
  81:   private float[] pos;
  82: 
  83: 
  84:   private AffineTransform[] transforms;
  85:   private int layoutFlags;
  86: 
  87:   /**
  88:    * The cached non-transformed outline of this glyph vector.
  89:    */
  90:   private Shape cleanOutline;
  91: 
  92:   /**
  93:    * Constructs a new GNUGlyphVector.
  94:    *
  95:    * @param fontDelegate the FontDelegate that creates this vector.
  96:    *
  97:    * @param font the Font that this GlyphVector will return for {@link
  98:    * #getFont()}. That object is also used to determine the point
  99:    * size, which affects the affine transformation used by the font
 100:    * scaler.
 101:    *
 102:    * @param renderContext an object with parameters for font
 103:    * rendering, such as whether anti-aliasing is enabled.
 104:    *
 105:    * @param glyphs the glyphs in this vector.
 106:    */
 107:   public GNUGlyphVector(FontDelegate fontDelegate,
 108:                         Font font,
 109:                         FontRenderContext renderContext,
 110:                         int[] glyphs)
 111:   {
 112:     this.fontDelegate = fontDelegate;
 113:     this.font = font;
 114:     this.renderContext = renderContext;
 115:     this.glyphs = glyphs;
 116: 
 117:     fontSize = font.getSize2D();
 118:     transform = font.getTransform(); // returns a modifiable copy
 119:     //transform.concatenate(renderContext.getTransform());
 120:   }
 121: 
 122: 
 123: 
 124:   /**
 125:    * Returns the font of the glyphs in this GlyphVector.
 126:    */
 127:   public Font getFont()
 128:   {
 129:     return font;
 130:   }
 131: 
 132: 
 133:   /**
 134:    * Returns the FontRenderContext that is used to calculate the
 135:    * extent and position of the glyphs.
 136:    */
 137:   public FontRenderContext getFontRenderContext()
 138:   {
 139:     return renderContext;
 140:   }
 141: 
 142: 
 143:   /**
 144:    * Moves each glyph in the vector to its default position.
 145:    */
 146:   public void performDefaultLayout()
 147:   {
 148:     float x, y, advanceWidth, advanceHeight;
 149:     int i, p;
 150:     AffineTransform tx;
 151:     Point2D.Float advance = new Point2D.Float();
 152: 
 153:     pos = new float[(glyphs.length + 1) * 2];
 154:     x = y = 0.0f;
 155:     p = 0;
 156:     for (i = p = 0; i < glyphs.length; i++)
 157:     {
 158:       p += 2;
 159: 
 160:       if ((transforms == null) || (tx = transforms[i]) == null)
 161:         tx = this.transform;
 162:       else
 163:       {
 164:         tx = new AffineTransform(tx);
 165:         tx.concatenate(this.transform);
 166:       }
 167: 
 168:       fontDelegate.getAdvance(glyphs[i], fontSize, tx,
 169:                               renderContext.isAntiAliased(),
 170:                               renderContext.usesFractionalMetrics(),
 171:                               /* horizontal */ true,
 172:                               advance);
 173:       // FIXME: We shouldn't round here, but instead hint the metrics
 174:       // correctly.
 175:       pos[p] = x += Math.round(advance.x);
 176:       pos[p + 1] = y += advance.y;
 177:     }
 178:     valid = true;
 179:   }
 180: 
 181: 
 182:   /**
 183:    * Determines the number of glyphs in this GlyphVector.
 184:    */
 185:   public int getNumGlyphs()
 186:   {
 187:     return glyphs.length;
 188:   }
 189: 
 190: 
 191:   /**
 192:    * Determines the glyph number by index in this vector.
 193:    * Glyph numbers are specific to each font, so two fonts
 194:    * will likely assign different numbers to the same glyph.
 195:    *
 196:    * @param glyphIndex the index of the glyph whose glyph number is to
 197:    * be retrieved.
 198:    *
 199:    * @throws IndexOutOfBoundsException if <code>glyphIndex</code>
 200:    * is not in the range <code[0 .. getNumGlyphs() - 1]</code>.
 201:    */
 202:   public int getGlyphCode(int glyphIndex)
 203:   {
 204:     /* The exception is thrown automatically if the index is out
 205:      * of the valid bounds.
 206:      */
 207:     return glyphs[glyphIndex];
 208:   }
 209: 
 210: 
 211:   /**
 212:    * Returns a slice of this GlyphVector.
 213:    *
 214:    * @param firstGlyphIndex the index of the first glyph in the
 215:    * returned slice.
 216:    *
 217:    * @param numEntries the size of the returned slice.
 218:    *
 219:    * @param outCodes a pre-allocated array for storing the slice,
 220:    * or <code>null</code> to cause allocation of a new array.
 221:    *
 222:    * @return a slice of this GlyphVector. If <code>outCodes</code>
 223:    * is <code>null</code>, the slice will be stored into a freshly
 224:    * allocated array; otherwise, the result will be stored into
 225:    * <code>outCodes</code>.
 226:    */
 227:   public int[] getGlyphCodes(int firstGlyphIndex,
 228:                              int numEntries,
 229:                              int[] outCodes)
 230:   {
 231:     if (numEntries < 0)
 232:       throw new IllegalArgumentException();
 233:     if (outCodes == null)
 234:       outCodes = new int[numEntries];
 235:     System.arraycopy(glyphs, firstGlyphIndex, outCodes, 0, numEntries);
 236:     return outCodes;
 237:   }
 238: 
 239: 
 240:   public Rectangle2D getLogicalBounds()
 241:   {
 242:     float ascent, descent;
 243: 
 244:     validate();
 245: 
 246:     return new Rectangle2D.Float(0, 0,
 247:                                  pos[pos.length - 2],
 248:                                  getAscent() - getDescent());
 249:   }
 250: 
 251: 
 252:   public Rectangle2D getVisualBounds()
 253:   {
 254:     validate();
 255: 
 256:     // FIXME: Not yet implemented.
 257:     return getLogicalBounds();
 258:   }
 259: 
 260: 
 261:   /**
 262:    * Returns the shape of this GlyphVector.
 263:    */
 264:   public Shape getOutline()
 265:   {
 266:     return getOutline(0.0f, 0.0f);
 267:   }
 268: 
 269: 
 270:   /**
 271:    * Returns the shape of this GlyphVector, translated to the
 272:    * specified position.
 273:    *
 274:    * @param x the horizontal position for rendering this vector.
 275:    * @param y the vertical position for rendering this vector.
 276:    */
 277:   public Shape getOutline(float x, float y)
 278:   {
 279:     validate();
 280: 
 281:     Shape outline;
 282:     if (cleanOutline == null)
 283:       {
 284:         GeneralPath path = new GeneralPath();
 285:         int len = glyphs.length;
 286:         for (int i = 0; i < len; i++)
 287:           {
 288:             GeneralPath p = new GeneralPath(getGlyphOutline(i));
 289:             path.append(p, false);
 290:           }
 291:         // Protect the cached instance from beeing modified by application
 292:         // code.
 293:         cleanOutline = new ShapeWrapper(path);
 294:         outline = cleanOutline;
 295:       }
 296:     else
 297:       {
 298:         outline = cleanOutline;
 299:       }
 300:     if (x != 0 || y != 0)
 301:       {
 302:         GeneralPath path = new GeneralPath(outline);
 303:         AffineTransform t = new AffineTransform();
 304:         t.translate(x, y);
 305:         path.transform(t);
 306:         outline = path;
 307:       }
 308:     return outline;
 309:   }
 310: 
 311:   public Shape getOutline(float x, float y, int type)
 312:   {
 313:     validate();
 314: 
 315:     GeneralPath outline = new GeneralPath();
 316:     int len = glyphs.length;
 317:     for (int i = 0; i < len; i++)
 318:       {
 319:         GeneralPath p = new GeneralPath(getGlyphOutline(i, type));
 320:         outline.append(p, false);
 321:       }
 322:     AffineTransform t = new AffineTransform();
 323:     t.translate(x, y);
 324:     outline.transform(t);
 325:     return outline;
 326:   }
 327: 
 328:   /**
 329:    * Determines the shape of the specified glyph.
 330:    *
 331:    * @throws IndexOutOfBoundsException if <code>glyphIndex</code> is
 332:    * not in the range <code[0 .. getNumGlyphs()]</code>.
 333:    */
 334:   public Shape getGlyphOutline(int glyphIndex)
 335:   {
 336:     AffineTransform tx, glyphTx;
 337:     GeneralPath path;
 338: 
 339:     validate();
 340: 
 341:     if ((transforms != null)
 342:         && ((glyphTx = transforms[glyphIndex]) != null))
 343:     {
 344:       tx =  new AffineTransform(transform);
 345:       tx.concatenate(glyphTx);
 346:     }
 347:     else
 348:       tx = transform;
 349: 
 350:     path = fontDelegate.getGlyphOutline(glyphs[glyphIndex], fontSize, tx,
 351:                                         renderContext.isAntiAliased(),
 352:                                         renderContext.usesFractionalMetrics(),
 353:                                         FontDelegate.FLAG_FITTED);
 354: 
 355:     tx = new AffineTransform();
 356:     tx.translate(pos[glyphIndex * 2], pos[glyphIndex * 2 + 1]);
 357:     path.transform(tx);
 358:     return path;
 359:   }
 360: 
 361:   public Shape getGlyphOutline(int glyphIndex, int type)
 362:   {
 363:     AffineTransform tx, glyphTx;
 364:     GeneralPath path;
 365: 
 366:     validate();
 367: 
 368:     if ((transforms != null)
 369:         && ((glyphTx = transforms[glyphIndex]) != null))
 370:     {
 371:       tx =  new AffineTransform(transform);
 372:       tx.concatenate(glyphTx);
 373:     }
 374:     else
 375:       tx = transform;
 376: 
 377:     path = fontDelegate.getGlyphOutline(glyphs[glyphIndex], fontSize, tx,
 378:                                         renderContext.isAntiAliased(),
 379:                                         renderContext.usesFractionalMetrics(),
 380:                                         type);
 381: 
 382:     tx = new AffineTransform();
 383:     tx.translate(pos[glyphIndex * 2], pos[glyphIndex * 2 + 1]);
 384:     path.transform(tx);
 385:     return path;
 386:   }
 387: 
 388:   /**
 389:    * Determines the position of the specified glyph, or the
 390:    * total advance width and height of the vector.
 391:    *
 392:    * @param glyphIndex the index of the glyph in question.
 393:    * If this value equals <code>getNumGlyphs()</code>, the
 394:    * position <i>after</i> the last glyph will be returned,
 395:    * which is the total advance width and height of the vector.
 396:    *
 397:    * @throws IndexOutOfBoundsException if <code>glyphIndex</code> is
 398:    * not in the range <code[0 .. getNumGlyphs()]</code>.
 399:    */
 400:   public Point2D getGlyphPosition(int glyphIndex)
 401:   {
 402:     validate();
 403:     return new Point2D.Float(pos[glyphIndex * 2],
 404:                              pos[glyphIndex * 2 + 1]);
 405:   }
 406: 
 407: 
 408:   /**
 409:    * Moves the specified glyph to a new position, or changes the
 410:    * advance width and height of the entire glyph vector.
 411:    *
 412:    * <p>Note that the position of an individual glyph may also
 413:    * affected by its affine transformation.
 414:    *
 415:    * @param glyphIndex the index of the moved glyph. If
 416:    * <code>glyphIndex</code> equals the total number of glyphs in this
 417:    * vector, the advance width and height of the vector is changed.
 418:    *
 419:    * @param position the new position of the glyph.
 420:    *
 421:    * @throws IndexOutOfBoundsException if <code>glyphIndex</code> is
 422:    * not in the range <code[0 .. getNumGlyphs()]</code>.
 423:    */
 424:   public void setGlyphPosition(int glyphIndex, Point2D position)
 425:   {
 426:     validate();
 427:     pos[glyphIndex * 2] = (float) position.getX();
 428:     pos[glyphIndex * 2 + 1] = (float) position.getY();
 429:   }
 430: 
 431: 
 432:   /**
 433:    * Returns the affine transformation that is applied to the
 434:    * glyph at the specified index.
 435:    *
 436:    * @param glyphIndex the index of the glyph whose transformation
 437:    * is to be retrieved.
 438:    *
 439:    * @return an affine transformation, or <code>null</code>
 440:    * for the identity transformation.
 441:    *
 442:    * @throws IndexOutOfBoundsException if <code>glyphIndex</code> is
 443:    * not in the range <code[0 .. getNumGlyphs() - 1]</code>.
 444:    */
 445:   public AffineTransform getGlyphTransform(int glyphIndex)
 446:   {
 447:     if (transforms == null)
 448:       return null;
 449:     else
 450:       return transforms[glyphIndex];
 451:   }
 452: 
 453: 
 454:   /**
 455:    * Applies an affine transformation to the glyph at the specified
 456:    * index.
 457:    *
 458:    * @param glyphIndex the index of the glyph to which the
 459:    * transformation is applied.
 460:    *
 461:    * @param transform the affine transformation for the glyph, or
 462:    * <code>null</code> for an identity transformation.
 463:    */
 464:   public void setGlyphTransform(int glyphIndex,
 465:                                 AffineTransform transform)
 466:   {
 467:     if (transforms == null)
 468:       transforms = new AffineTransform[glyphs.length];
 469:     transforms[glyphIndex] = transform;
 470: 
 471:     /* If the GlyphVector has only a transform for a single glyph, and
 472:      * the caller clears its transform, the FLAG_HAS_TRANSFORMS bit
 473:      * should be cleared in layoutFlags.  However, this would require
 474:      * that we keep track of the number of transformed glyphs, or that
 475:      * we count them when a transform is cleared. This would
 476:      * complicate the code quite a bit. Note that the only drawback of
 477:      * wrongly setting FLAG_HAS_TRANSFORMS is that a slower code path
 478:      * might be taken for rendering the vector. Right now, we never
 479:      * really look at the flag, so it does not make any difference.
 480:      */
 481:     if (transform != null)
 482:       layoutFlags |= FLAG_HAS_TRANSFORMS;
 483:     valid = false;
 484:   }
 485: 
 486: 
 487:   /**
 488:    * Returns flags that can be used for optimizing the rendering
 489:    * of this GlyphVector.
 490:    *
 491:    * @return a bit mask with the applicable flags set.
 492:    *
 493:    * @since 1.4
 494:    *
 495:    * @see GlyphVector#FLAG_HAS_POSITION_ADJUSTMENTS
 496:    * @see GlyphVector#FLAG_HAS_TRANSFORMS
 497:    * @see GlyphVector#FLAG_RUN_RTL
 498:    * @see GlyphVector#FLAG_COMPLEX_GLYPHS
 499:    * @see GlyphVector#FLAG_MASK
 500:    */
 501:   public int getLayoutFlags()
 502:   {
 503:     return layoutFlags;
 504:   }
 505: 
 506: 
 507:   /**
 508:    * Returns the positions of a range of glyphs in this vector.
 509:    *
 510:    * @param firstGlyphIndex the index of the first glyph whose
 511:    * position is retrieved.
 512:    *
 513:    * @param numGlyphs the number of glyphs whose positions
 514:    * are retrieved.
 515:    *
 516:    * @param outPositions an array for storing the results
 517:    * (the length must be at least twice <code>numGlyphs</code>),
 518:    * or <code>null</code> for freshly allocating an array.
 519:    *
 520:    * @return an array with the glyph positions. The horizontal
 521:    * position of the <code>i</code>-th glyph is at index <code>2 *
 522:    * i</code>, the vertical position at index <code>2 * i + 1</code>.
 523:    *
 524:    * @throws IllegalArgumentException if <code>numGlyphs</code>
 525:    * is less than zero.
 526:    *
 527:    * @throws IndexOutOfBoundsException if either
 528:    * <code>firstGlyphIndex</code> or <code>(firstGlyphIndex +
 529:    * numGlyphs)</code> is not in the range <code>[0 .. getNumGlyphs() -
 530:    * 1]</code>.
 531:    */
 532:   public float[] getGlyphPositions(int firstGlyphIndex,
 533:                                    int numGlyphs,
 534:                                    float[] outPositions)
 535:   {
 536:     if (numGlyphs < 0)
 537:       throw new IllegalArgumentException();
 538: 
 539:     validate();
 540:     if (outPositions == null)
 541:       outPositions = new float[numGlyphs * 2];
 542: 
 543:     System.arraycopy(/*src */ pos, /* srcStart */ firstGlyphIndex * 2,
 544:                      /* dest */ outPositions, /* destStart */ 0,
 545:                      /* length */ numGlyphs * 2);
 546:     return outPositions;
 547:   }
 548: 
 549: 
 550:   private float getAscent()
 551:   {
 552:     return fontDelegate.getAscent(fontSize, transform,
 553:                                   renderContext.isAntiAliased(),
 554:                                   renderContext.usesFractionalMetrics(),
 555:                                   /* horizontal */ true);
 556:   }
 557: 
 558: 
 559:   private float getDescent()
 560:   {
 561:     return fontDelegate.getDescent(fontSize, transform,
 562:                                    renderContext.isAntiAliased(),
 563:                                    renderContext.usesFractionalMetrics(),
 564:                                    /* horizontal */ true);
 565:   }
 566: 
 567: 
 568:   public Shape getGlyphLogicalBounds(int glyphIndex)
 569:   {
 570:     float x, y, ascent;
 571: 
 572:     validate();
 573:     ascent = getAscent();
 574:     x = pos[glyphIndex * 2];
 575:     y = pos[glyphIndex * 2 + 1];
 576: 
 577:     return new Rectangle2D.Float(x, y - ascent,
 578:                                  pos[(glyphIndex + 1) * 2] - x,
 579:                                  ascent - getDescent());
 580:   }
 581: 
 582: 
 583:   public Shape getGlyphVisualBounds(int glyphIndex)
 584:   {
 585:     return getGlyphOutline(glyphIndex).getBounds2D();
 586:   }
 587: 
 588: 
 589:   /**
 590:    * Determines the metrics of the glyph at the specified index.
 591:    *
 592:    * @param glyphIndex the index of the glyph whose metrics is to be
 593:    * retrieved.
 594:    *
 595:    * @throws IndexOutOfBoundsException if <code>glyphIndex</code> is
 596:    * not in the range <code[0 .. getNumGlyphs() - 1]</code>.
 597:    */
 598:   public GlyphMetrics getGlyphMetrics(int glyphIndex)
 599:   {
 600:     // FIXME: Not yet implemented.
 601:     throw new UnsupportedOperationException();
 602:   }
 603: 
 604: 
 605:   /**
 606:    * Determines the justification information for the glyph at the
 607:    * specified index.
 608:    *
 609:    * @param glyphIndex the index of the glyph whose justification
 610:    * information is to be retrieved.
 611:    *
 612:    * @throws IndexOutOfBoundsException if <code>glyphIndex</code> is
 613:    * not in the range <code[0 .. getNumGlyphs() - 1]</code>.
 614:    */
 615:   public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex)
 616:   {
 617:     // FIXME: Not yet implemented.
 618:     throw new UnsupportedOperationException();
 619:   }
 620: 
 621: 
 622:   /**
 623:    * Determines whether another GlyphVector is for the same font and
 624:    * rendering context, uses the same glyphs and positions them to the
 625:    * same location.
 626:    *
 627:    * @param other the GlyphVector to compare with.
 628:    *
 629:    * @return <code>true</code> if the two vectors are equal,
 630:    * <code>false</code> otherwise.
 631:    */
 632:   public boolean equals(GlyphVector other)
 633:   {
 634:     GNUGlyphVector o;
 635:     if (!(other instanceof GNUGlyphVector))
 636:       return false;
 637: 
 638:     o = (GNUGlyphVector) other;
 639:     if ((this.font != o.font)
 640:         || (this.fontDelegate != o.fontDelegate)
 641:         || (this.renderContext != o.renderContext)
 642:         || (this.glyphs.length != o.glyphs.length))
 643:       return false;
 644: 
 645:     for (int i = 0; i < glyphs.length; i++)
 646:       if (this.glyphs[i] != o.glyphs[i])
 647:         return false;
 648: 
 649:     validate();
 650:     o.validate();
 651:     for (int i = 0; i < pos.length; i++)
 652:       if (this.pos[i] != o.pos[i])
 653:         return false;
 654: 
 655:     return true;
 656:   }
 657: 
 658:   private void validate()
 659:   {
 660:     if (!valid)
 661:       performDefaultLayout();
 662:   }
 663: }