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 Point2D getKerning(int leftGlyph, int rightGlyph, long font);
 251: 
 252:   private native double[] getMetricsNative(int glyphCode, long font);
 253: 
 254:   private native GeneralPath getGlyphOutlineNative(int glyphIndex, long font);
 255: 
 256: 
 257:   public Object clone()
 258:   {
 259:     return new FreetypeGlyphVector( this );
 260:   }
 261: 
 262:   /**
 263:    * Duh, compares two instances.
 264:    */
 265:   public boolean equals(GlyphVector gv)
 266:   {
 267:     if( ! (gv instanceof FreetypeGlyphVector) )
 268:       return false;
 269: 
 270:     return (((FreetypeGlyphVector)gv).font.equals(font) && 
 271:         ((FreetypeGlyphVector)gv).frc.equals(frc)
 272:         && ((FreetypeGlyphVector)gv).s.equals(s));
 273:   }
 274: 
 275:   /**
 276:    * Returns the associated Font
 277:    */
 278:   public Font getFont()
 279:   {
 280:     return font;
 281:   }
 282: 
 283:   /**
 284:    * Returns the associated FontRenderContext
 285:    */
 286:   public FontRenderContext getFontRenderContext()
 287:   {
 288:     return frc;
 289:   }
 290: 
 291:   /**
 292:    * Layout the glyphs.
 293:    */
 294:   public void performDefaultLayout()
 295:   {
 296:     logicalBounds = null; // invalidate caches.
 297:     glyphTransforms = new AffineTransform[nGlyphs];
 298:     Arrays.fill(glyphTransforms, null);
 299:     glyphPositions = new float[(nGlyphs + 1) * 2];
 300: 
 301:     GlyphMetrics gm = null;
 302:     float x = 0;
 303:     float y = 0;
 304:     for(int i = 0; i < nGlyphs; i++)
 305:       {
 306:         gm = getGlyphMetrics( i );
 307:         glyphPositions[i*2] = x;
 308:         glyphPositions[i*2 + 1] = y;
 309: 
 310:         x += gm.getAdvanceX();
 311:         y += gm.getAdvanceY();
 312: 
 313:         // Get the kerning only if it's not the last glyph, and the two glyphs are
 314:         // using the same font
 315:         if (i != nGlyphs-1 && fontSet[i] == fontSet[i+1])
 316:           {
 317:             Point2D p = getKerning(glyphCodes[i], glyphCodes[i + 1], fontSet[i]);
 318:             x += p.getX();
 319:             y += p.getY();
 320:           }
 321:       }
 322:     glyphPositions[nGlyphs * 2] = x;
 323:     glyphPositions[nGlyphs * 2 + 1] = y;
 324:     
 325:     // Apply any transform that may be in the font's attributes
 326:     TransformAttribute ta;
 327:     ta = (TransformAttribute)font.getAttributes().get(TextAttribute.TRANSFORM);
 328:     if (ta != null)
 329:       {
 330:         AffineTransform tx = ta.getTransform();
 331:         
 332:         // Transform glyph positions
 333:         tx.transform(glyphPositions, 0, glyphPositions, 0,
 334:                      glyphPositions.length / 2);
 335:         
 336:         // Also store per-glyph scale/shear/rotate (but not translation) 
 337:         double[] matrix = new double[4];
 338:         tx.getMatrix(matrix);
 339:         AffineTransform deltaTx = new AffineTransform(matrix);
 340:         if (!deltaTx.isIdentity())
 341:           Arrays.fill(glyphTransforms, deltaTx);
 342:       }
 343:   }
 344: 
 345:   /**
 346:    * Returns the code of the glyph at glyphIndex;
 347:    */
 348:   public int getGlyphCode(int glyphIndex)
 349:   {
 350:     return glyphCodes[ glyphIndex ];
 351:   }
 352: 
 353:   /**
 354:    * Returns multiple glyphcodes.
 355:    */
 356:   public int[] getGlyphCodes(int beginGlyphIndex, int numEntries, 
 357:                              int[] codeReturn)
 358:   {
 359:     int[] rval;
 360: 
 361:     if( codeReturn == null || codeReturn.length < numEntries)
 362:       rval = new int[ numEntries ];
 363:     else
 364:       rval = codeReturn;
 365:     
 366:     System.arraycopy(glyphCodes, beginGlyphIndex, rval, 0, numEntries);
 367: 
 368:     return rval;
 369:   }
 370: 
 371:   /**
 372:    * Returns pointers to the fonts used in this glyph vector.
 373:    * 
 374:    * The array index matches that of the glyph vector itself.
 375:    */
 376:   protected long[] getGlyphFonts(int beginGlyphIndex, int numEntries, 
 377:                                  long[] codeReturn)
 378:   {
 379:     long[] rval;
 380: 
 381:     if( codeReturn == null || codeReturn.length < numEntries)
 382:       rval = new long[ numEntries ];
 383:     else
 384:       rval = codeReturn;
 385:     
 386:     System.arraycopy(fontSet, beginGlyphIndex, rval, 0, numEntries);
 387: 
 388:     return rval;
 389:   }
 390: 
 391:   public Shape getGlyphLogicalBounds(int glyphIndex)
 392:   {
 393:     GlyphMetrics gm = getGlyphMetrics( glyphIndex );
 394:     if( gm == null )
 395:       return null; 
 396:     Rectangle2D r = gm.getBounds2D();
 397:     Point2D p = getGlyphPosition( glyphIndex );
 398:     
 399:     double[] bounds = new double[] {p.getX() + r.getX() - gm.getLSB(),
 400:                                     p.getY() + r.getY(),
 401:                                     p.getX() + r.getX() - gm.getLSB() + gm.getAdvanceX(),
 402:                                     p.getY() + r.getY() + r.getHeight()};
 403:     
 404:     if (glyphTransforms[glyphIndex] != null)
 405:       glyphTransforms[glyphIndex].transform(bounds, 0, bounds, 0, 2);
 406:     
 407:     return new Rectangle2D.Double(bounds[0], bounds[1], bounds[2] - bounds[0],
 408:                                   bounds[3] - bounds[1]);
 409:   }
 410: 
 411:   /*
 412:    * FIXME: Not all glyph types are supported.
 413:    * (The JDK doesn't really seem to do so either)
 414:    */
 415:   public void setupGlyphMetrics()
 416:   {
 417:     metricsCache = new GlyphMetrics[ nGlyphs ];
 418: 
 419:     for(int i = 0; i < nGlyphs; i++)
 420:       {
 421:         GlyphMetrics gm = (GlyphMetrics)peer.getGlyphMetrics(glyphCodes[i]);
 422:         if( gm == null )
 423:           {
 424:             double[] val = getMetricsNative(glyphCodes[i], fontSet[i]);
 425:             if( val == null )
 426:               gm = null;
 427:             else
 428:               {
 429:                 gm = new GlyphMetrics(true, 
 430:                                       (float)val[1], 
 431:                                       (float)val[2], 
 432:                                       new Rectangle2D.Double(val[3], val[4], 
 433:                                                              val[5], val[6] ),
 434:                                       GlyphMetrics.STANDARD );
 435:                 peer.putGlyphMetrics( glyphCodes[ i ], gm );
 436:               }
 437:           }
 438:         metricsCache[ i ] = gm;
 439:       }
 440:   }
 441: 
 442:   /**
 443:    * Returns the metrics of a single glyph.
 444:    */
 445:   public GlyphMetrics getGlyphMetrics(int glyphIndex)
 446:   {
 447:     if( metricsCache == null )
 448:       setupGlyphMetrics();
 449: 
 450:     return metricsCache[ glyphIndex ];
 451:   }
 452: 
 453:   /**
 454:    * Returns the outline of a single glyph.
 455:    * 
 456:    * Despite what the Sun API says, this method returns the glyph relative to
 457:    * the origin of the *entire string*, not each individual glyph.
 458:    */
 459:   public Shape getGlyphOutline(int glyphIndex)
 460:   {
 461:     GeneralPath gp = getGlyphOutlineNative(glyphCodes[glyphIndex],
 462:                                            fontSet[glyphIndex]);
 463:     
 464:     AffineTransform tx = AffineTransform.getTranslateInstance(glyphPositions[glyphIndex*2],
 465:                                                               glyphPositions[glyphIndex*2+1]);
 466:     if (glyphTransforms[glyphIndex] != null)
 467:       tx.concatenate( glyphTransforms[glyphIndex]);
 468: 
 469:     gp.transform(tx);
 470:     return gp;
 471:   }
 472: 
 473:   /**
 474:    * Returns the position of a single glyph.
 475:    */
 476:   public Point2D getGlyphPosition(int glyphIndex)
 477:   {
 478:     return new Point2D.Float(glyphPositions[glyphIndex*2],
 479:                              glyphPositions[glyphIndex*2 + 1]);
 480:   }
 481: 
 482:   /**
 483:    * Returns the positions of multiple glyphs.
 484:    */
 485:   public float[] getGlyphPositions(int beginGlyphIndex, int numEntries, 
 486:                    float[] positionReturn)
 487:   {
 488:     if (positionReturn == null || positionReturn.length < (numEntries * 2))
 489:       positionReturn = new float[numEntries*2];
 490:     
 491:     System.arraycopy(glyphPositions, beginGlyphIndex*2, positionReturn, 0,
 492:                      numEntries*2);
 493:     return positionReturn;
 494:   }
 495: 
 496:   /**
 497:    * Returns the transform of a glyph.
 498:    */
 499:   public AffineTransform getGlyphTransform(int glyphIndex)
 500:   {
 501:     return glyphTransforms[glyphIndex];
 502:   }
 503:   
 504:   /**
 505:    * Checks whether any transform has been set on any glyphs.
 506:    */
 507:   protected boolean hasTransforms()
 508:   {
 509:     for (int i = 0; i < glyphTransforms.length; i++)
 510:       if (glyphTransforms[i] != null)
 511:         return true;
 512:     
 513:     return false;
 514:   }
 515:   
 516:   /**
 517:    * Returns the visual bounds of a glyph
 518:    * May be off by a pixel or two due to hinting/rasterization.
 519:    */
 520:   public Shape getGlyphVisualBounds(int glyphIndex)
 521:   {
 522:     return getGlyphOutline( glyphIndex ).getBounds2D();
 523:   }
 524: 
 525:   /**
 526:    * Return the logical bounds of the whole thing.
 527:    */
 528:   public Rectangle2D getLogicalBounds()
 529:   {
 530:     if( nGlyphs == 0 )
 531:       return new Rectangle2D.Double(0, 0, 0, 0);
 532:     if( logicalBounds != null )
 533:       return logicalBounds;
 534: 
 535:     Rectangle2D rect = (Rectangle2D)getGlyphLogicalBounds( 0 );
 536:     for( int i = 1; i < nGlyphs; i++ )
 537:       {
 538:         Rectangle2D r2 = (Rectangle2D)getGlyphLogicalBounds( i );
 539:         
 540:         rect = rect.createUnion( r2 );
 541:       }
 542: 
 543:     logicalBounds = rect;
 544:     return rect;
 545:   }
 546: 
 547:   /**
 548:    * Returns the number of glyphs.
 549:    */
 550:   public int getNumGlyphs()
 551:   {
 552:     return glyphCodes.length;
 553:   }
 554: 
 555:   /**
 556:    * Returns the outline of the entire GlyphVector.
 557:    */
 558:   public Shape getOutline()
 559:   {
 560:     GeneralPath path = new GeneralPath();
 561:     for( int i = 0; i < getNumGlyphs(); i++ )
 562:       path.append(getGlyphOutline(i), false);
 563:     return path;
 564:   }
 565: 
 566:   /**
 567:    * TODO: 
 568:    * FreeType does not currently have an API for the JSTF table. We should 
 569:    * probably get the table ourselves from FT and pass it to some parser 
 570:    * which the native font peers will need.
 571:    */
 572:   public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex)
 573:   {
 574:     return null;
 575:   }
 576: 
 577:   /**
 578:    * Returns the outline of the entire vector, drawn at (x,y).
 579:    */
 580:   public Shape getOutline(float x, float y)
 581:   {
 582:     AffineTransform tx = AffineTransform.getTranslateInstance( x, y );
 583:     GeneralPath gp = (GeneralPath)getOutline();
 584:     gp.transform( tx );
 585:     return gp;
 586:   }
 587: 
 588:   /**
 589:    * Returns the visual bounds of the entire GlyphVector.
 590:    * May be off by a pixel or two due to hinting/rasterization.
 591:    */
 592:   public Rectangle2D getVisualBounds()
 593:   {
 594:     return getOutline().getBounds2D();
 595:   }
 596: 
 597:   /**
 598:    * Sets the position of a glyph.
 599:    */
 600:   public void setGlyphPosition(int glyphIndex, Point2D newPos)
 601:   {
 602:     glyphPositions[glyphIndex*2] = (float)(newPos.getX());
 603:     glyphPositions[glyphIndex*2 + 1] = (float)(newPos.getY());
 604:     logicalBounds = null;
 605:   }
 606: 
 607:   /**
 608:    * Sets the transform of a single glyph.
 609:    */
 610:   public void setGlyphTransform(int glyphIndex, AffineTransform newTX)
 611:   {
 612:     // The identity transform should never be in the glyphTransforms array;
 613:     // using and checking for nulls can be much faster.
 614:     if (newTX != null && newTX.isIdentity())
 615:       newTX = null;
 616:     
 617:     // If the old and new transforms are identical, bail
 618:     if (glyphTransforms[glyphIndex] == null && newTX == null)
 619:       return;
 620:     
 621:     if (newTX != null && newTX.equals(glyphTransforms[glyphIndex]))
 622:       return;
 623:     
 624:     // Invalidate bounds cache and set new transform
 625:     logicalBounds = null;
 626:     glyphTransforms[glyphIndex] = newTX;
 627:   }
 628: }