View Javadoc

1   // Copyright 2001-2006, FreeHEP.
2   package org.freehep.graphicsio.font.truetype;
3   
4   import java.awt.Rectangle;
5   import java.awt.geom.AffineTransform;
6   import java.awt.geom.GeneralPath;
7   import java.io.IOException;
8   
9   /**
10   * GLYPH Table.
11   * 
12   * @author Simon Fischer
13   * @version $Id: TTFGlyfTable.java 8584 2006-08-10 23:06:37Z duns $
14   */
15  public class TTFGlyfTable extends TTFVersionTable {
16  
17      /**
18       * If this variable is set to false then the glyphs will not be read until
19       * they are retrieved with <tt>getGlyph(int)</tt>.
20       */
21      private static final boolean READ_GLYPHS = false;
22  
23      public abstract class Glyph {
24  
25          public int xMin, yMin, xMax, yMax;
26  
27          public abstract String getType();
28  
29          public abstract GeneralPath getShape();
30  
31          public void read() throws IOException {
32              xMin = ttf.readFWord();
33              yMin = ttf.readFWord();
34              xMax = ttf.readFWord();
35              yMax = ttf.readFWord();
36          }
37  
38          public Rectangle getBBox() {
39              return new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin);
40          }
41  
42          public String toString() {
43              return "[" + getType() + "] (" + xMin + "," + yMin + "):(" + xMax
44                      + "," + yMax + ")";
45          }
46  
47          public String toDetailedString() {
48              return toString();
49          }
50      }
51  
52      // --------------------------------------------------------------------------------
53  
54      public class SimpleGlyph extends Glyph {
55  
56          private static final int ON_CURVE = 0;
57  
58          private static final int X_SHORT = 1;
59  
60          private static final int Y_SHORT = 2;
61  
62          private static final int REPEAT_FLAG = 3;
63  
64          private static final int X_SAME = 4;
65  
66          private static final int Y_SAME = 5;
67  
68          private static final int X_POSITIVE = 4;
69  
70          private static final int Y_POSITIVE = 5;
71  
72          public int numberOfContours;
73  
74          public int[] endPtsOfContours;
75  
76          public int[] instructions;
77  
78          public int[] flags;
79  
80          public int[] xCoordinates, yCoordinates;
81  
82          public boolean[] onCurve;
83  
84          public GeneralPath shape;
85  
86          public SimpleGlyph(int numberOfContours) {
87              this.numberOfContours = numberOfContours;
88              this.endPtsOfContours = new int[numberOfContours];
89          }
90  
91          public String getType() {
92              return "Simple Glyph";
93          }
94  
95          public void read() throws IOException {
96              super.read();
97  
98              for (int i = 0; i < endPtsOfContours.length; i++)
99                  endPtsOfContours[i] = ttf.readUShort();
100 
101             instructions = new int[ttf.readUShort()];
102             for (int i = 0; i < instructions.length; i++)
103                 instructions[i] = ttf.readByte();
104 
105             int numberOfPoints = endPtsOfContours[endPtsOfContours.length - 1] + 1;
106             flags = new int[numberOfPoints];
107             xCoordinates = new int[numberOfPoints];
108             yCoordinates = new int[numberOfPoints];
109             onCurve = new boolean[numberOfPoints];
110             int repeatCount = 0;
111             int repeatFlag = 0;
112             for (int i = 0; i < numberOfPoints; i++) {
113                 if (repeatCount > 0) {
114                     flags[i] = repeatFlag;
115                     repeatCount--;
116                 } else {
117                     flags[i] = ttf.readRawByte();
118                     if (TTFInput.flagBit(flags[i], REPEAT_FLAG)) {
119                         repeatCount = ttf.readByte();
120                         repeatFlag = flags[i];
121                     }
122                 }
123                 TTFInput.checkZeroBit(flags[i], 6, "flags");
124                 TTFInput.checkZeroBit(flags[i], 7, "flags");
125                 onCurve[i] = TTFInput.flagBit(flags[i], ON_CURVE);
126             }
127 
128             int last = 0;
129             for (int i = 0; i < numberOfPoints; i++) {
130                 if (TTFInput.flagBit(flags[i], X_SHORT)) {
131                     if (TTFInput.flagBit(flags[i], X_POSITIVE)) {
132                         last = xCoordinates[i] = last + ttf.readByte();
133                     } else {
134                         last = xCoordinates[i] = last - ttf.readByte();
135                     }
136                 } else {
137                     if (TTFInput.flagBit(flags[i], X_SAME)) {
138                         last = xCoordinates[i] = last;
139                     } else {
140                         last = xCoordinates[i] = last + ttf.readShort();
141                     }
142                 }
143             }
144 
145             last = 0;
146             for (int i = 0; i < numberOfPoints; i++) {
147                 if (TTFInput.flagBit(flags[i], Y_SHORT)) {
148                     if (TTFInput.flagBit(flags[i], Y_POSITIVE)) {
149                         last = yCoordinates[i] = last + ttf.readByte();
150                     } else {
151                         last = yCoordinates[i] = last - ttf.readByte();
152                     }
153                 } else {
154                     if (TTFInput.flagBit(flags[i], Y_SAME)) {
155                         last = yCoordinates[i] = last;
156                     } else {
157                         last = yCoordinates[i] = last + ttf.readShort();
158                     }
159                 }
160             }
161         }
162 
163         public String toString() {
164             String str = super.toString() + ", " + numberOfContours
165                     + " contours, endPts={";
166             for (int i = 0; i < numberOfContours; i++)
167                 str += (i == 0 ? "" : ",") + endPtsOfContours[i];
168             str += "}, " + instructions.length + " instructions";
169             return str;
170         }
171 
172         public String toDetailedString() {
173             String str = toString() + "\n  instructions = {";
174             for (int i = 0; i < instructions.length; i++) {
175                 str += Integer.toHexString(instructions[i]) + " ";
176             }
177             return str + "}";
178         }
179 
180         public GeneralPath getShape() {
181             if (shape != null) {
182                 return shape;
183             }
184 
185             shape = new GeneralPath(GeneralPath.WIND_NON_ZERO);
186             int p = 0;
187             for (int i = 0; i < endPtsOfContours.length; i++) {
188                 int startIndex = p++;
189                 shape.moveTo(xCoordinates[startIndex], yCoordinates[startIndex]);
190                 boolean lastOnCurve = true;
191                 while (p <= endPtsOfContours[i]) {
192 
193                     if (onCurve[p]) {
194                         if (lastOnCurve) {
195                             shape.lineTo(xCoordinates[p], yCoordinates[p]);
196                         } else {
197                             shape.quadTo(xCoordinates[p - 1], yCoordinates[p - 1],
198                                     xCoordinates[p], yCoordinates[p]);
199                         }
200                         lastOnCurve = true;
201                     } else {
202                         if (!lastOnCurve) {
203                             int x1 = xCoordinates[p - 1];
204                             int y1 = yCoordinates[p - 1];
205                             int x2 = (int)((x1 + xCoordinates[p])/ 2.0);
206                             int y2 = (int)((y1 + yCoordinates[p])/ 2.0);
207                             shape.quadTo(x1, y1, x2, y2);
208                         }
209                         lastOnCurve = false;
210                     }
211                     p++;
212                 }
213                 if (!onCurve[p - 1]) {
214                     shape.quadTo(xCoordinates[p - 1], yCoordinates[p - 1],
215                             xCoordinates[startIndex], yCoordinates[startIndex]);
216                 } else if ((xCoordinates[p - 1] != xCoordinates[startIndex]) ||
217                            (yCoordinates[p - 1] != yCoordinates[startIndex])) {
218                     shape.closePath();
219                 }
220             }
221             return shape;
222         }
223     }
224 
225     // --------------------------------------------------------------------------------
226 
227     public class CompositeGlyph extends Glyph {
228 
229         private static final int ARGS_WORDS = 0;
230 
231         private static final int ARGS_XY = 1;
232 
233         private static final int SCALE = 3;
234 
235         private static final int XY_SCALE = 6;
236 
237         private static final int TWO_BY_TWO = 7;
238 
239         private static final int MORE_COMPONENTS = 5;
240 
241         private GeneralPath shape;
242 
243         private int noComponents;
244 
245         public String getType() {
246             return "Composite Glyph";
247         }
248 
249         public GeneralPath getShape() {
250             return shape;
251         }
252 
253         public void read() throws IOException {
254             super.read();
255             shape = new GeneralPath();
256 
257             noComponents = 0;
258             boolean more = true;
259             while (more) {
260                 noComponents++;
261                 ttf.readUShortFlags();
262                 more = ttf.flagBit(MORE_COMPONENTS);
263                 int glyphIndex = ttf.readUShort();
264                 int arg1, arg2;
265                 if (ttf.flagBit(ARGS_WORDS)) {
266                     arg1 = ttf.readShort();
267                     arg2 = ttf.readShort();
268                 } else {
269                     arg1 = ttf.readChar();
270                     arg2 = ttf.readChar();
271                 }
272                 AffineTransform t = new AffineTransform();
273                 if (ttf.flagBit(ARGS_XY)) {
274                     t.translate(arg1, arg2);
275                 } else {
276                     System.err
277                             .println("TTFGlyfTable: ARGS_ARE_POINTS not implemented.");
278                 }
279 
280                 if (ttf.flagBit(SCALE)) {
281                     double scale = ttf.readF2Dot14();
282                     t.scale(scale, scale);
283                 } else if (ttf.flagBit(XY_SCALE)) {
284                     double scaleX = ttf.readF2Dot14();
285                     double scaleY = ttf.readF2Dot14();
286                     t.scale(scaleX, scaleY);
287                 } else if (ttf.flagBit(TWO_BY_TWO)) {
288                     System.err
289                             .println("TTFGlyfTable: WE_HAVE_A_TWO_BY_TWO not implemented.");
290                 }
291 
292                 GeneralPath appendGlyph = (GeneralPath) getGlyph(glyphIndex)
293                         .getShape().clone();
294                 appendGlyph.transform(t);
295                 shape.append(appendGlyph, false);
296             }
297         }
298 
299         public String toString() {
300             return super.toString() + ", " + noComponents + " components";
301         }
302 
303     }
304 
305     // --------------------------------------------------------------------------------
306 
307     public Glyph[] glyphs;
308 
309     private long[] offsets;
310 
311     public String getTag() {
312         return "glyf";
313     }
314 
315     public void readTable() throws IOException {
316         glyphs = new Glyph[((TTFMaxPTable) getTable("maxp")).numGlyphs];
317         offsets = ((TTFLocaTable) getTable("loca")).offset;
318 
319         if (READ_GLYPHS) {
320             for (int i = 0; i < glyphs.length; i++) {
321                 if ((i > 0) && (offsets[i - 1] == offsets[i])) {
322                     glyphs[i] = glyphs[i - 1];
323                 } else {
324                     try {
325                         getGlyph(i);
326                     } catch (IOException e) {
327                         System.err.println("While reading glyph #" + i
328                                 + " (offset " + offsets[i] + "):");
329                         e.printStackTrace();
330                     }
331                 }
332             }
333         }
334 
335     }
336 
337     public Glyph getGlyph(int i) throws IOException {
338         if (glyphs[i] != null) {
339             return glyphs[i];
340         } else {
341             ttf.pushPos();
342             ttf.seek(offsets[i]);
343             int numberOfContours = ttf.readShort();
344             if (numberOfContours >= 0)
345                 glyphs[i] = new SimpleGlyph(numberOfContours);
346             else
347                 glyphs[i] = new CompositeGlyph();
348             glyphs[i].read();
349             // System.out.println(i+": "+offsets[i]+"-"+ttf.getPointer());
350             ttf.popPos();
351             return glyphs[i];
352         }
353     }
354 
355     public String toString() {
356         String str = super.toString();
357         for (int i = 0; i < glyphs.length; i++)
358             str += "\n  #" + i + ": " + glyphs[i];
359         return str;
360     }
361 }