OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / gnu / java / awt / peer / gtk / FreetypeGlyphVector.java
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.geom.AffineTransform;
47 import java.awt.geom.GeneralPath;
48 import java.awt.geom.Point2D;
49 import java.awt.geom.Rectangle2D;
50 import java.util.Arrays;
51
52 public class FreetypeGlyphVector extends GlyphVector
53 {
54   /**
55    * The associated font and its peer.
56    */
57   private Font font;
58   private GdkFontPeer peer; // ATTN: Accessed from native code.
59
60   private Rectangle2D logicalBounds;
61
62   private float[] glyphPositions;
63   /**
64    * The string represented by this GlyphVector.
65    */
66   private String s;
67
68   /**
69    * The font render context
70    */
71   private FontRenderContext frc;
72
73   /**
74    * The total # of glyphs.
75    */
76   private int nGlyphs;
77
78   /**
79    * The glyph codes
80    */
81   private int[] glyphCodes;
82
83   /**
84    * Glyph transforms. (de facto only the translation is used)
85    */
86   private AffineTransform[] glyphTransforms;
87
88   private GlyphMetrics[] metricsCache;
89
90   /**
91    * Create a glyphvector from a given (Freetype) font and a String.
92    */
93   public FreetypeGlyphVector(Font f, String s, FontRenderContext frc)
94   {
95     this(f, s.toCharArray(), 0, s.length(), frc, Font.LAYOUT_LEFT_TO_RIGHT);
96   }
97
98   /**
99    * Create a glyphvector from a given (Freetype) font and a String.
100    */
101   public FreetypeGlyphVector(Font f, char[] chars, int start, int len,
102                              FontRenderContext frc, int flags)
103   {
104     this.s = new String(chars, start, len);
105
106     this.font = f;
107     this.frc = frc;
108     if( !(font.getPeer() instanceof GdkFontPeer ) )
109       throw new IllegalArgumentException("Not a valid font.");
110     peer = (GdkFontPeer)font.getPeer();
111
112     getGlyphs();
113     if( flags == Font.LAYOUT_RIGHT_TO_LEFT )
114       {
115         // reverse the glyph ordering.
116         int[] temp = new int[ nGlyphs ];
117         for(int i = 0; i < nGlyphs; i++)
118           temp[ i ] = glyphCodes[ nGlyphs - i - 1];
119         glyphCodes = temp;
120       }
121     performDefaultLayout();
122   }
123
124   /**
125    * Create a glyphvector from a given set of glyph codes.
126    */
127   public FreetypeGlyphVector(Font f, int[] codes, FontRenderContext frc)
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     glyphCodes = new int[ codes.length ];
136     System.arraycopy(codes, 0, glyphCodes, 0, codes.length);
137     nGlyphs = glyphCodes.length;
138     performDefaultLayout();
139   }
140
141   /**
142    * Cloning constructor
143    */  
144   private FreetypeGlyphVector( FreetypeGlyphVector gv )
145   {
146     font = gv.font;
147     peer = gv.peer;
148     frc = gv.frc;
149     s = gv.s;
150     nGlyphs = gv.nGlyphs;
151     logicalBounds = gv.logicalBounds.getBounds2D();
152
153     if( gv.metricsCache != null )
154       {
155         metricsCache = new GlyphMetrics[ nGlyphs ];
156         System.arraycopy(gv.metricsCache, 0, metricsCache, 0, nGlyphs);
157       }
158
159     glyphCodes = new int[ nGlyphs ];
160     glyphPositions = new float[(nGlyphs + 1) * 2];
161     glyphTransforms = new AffineTransform[ nGlyphs ];
162     for(int i = 0; i < nGlyphs; i++ )
163       {
164         glyphTransforms[ i ] = new AffineTransform( gv.glyphTransforms[ i ] );
165         glyphCodes[i] = gv.glyphCodes[ i ];
166       }
167     System.arraycopy(gv.glyphPositions, 0, glyphPositions, 0,
168                      glyphPositions.length);
169   }
170
171   /**
172    * Create the array of glyph codes.
173    */
174   private void getGlyphs()
175   {
176     nGlyphs = s.codePointCount( 0, s.length() );
177     glyphCodes = new int[ nGlyphs ];
178     int[] codePoints = new int[ nGlyphs ];
179     int stringIndex = 0;
180
181     for(int i = 0; i < nGlyphs; i++)
182       {
183         codePoints[i] = s.codePointAt( stringIndex );
184         // UTF32 surrogate handling
185         if( codePoints[i] != (int)s.charAt( stringIndex ) )
186           stringIndex ++;
187         stringIndex ++;
188
189         if (Character.isISOControl(codePoints[i]))
190           {
191             // Replace with 'hair space'. Should better be 'zero-width space'
192             // but that doesn't seem to be supported by default font.
193             codePoints[i] = 8202;
194           }
195       }
196
197    glyphCodes = getGlyphs( codePoints );
198   }
199
200   /**
201    * Returns the glyph code within the font for a given character
202    */
203   public native int[] getGlyphs(int[] codepoints);
204
205   /**
206    * Returns the kerning of a glyph pair
207    */
208   private native Point2D getKerning(int leftGlyph, int rightGlyph);
209
210   private native double[] getMetricsNative( int glyphCode );
211
212   private native GeneralPath getGlyphOutlineNative(int glyphIndex);
213
214
215   public Object clone()
216   {
217     return new FreetypeGlyphVector( this );
218   }
219
220   /**
221    * Duh, compares two instances.
222    */
223   public boolean equals(GlyphVector gv)
224   {
225     if( ! (gv instanceof FreetypeGlyphVector) )
226       return false;
227
228     return (((FreetypeGlyphVector)gv).font.equals(font) && 
229             ((FreetypeGlyphVector)gv).frc.equals(frc)
230             && ((FreetypeGlyphVector)gv).s.equals(s));
231   }
232
233   /**
234    * Returns the associated Font
235    */
236   public Font getFont()
237   {
238     return font;
239   }
240
241   /**
242    * Returns the associated FontRenderContext
243    */
244   public FontRenderContext getFontRenderContext()
245   {
246     return frc;
247   }
248
249   /**
250    * Layout the glyphs.
251    */
252   public void performDefaultLayout()
253   {
254     logicalBounds = null; // invalidate caches.
255     glyphTransforms = new AffineTransform[nGlyphs];
256     Arrays.fill(glyphTransforms, null);
257     glyphPositions = new float[(nGlyphs + 1) * 2];
258
259     GlyphMetrics gm = null;
260     float x = 0;
261     float y = 0;
262     for(int i = 0; i < nGlyphs; i++)
263       {
264         gm = getGlyphMetrics( i );
265         glyphPositions[i*2] = x;
266         glyphPositions[i*2 + 1] = y;
267
268         x += gm.getAdvanceX();
269         y += gm.getAdvanceY();
270         
271         if (i != nGlyphs-1)
272           {
273             Point2D p = getKerning(glyphCodes[i], glyphCodes[i + 1]);
274             x += p.getX();
275             y += p.getY();
276           }
277       }
278     glyphPositions[nGlyphs * 2] = x;
279     glyphPositions[nGlyphs * 2 + 1] = y;
280   }
281
282   /**
283    * Returns the code of the glyph at glyphIndex;
284    */
285   public int getGlyphCode(int glyphIndex)
286   {
287     return glyphCodes[ glyphIndex ];
288   }
289
290   /**
291    * Returns multiple glyphcodes.
292    */
293   public int[] getGlyphCodes(int beginGlyphIndex, int numEntries, 
294                              int[] codeReturn)
295   {
296     int[] rval;
297
298     if( codeReturn == null || codeReturn.length < numEntries)
299       rval = new int[ numEntries ];
300     else
301       rval = codeReturn;
302     
303     System.arraycopy(glyphCodes, beginGlyphIndex, rval, 0, numEntries);
304
305     return rval;
306   }
307
308   public Shape getGlyphLogicalBounds(int glyphIndex)
309   {
310     GlyphMetrics gm = getGlyphMetrics( glyphIndex );
311     if( gm == null )
312       return null; 
313     Rectangle2D r = gm.getBounds2D();
314     Point2D p = getGlyphPosition( glyphIndex );
315     
316     double[] bounds = new double[] {p.getX() + r.getX() - gm.getLSB(),
317                                     p.getY() + r.getY(),
318                                     p.getX() + r.getX() - gm.getLSB() + gm.getAdvanceX(),
319                                     p.getY() + r.getY() + r.getHeight()};
320     
321     if (glyphTransforms[glyphIndex] != null)
322       glyphTransforms[glyphIndex].transform(bounds, 0, bounds, 0, 4);
323     
324     return new Rectangle2D.Double(bounds[0], bounds[1], bounds[2] - bounds[0],
325                                   bounds[3] - bounds[1]);
326   }
327
328   /*
329    * FIXME: Not all glyph types are supported.
330    * (The JDK doesn't really seem to do so either)
331    */
332   public void setupGlyphMetrics()
333   {
334     metricsCache = new GlyphMetrics[ nGlyphs ];
335
336     for(int i = 0; i < nGlyphs; i++)
337       {
338         GlyphMetrics gm = (GlyphMetrics)
339           peer.getGlyphMetrics( glyphCodes[ i ] );
340         if( gm == null )
341           {
342             double[] val = getMetricsNative( glyphCodes[ i ] );
343             if( val == null )
344               gm = null;
345             else
346               {
347                 gm = new GlyphMetrics( true, 
348                                        (float)val[1], 
349                                        (float)val[2], 
350                                        new Rectangle2D.Double
351                                        ( val[3], val[4], 
352                                          val[5], val[6] ),
353                                        GlyphMetrics.STANDARD );
354                 peer.putGlyphMetrics( glyphCodes[ i ], gm );
355               }
356           }
357         metricsCache[ i ] = gm;
358       }
359   }
360
361   /**
362    * Returns the metrics of a single glyph.
363    */
364   public GlyphMetrics getGlyphMetrics(int glyphIndex)
365   {
366     if( metricsCache == null )
367       setupGlyphMetrics();
368
369     return metricsCache[ glyphIndex ];
370   }
371
372   /**
373    * Returns the outline of a single glyph.
374    */
375   public Shape getGlyphOutline(int glyphIndex)
376   {
377     GeneralPath gp = getGlyphOutlineNative( glyphCodes[ glyphIndex ] );
378     if (glyphTransforms[glyphIndex] != null)
379       gp.transform( glyphTransforms[glyphIndex]);
380     
381     return gp;
382   }
383
384   /**
385    * Returns the position of a single glyph.
386    */
387   public Point2D getGlyphPosition(int glyphIndex)
388   {
389     return new Point2D.Float(glyphPositions[glyphIndex*2],
390                              glyphPositions[glyphIndex*2 + 1]);
391   }
392
393   /**
394    * Returns the positions of multiple glyphs.
395    */
396   public float[] getGlyphPositions(int beginGlyphIndex, int numEntries, 
397                                    float[] positionReturn)
398   {
399     if (positionReturn == null || positionReturn.length < (numEntries * 2))
400       positionReturn = new float[numEntries*2];
401     
402     System.arraycopy(glyphPositions, beginGlyphIndex*2, positionReturn, 0,
403                      numEntries*2);
404     return positionReturn;
405   }
406
407   /**
408    * Returns the transform of a glyph.
409    */
410   public AffineTransform getGlyphTransform(int glyphIndex)
411   {
412     return glyphTransforms[glyphIndex];
413   }
414
415   /**
416    * Returns the visual bounds of a glyph
417    * May be off by a pixel or two due to hinting/rasterization.
418    */
419   public Shape getGlyphVisualBounds(int glyphIndex)
420   {
421     return getGlyphOutline( glyphIndex ).getBounds2D();
422   }
423
424   /**
425    * Return the logical bounds of the whole thing.
426    */
427   public Rectangle2D getLogicalBounds()
428   {
429     if( nGlyphs == 0 )
430       return new Rectangle2D.Double(0, 0, 0, 0);
431     if( logicalBounds != null )
432       return logicalBounds;
433
434     Rectangle2D rect = (Rectangle2D)getGlyphLogicalBounds( 0 );
435     AffineTransform tx = new AffineTransform();
436     for( int i = 1; i < nGlyphs; i++ )
437       {
438         Rectangle2D r2 = (Rectangle2D)getGlyphLogicalBounds( i );
439         
440         rect = rect.createUnion( r2 );
441       }
442
443     logicalBounds = rect;
444     return rect;
445   }
446
447   /**
448    * Returns the number of glyphs.
449    */
450   public int getNumGlyphs()
451   {
452     return glyphCodes.length;
453   }
454
455   /**
456    * Returns the outline of the entire GlyphVector.
457    */
458   public Shape getOutline()
459   {
460     GeneralPath path = new GeneralPath();
461     AffineTransform tx = new AffineTransform();
462     for( int i = 0; i < getNumGlyphs(); i++ )
463       {
464         Shape outline = getGlyphOutline(i);
465         tx.setToTranslation(glyphPositions[i*2], glyphPositions[i*2 +1]);
466         outline = tx.createTransformedShape(outline);
467         path.append(outline, false);
468       }
469     return path;
470   }
471
472   /**
473    * TODO: 
474    * FreeType does not currently have an API for the JSTF table. We should 
475    * probably get the table ourselves from FT and pass it to some parser 
476    * which the native font peers will need.
477    */
478   public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex)
479   {
480     return null;
481   }
482
483   /**
484    * Returns the outline of the entire vector, drawn at (x,y).
485    */
486   public Shape getOutline(float x, float y)
487   {
488     AffineTransform tx = AffineTransform.getTranslateInstance( x, y );
489     GeneralPath gp = (GeneralPath)getOutline();
490     gp.transform( tx );
491     return gp;
492   }
493
494   /**
495    * Returns the visual bounds of the entire GlyphVector.
496    * May be off by a pixel or two due to hinting/rasterization.
497    */
498   public Rectangle2D getVisualBounds()
499   {
500     return getOutline().getBounds2D();
501   }
502
503   /**
504    * Sets the position of a glyph.
505    */
506   public void setGlyphPosition(int glyphIndex, Point2D newPos)
507   {
508     glyphPositions[glyphIndex*2] = (float)(newPos.getX());
509     glyphPositions[glyphIndex*2 + 1] = (float)(newPos.getY());
510     logicalBounds = null;
511   }
512
513   /**
514    * Sets the transform of a single glyph.
515    */
516   public void setGlyphTransform(int glyphIndex, AffineTransform newTX)
517   {
518     logicalBounds = null;
519     glyphTransforms[glyphIndex] = newTX;
520   }
521 }