Source for gnu.java.awt.peer.gtk.FreetypeGlyphVector

   1: /* FreetypeGlyphVector.java
   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.peer.gtk;
  39: 
  40: import java.awt.Font;
  41: import java.awt.Shape;
  42: import java.awt.font.FontRenderContext;
  43: import java.awt.font.GlyphJustificationInfo;
  44: import java.awt.font.GlyphMetrics;
  45: import java.awt.font.GlyphVector;
  46: import java.awt.font.TextAttribute;
  47: import java.awt.font.TransformAttribute;
  48: import java.awt.geom.AffineTransform;
  49: import java.awt.geom.GeneralPath;
  50: import java.awt.geom.Point2D;
  51: import java.awt.geom.Rectangle2D;
  52: import java.util.Arrays;
  53: 
  54: public class FreetypeGlyphVector extends GlyphVector
  55: {
  56:   /**
  57:    * The associated font and its peer.
  58:    */
  59:   private Font font;
  60:   private GdkFontPeer peer; // ATTN: Accessed from native code.
  61: 
  62:   private Rectangle2D logicalBounds;
  63: 
  64:   private float[] glyphPositions;
  65:   /**
  66:    * The string represented by this GlyphVector.
  67:    */
  68:   private String s;
  69: 
  70:   /**
  71:    * The font render context
  72:    */
  73:   private FontRenderContext frc;
  74: 
  75:   /**
  76:    * The total # of glyphs.
  77:    */
  78:   private int nGlyphs;
  79: 
  80:   /**
  81:    * The glyph codes
  82:    */
  83:   private int[] glyphCodes;
  84: 
  85:   /**
  86:    * The set of fonts used in this glyph vector.
  87:    */
  88:   private long[] fontSet = null;
  89: 
  90:   /**
  91:    * Glyph transforms.  Supports all transform operations.
  92:    *
  93:    * The identity transform should not be stored in this array; use a null
  94:    * instead (will result in performance improvements).
  95:    */
  96:   private AffineTransform[] glyphTransforms;
  97: 
  98:   private GlyphMetrics[] metricsCache;
  99: 
 100:   private native void dispose(long[] fonts);
 101: 
 102:   /**
 103:    * Returns a pointer to the native PangoFcFont object.
 104:    *
 105:    * The object will be referenced with g_object_ref n times before being
 106:    * returned, and must be unreferenced a corresponding number of times.
 107:    *
 108:    * @param n Number of times to reference the object.
 109:    * @return Pointer to the native default font.
 110:    */
 111:   private native long getNativeFontPointer(int n);
 112: 
 113:   /**
 114:    * Create a glyphvector from a given (Freetype) font and a String.
 115:    */
 116:   public FreetypeGlyphVector(Font f, String s, FontRenderContext frc)
 117:   {
 118:     this(f, s.toCharArray(), 0, s.length(), frc, Font.LAYOUT_LEFT_TO_RIGHT);
 119:   }
 120: 
 121:   /**
 122:    * Create a glyphvector from a given (Freetype) font and a String.
 123:    */
 124:   public FreetypeGlyphVector(Font f, char[] chars, int start, int len,
 125:                              FontRenderContext frc, int flags)
 126:   {
 127:     this.s = new String(chars, start, len);
 128: 
 129:     this.font = f;
 130:     this.frc = frc;
 131:     if( !(font.getPeer() instanceof GdkFontPeer ) )
 132:       throw new IllegalArgumentException("Not a valid font.");
 133:     peer = (GdkFontPeer)font.getPeer();
 134: 
 135:     getGlyphs();
 136:     if( flags == Font.LAYOUT_RIGHT_TO_LEFT )
 137:       {
 138:         // reverse the glyph ordering.
 139:         int[] temp = new int[ nGlyphs ];
 140:         for(int i = 0; i < nGlyphs; i++)
 141:           temp[i] = glyphCodes[nGlyphs - i - 1];
 142:         glyphCodes = temp;
 143:       }
 144:     performDefaultLayout();
 145:   }
 146: 
 147:   /**
 148:    * Create a glyphvector from a given set of glyph codes.
 149:    */
 150:   public FreetypeGlyphVector(Font f, int[] codes, FontRenderContext frc)
 151:   {
 152:     this.font = f;
 153:     this.frc = frc;
 154:     if( !(font.getPeer() instanceof GdkFontPeer ) )
 155:       throw new IllegalArgumentException("Not a valid font.");
 156:     peer = (GdkFontPeer)font.getPeer();
 157: 
 158:     glyphCodes = new int[ codes.length ];
 159:     System.arraycopy(codes, 0, glyphCodes, 0, codes.length);
 160:     nGlyphs = glyphCodes.length;
 161: 
 162:     if (fontSet == null)
 163:       {
 164:         fontSet = new long[nGlyphs];
 165:         Arrays.fill(fontSet, getNativeFontPointer(nGlyphs));
 166:       }
 167: 
 168:     performDefaultLayout();
 169:   }
 170: 
 171:   /**
 172:    * Cloning constructor
 173:    */
 174:   private FreetypeGlyphVector( FreetypeGlyphVector gv )
 175:   {
 176:     font = gv.font;
 177:     peer = gv.peer;
 178:     frc = gv.frc;
 179:     s = gv.s;
 180:     nGlyphs = gv.nGlyphs;
 181:     logicalBounds = gv.logicalBounds.getBounds2D();
 182: 
 183:     if( gv.metricsCache != null )
 184:       {
 185:         metricsCache = new GlyphMetrics[ nGlyphs ];
 186:         System.arraycopy(gv.metricsCache, 0, metricsCache, 0, nGlyphs);
 187:       }
 188: 
 189:     glyphCodes = new int[ nGlyphs ];
 190:     fontSet = new long[nGlyphs];
 191:     glyphPositions = new float[(nGlyphs + 1) * 2];
 192:     glyphTransforms = new AffineTransform[ nGlyphs ];
 193:     Arrays.fill(glyphTransforms, null);
 194: 
 195:     for(int i = 0; i < nGlyphs; i++ )
 196:       {
 197:         if (gv.glyphTransforms[i] != null)
 198:           glyphTransforms[ i ] = new AffineTransform(gv.glyphTransforms[i]);
 199:         glyphCodes[i] = gv.glyphCodes[ i ];
 200:       }
 201:     System.arraycopy(gv.glyphPositions, 0, glyphPositions, 0,
 202:                      glyphPositions.length);
 203:     System.arraycopy(gv.glyphCodes, 0, glyphCodes, 0, nGlyphs);
 204:     System.arraycopy(gv.fontSet, 0, fontSet, 0, nGlyphs);
 205:   }
 206: 
 207:   public void finalize()
 208:   {
 209:     dispose(fontSet);
 210:   }
 211: 
 212:   /**
 213:    * Create the array of glyph codes.
 214:    */
 215:   private void getGlyphs()
 216:   {
 217:     nGlyphs = s.codePointCount( 0, s.length() );
 218:     glyphCodes = new int[ nGlyphs ];
 219:     fontSet = new long[ nGlyphs ];
 220:     int[] codePoints = new int[ nGlyphs ];
 221:     int stringIndex = 0;
 222: 
 223:     for(int i = 0; i < nGlyphs; i++)
 224:       {
 225:         codePoints[i] = s.codePointAt( stringIndex );
 226:         // UTF32 surrogate handling
 227:         if( codePoints[i] != (int)s.charAt( stringIndex ) )
 228:           stringIndex ++;
 229:         stringIndex ++;
 230: 
 231:         if (Character.isISOControl(codePoints[i]))
 232:           {
 233:             // Replace with 'hair space'. Should better be 'zero-width space'
 234:             // but that doesn't seem to be supported by default font.
 235:             codePoints[i] = 8202;
 236:           }
 237:       }
 238: 
 239:     getGlyphs( codePoints, glyphCodes, fontSet );
 240:   }
 241: 
 242:   /**
 243:    * Returns the glyph code within the font for a given character
 244:    */
 245:   public native void getGlyphs(int[] codepoints, int[] glyphs, long[] fonts);
 246: 
 247:   /**
 248:    * Returns the kerning of a glyph pair
 249:    */
 250:   private native void getKerning(int leftGlyph, int rightGlyph, long font,
 251:                                  float[] p);
 252: 
 253:   private native double[] getMetricsNative(int glyphCode, long font);
 254: 
 255:   private native GeneralPath getGlyphOutlineNative(int glyphIndex, long font);
 256: 
 257: 
 258:   public Object clone()
 259:   {
 260:     return new FreetypeGlyphVector( this );
 261:   }
 262: 
 263:   /**
 264:    * Duh, compares two instances.
 265:    */
 266:   public boolean equals(GlyphVector gv)
 267:   {
 268:     if( ! (gv instanceof FreetypeGlyphVector) )
 269:       return false;
 270: 
 271:     return (((FreetypeGlyphVector)gv).font.equals(font) &&
 272:             ((FreetypeGlyphVector)gv).frc.equals(frc)
 273:             && ((FreetypeGlyphVector)gv).s.equals(s));
 274:   }
 275: 
 276:   /**
 277:    * Returns the associated Font
 278:    */
 279:   public Font getFont()
 280:   {
 281:     return font;
 282:   }
 283: 
 284:   /**
 285:    * Returns the associated FontRenderContext
 286:    */
 287:   public FontRenderContext getFontRenderContext()
 288:   {
 289:     return frc;
 290:   }
 291: 
 292:   /**
 293:    * Layout the glyphs.
 294:    */
 295:   public void performDefaultLayout()
 296:   {
 297:     logicalBounds = null; // invalidate caches.
 298:     glyphTransforms = new AffineTransform[nGlyphs];
 299:     Arrays.fill(glyphTransforms, null);
 300:     glyphPositions = new float[(nGlyphs + 1) * 2];
 301: 
 302:     GlyphMetrics gm = null;
 303:     float x = 0;
 304:     float y = 0;
 305:     float[] p = {0.0f, 0.0f};
 306:     for(int i = 0; i < nGlyphs; i++)
 307:       {
 308:         gm = getGlyphMetrics( i );
 309:         glyphPositions[i*2] = x;
 310:         glyphPositions[i*2 + 1] = y;
 311: 
 312:         x += gm.getAdvanceX();
 313:         y += gm.getAdvanceY();
 314: 
 315:         // Get the kerning only if it's not the last glyph, and the two glyphs are
 316:         // using the same font
 317:         if (i != nGlyphs-1 && fontSet[i] == fontSet[i+1])
 318:           {
 319:             getKerning(glyphCodes[i], glyphCodes[i + 1], fontSet[i], p);
 320:             x += p[0];
 321:             y += p[1];
 322:           }
 323:       }
 324:     glyphPositions[nGlyphs * 2] = x;
 325:     glyphPositions[nGlyphs * 2 + 1] = y;
 326: 
 327:     // Apply any transform that may be in the font's attributes
 328:     TransformAttribute ta;
 329:     ta = (TransformAttribute)font.getAttributes().get(TextAttribute.TRANSFORM);
 330:     if (ta != null)
 331:       {
 332:         AffineTransform tx = ta.getTransform();
 333: 
 334:         // Transform glyph positions
 335:         tx.transform(glyphPositions, 0, glyphPositions, 0,
 336:                      glyphPositions.length / 2);
 337: 
 338:         // Also store per-glyph scale/shear/rotate (but not translation)
 339:         double[] matrix = new double[4];
 340:         tx.getMatrix(matrix);
 341:         AffineTransform deltaTx = new AffineTransform(matrix);
 342:         if (!deltaTx.isIdentity())
 343:           Arrays.fill(glyphTransforms, deltaTx);
 344:       }
 345:   }
 346: 
 347:   /**
 348:    * Returns the code of the glyph at glyphIndex;
 349:    */
 350:   public int getGlyphCode(int glyphIndex)
 351:   {
 352:     return glyphCodes[ glyphIndex ];
 353:   }
 354: 
 355:   /**
 356:    * Returns multiple glyphcodes.
 357:    */
 358:   public int[] getGlyphCodes(int beginGlyphIndex, int numEntries,
 359:                              int[] codeReturn)
 360:   {
 361:     int[] rval;
 362: 
 363:     if( codeReturn == null || codeReturn.length < numEntries)
 364:       rval = new int[ numEntries ];
 365:     else
 366:       rval = codeReturn;
 367: 
 368:     System.arraycopy(glyphCodes, beginGlyphIndex, rval, 0, numEntries);
 369: 
 370:     return rval;
 371:   }
 372: 
 373:   /**
 374:    * Returns pointers to the fonts used in this glyph vector.
 375:    *
 376:    * The array index matches that of the glyph vector itself.
 377:    */
 378:   protected long[] getGlyphFonts(int beginGlyphIndex, int numEntries,
 379:                                  long[] codeReturn)
 380:   {
 381:     long[] rval;
 382: 
 383:     if( codeReturn == null || codeReturn.length < numEntries)
 384:       rval = new long[ numEntries ];
 385:     else
 386:       rval = codeReturn;
 387: 
 388:     System.arraycopy(fontSet, beginGlyphIndex, rval, 0, numEntries);
 389: 
 390:     return rval;
 391:   }
 392: 
 393:   public Shape getGlyphLogicalBounds(int glyphIndex)
 394:   {
 395:     GlyphMetrics gm = getGlyphMetrics( glyphIndex );
 396:     if( gm == null )
 397:       return null;
 398:     Rectangle2D r = gm.getBounds2D();
 399:     Point2D p = getGlyphPosition( glyphIndex );
 400: 
 401:     double[] bounds = new double[] {p.getX() + r.getX() - gm.getLSB(),
 402:                                     p.getY() + r.getY(),
 403:                                     p.getX() + r.getX() - gm.getLSB() + gm.getAdvanceX(),
 404:                                     p.getY() + r.getY() + r.getHeight()};
 405: 
 406:     if (glyphTransforms[glyphIndex] != null)
 407:       glyphTransforms[glyphIndex].transform(bounds, 0, bounds, 0, 2);
 408: 
 409:     return new Rectangle2D.Double(bounds[0], bounds[1], bounds[2] - bounds[0],
 410:                                   bounds[3] - bounds[1]);
 411:   }
 412: 
 413:   /*
 414:    * FIXME: Not all glyph types are supported.
 415:    * (The JDK doesn't really seem to do so either)
 416:    */
 417:   public void setupGlyphMetrics()
 418:   {
 419:     metricsCache = new GlyphMetrics[ nGlyphs ];
 420: 
 421:     for(int i = 0; i < nGlyphs; i++)
 422:       {
 423:         GlyphMetrics gm = (GlyphMetrics)peer.getGlyphMetrics(glyphCodes[i]);
 424:         if( gm == null )
 425:           {
 426:             double[] val = getMetricsNative(glyphCodes[i], fontSet[i]);
 427:             if( val == null )
 428:               gm = null;
 429:             else
 430:               {
 431:                 gm = new GlyphMetrics(true,
 432:                                       (float)val[1],
 433:                                       (float)val[2],
 434:                                       new Rectangle2D.Double(val[3], val[4],
 435:                                                              val[5], val[6] ),
 436:                                       GlyphMetrics.STANDARD );
 437:                 peer.putGlyphMetrics( glyphCodes[ i ], gm );
 438:               }
 439:           }
 440:         metricsCache[ i ] = gm;
 441:       }
 442:   }
 443: 
 444:   /**
 445:    * Returns the metrics of a single glyph.
 446:    */
 447:   public GlyphMetrics getGlyphMetrics(int glyphIndex)
 448:   {
 449:     if( metricsCache == null )
 450:       setupGlyphMetrics();
 451: 
 452:     return metricsCache[ glyphIndex ];
 453:   }
 454: 
 455:   /**
 456:    * Returns the outline of a single glyph.
 457:    *
 458:    * Despite what the Sun API says, this method returns the glyph relative to
 459:    * the origin of the *entire string*, not each individual glyph.
 460:    */
 461:   public Shape getGlyphOutline(int glyphIndex)
 462:   {
 463:     GeneralPath gp = getGlyphOutlineNative(glyphCodes[glyphIndex],
 464:                                            fontSet[glyphIndex]);
 465: 
 466:     AffineTransform tx = AffineTransform.getTranslateInstance(glyphPositions[glyphIndex*2],
 467:                                                               glyphPositions[glyphIndex*2+1]);
 468:     if (glyphTransforms[glyphIndex] != null)
 469:       tx.concatenate( glyphTransforms[glyphIndex]);
 470: 
 471:     gp.transform(tx);
 472:     return gp;
 473:   }
 474: 
 475:   /**
 476:    * Returns the position of a single glyph.
 477:    */
 478:   public Point2D getGlyphPosition(int glyphIndex)
 479:   {
 480:     return new Point2D.Float(glyphPositions[glyphIndex*2],
 481:                              glyphPositions[glyphIndex*2 + 1]);
 482:   }
 483: 
 484:   /**
 485:    * Returns the positions of multiple glyphs.
 486:    */
 487:   public float[] getGlyphPositions(int beginGlyphIndex, int numEntries,
 488:                                    float[] positionReturn)
 489:   {
 490:     if (positionReturn == null || positionReturn.length < (numEntries * 2))
 491:       positionReturn = new float[numEntries*2];
 492: 
 493:     System.arraycopy(glyphPositions, beginGlyphIndex*2, positionReturn, 0,
 494:                      numEntries*2);
 495:     return positionReturn;
 496:   }
 497: 
 498:   /**
 499:    * Returns the transform of a glyph.
 500:    */
 501:   public AffineTransform getGlyphTransform(int glyphIndex)
 502:   {
 503:     return glyphTransforms[glyphIndex];
 504:   }
 505: 
 506:   /**
 507:    * Checks whether any transform has been set on any glyphs.
 508:    */
 509:   protected boolean hasTransforms()
 510:   {
 511:     for (int i = 0; i < glyphTransforms.length; i++)
 512:       if (glyphTransforms[i] != null)
 513:         return true;
 514: 
 515:     return false;
 516:   }
 517: 
 518:   /**
 519:    * Returns the visual bounds of a glyph
 520:    * May be off by a pixel or two due to hinting/rasterization.
 521:    */
 522:   public Shape getGlyphVisualBounds(int glyphIndex)
 523:   {
 524:     return getGlyphOutline( glyphIndex ).getBounds2D();
 525:   }
 526: 
 527:   /**
 528:    * Return the logical bounds of the whole thing.
 529:    */
 530:   public Rectangle2D getLogicalBounds()
 531:   {
 532:     if( nGlyphs == 0 )
 533:       return new Rectangle2D.Double(0, 0, 0, 0);
 534:     if( logicalBounds != null )
 535:       return logicalBounds;
 536: 
 537:     Rectangle2D rect = (Rectangle2D)getGlyphLogicalBounds( 0 );
 538:     for( int i = 1; i < nGlyphs; i++ )
 539:       {
 540:         Rectangle2D r2 = (Rectangle2D)getGlyphLogicalBounds( i );
 541: 
 542:         rect = rect.createUnion( r2 );
 543:       }
 544: 
 545:     logicalBounds = rect;
 546:     return rect;
 547:   }
 548: 
 549:   /**
 550:    * Returns the number of glyphs.
 551:    */
 552:   public int getNumGlyphs()
 553:   {
 554:     return glyphCodes.length;
 555:   }
 556: 
 557:   /**
 558:    * Returns the outline of the entire GlyphVector.
 559:    */
 560:   public Shape getOutline()
 561:   {
 562:     GeneralPath path = new GeneralPath();
 563:     for( int i = 0; i < getNumGlyphs(); i++ )
 564:       path.append(getGlyphOutline(i), false);
 565:     return path;
 566:   }
 567: 
 568:   /**
 569:    * TODO:
 570:    * FreeType does not currently have an API for the JSTF table. We should
 571:    * probably get the table ourselves from FT and pass it to some parser
 572:    * which the native font peers will need.
 573:    */
 574:   public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex)
 575:   {
 576:     return null;
 577:   }
 578: 
 579:   /**
 580:    * Returns the outline of the entire vector, drawn at (x,y).
 581:    */
 582:   public Shape getOutline(float x, float y)
 583:   {
 584:     AffineTransform tx = AffineTransform.getTranslateInstance( x, y );
 585:     GeneralPath gp = (GeneralPath)getOutline();
 586:     gp.transform( tx );
 587:     return gp;
 588:   }
 589: 
 590:   /**
 591:    * Returns the visual bounds of the entire GlyphVector.
 592:    * May be off by a pixel or two due to hinting/rasterization.
 593:    */
 594:   public Rectangle2D getVisualBounds()
 595:   {
 596:     return getOutline().getBounds2D();
 597:   }
 598: 
 599:   /**
 600:    * Sets the position of a glyph.
 601:    */
 602:   public void setGlyphPosition(int glyphIndex, Point2D newPos)
 603:   {
 604:     glyphPositions[glyphIndex*2] = (float)(newPos.getX());
 605:     glyphPositions[glyphIndex*2 + 1] = (float)(newPos.getY());
 606:     logicalBounds = null;
 607:   }
 608: 
 609:   /**
 610:    * Sets the transform of a single glyph.
 611:    */
 612:   public void setGlyphTransform(int glyphIndex, AffineTransform newTX)
 613:   {
 614:     // The identity transform should never be in the glyphTransforms array;
 615:     // using and checking for nulls can be much faster.
 616:     if (newTX != null && newTX.isIdentity())
 617:       newTX = null;
 618: 
 619:     // If the old and new transforms are identical, bail
 620:     if (glyphTransforms[glyphIndex] == null && newTX == null)
 621:       return;
 622: 
 623:     if (newTX != null && newTX.equals(glyphTransforms[glyphIndex]))
 624:       return;
 625: 
 626:     // Invalidate bounds cache and set new transform
 627:     logicalBounds = null;
 628:     glyphTransforms[glyphIndex] = newTX;
 629:   }
 630: }