KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openlaszlo > media > TTF2FFT


1 /******************************************************************************
2  * TTF2FFT.java
3  * ****************************************************************************/

4
5 /* J_LZ_COPYRIGHT_BEGIN *******************************************************
6 * Copyright 2001-2004 Laszlo Systems, Inc. All Rights Reserved. *
7 * Use is subject to license terms. *
8 * J_LZ_COPYRIGHT_END *********************************************************/

9
10 package org.openlaszlo.media;
11
12 import java.io.InputStream JavaDoc;
13 import java.io.FileInputStream JavaDoc;
14 import java.io.IOException JavaDoc;
15 import java.io.FileNotFoundException JavaDoc;
16 import java.io.File JavaDoc;
17 import java.awt.geom.Rectangle2D JavaDoc;
18
19 // JGenerator APIs
20
import org.openlaszlo.iv.flash.api.*;
21 import org.openlaszlo.iv.flash.api.shape.*;
22 import org.openlaszlo.iv.flash.api.sound.*;
23 import org.openlaszlo.iv.flash.api.text.Font;
24 import org.openlaszlo.iv.flash.api.image.*;
25 import org.openlaszlo.iv.flash.util.*;
26
27 import org.openlaszlo.server.LPS;
28
29 // Logger
30
import org.apache.log4j.*;
31
32 // Apache Batik TrueType Font Parser
33
import org.apache.batik.svggen.font.*;
34 import org.apache.batik.svggen.font.table.*;
35
36 /**
37  * TrueType Font to Flash Font converter
38  *
39  * @author <a HREF="mailto:bloch@laszlosystems.com">Eric Bloch</a>
40  */

41 public class TTF2FFT {
42
43     /** Character code used by lfc for newline processing
44      * See LzFont.as and LzFontManager.as. 0 doesn't work for some reason.
45      */

46     private final static int LFC_TMARK = 160;
47
48     /** Logger */
49     private static Logger mLogger = Logger.getLogger(TTF2FFT.class);
50
51     /** Units per EmSquare for FFTs */
52     private static final int FFT_UnitsPerEm = 1024;
53
54     /**
55      * @param input input TTF file
56      * @return InputStream FFT
57      */

58     public static InputStream JavaDoc convert(File JavaDoc input)
59         throws TranscoderException, FileNotFoundException JavaDoc {
60
61         String JavaDoc path = input.getPath();
62
63         if (!input.exists()) {
64             throw new FileNotFoundException JavaDoc(path);
65         }
66
67         // Batik should throw an exception when it can't read
68
// the file (for access perms), but it doesn't.
69
if (!input.canRead()) {
70             throw new FileNotFoundException JavaDoc("Can't read: " + path);
71         }
72
73         org.apache.batik.svggen.font.Font ttf;
74         ttf = org.apache.batik.svggen.font.Font.create(input.getPath());
75
76         NameTable nameTable = ttf.getNameTable();
77         String JavaDoc fontName = "";
78         if (nameTable == null) {
79             fontName = input.getName();
80             int index = fontName.indexOf(".");
81             if (index > 0) {
82                 fontName = fontName.substring(0, index);
83             }
84             mLogger.warn("font missing ttf name table; made name, " + fontName + ", based on filename ");
85         } else {
86             fontName = nameTable.getRecord((short)1);
87         }
88         HeadTable headTable = ttf.getHeadTable();
89         HmtxTable hmtxTable = ttf.getHmtxTable();
90
91         if (headTable == null) {
92             // Bitmap fonts aren't required to have the head table.
93
// We don't support them yet. XXX
94
throw new TranscoderException(path + " missing ttf head table; this ttf font not supported");
95         }
96
97         if (hmtxTable == null) {
98             throw new TranscoderException(path + " missing ttf hmtx (horiz. metrics) table; this ttf font not supported");
99         }
100
101         // FFT flags
102
int flags = 0;
103
104         // Is font bold, italic, or bold-italic?
105
int macStyle = headTable.getMacStyle();
106         boolean isBold = (macStyle & 0x1) != 0;
107         boolean isItalic = (macStyle & 0x2) != 0;
108
109         boolean isUnicode = false;
110                                        
111                                        
112         if (isBold)
113             flags |= org.openlaszlo.iv.flash.api.text.Font.BOLD;
114         if (isItalic)
115             flags |= org.openlaszlo.iv.flash.api.text.Font.ITALIC;
116
117         // We have font metric info for the ttf
118
flags |= org.openlaszlo.iv.flash.api.text.Font.HAS_LAYOUT;
119
120         final int maxCodes = 0xffff;
121         int numCodes = 0;
122
123         int [] codeTable = new int[maxCodes];
124         int [] indexTable = new int[maxCodes];
125         int maxCode = 0;
126
127         // Add Code 0 (not sure why this is needed. Probably some lfc reason
128
codeTable[0] = 0;
129         indexTable[0] = 0;
130         numCodes = 1;
131
132         // 3 tries
133
final int NUM_TRIES = 3;
134         short [] cmapPlats = {
135                 Table.platformMicrosoft,
136                 Table.platformMacintosh,
137                 Table.platformMicrosoft,
138         };
139
140         short [] cmapEncodes = {
141                 Table.encodingUGL,
142                 Table.encodingRoman,
143                 Table.encodingUndefined,
144         };
145
146         boolean [] cmapIsUnicode = {
147             true,
148             false,
149             false,
150         };
151
152         int tries = 0;
153
154
155         CmapFormat cmapFmt = null;
156         boolean hasTmark = false;
157         int spaceIndex = 0;
158
159         for (int t = 0; t < NUM_TRIES; t++) {
160
161             cmapFmt = ttf.getCmapTable().getCmapFormat(cmapPlats[t], cmapEncodes[t]);
162             // Find char codes
163
if (cmapFmt != null) {
164                 for (int ch = 0; ch < 0xffff; ch++) {
165                     int index = cmapFmt.mapCharCode(ch);
166
167                     if (ch == 32) {
168                         spaceIndex = index;
169                     }
170         
171                     if (index != 0) {
172                         if (ch == LFC_TMARK) {
173                             hasTmark = true;
174                         }
175                         codeTable[numCodes] = ch;
176                         indexTable[numCodes] = index;
177                         numCodes++;
178                         if (ch > maxCode) {
179                             maxCode = ch;
180                         }
181                     }
182                 }
183             }
184             if (numCodes > 1) {
185                 break;
186             }
187             isUnicode = cmapIsUnicode[t];
188         }
189
190         if (cmapFmt == null) {
191             throw new TranscoderException("Can't find a cmap table in " + path);
192         }
193
194         if (!hasTmark) {
195             if (LFC_TMARK > maxCode) {
196                 maxCode = LFC_TMARK;
197             }
198
199             codeTable[numCodes] = LFC_TMARK;
200             indexTable[numCodes] = spaceIndex;
201             numCodes++;
202         }
203
204         if (isUnicode)
205             flags |= org.openlaszlo.iv.flash.api.text.Font.UNICODE;
206         else
207             flags |= org.openlaszlo.iv.flash.api.text.Font.ANSI;
208     
209         boolean useWideCodes = (maxCode > 255);
210         if (useWideCodes)
211             flags |= org.openlaszlo.iv.flash.api.text.Font.WIDE_CODES;
212
213         GlyfTable glyfTable = (GlyfTable)ttf.getTable(
214                 org.apache.batik.svggen.font.table.Table.glyf);
215
216         //int numGlyphs = ttf.getNumGlyphs();
217
int numGlyphs = numCodes;
218         Shape [] shapeTable = new Shape[numGlyphs];
219         Rectangle2D JavaDoc [] boundsTable = new Rectangle2D JavaDoc[numGlyphs];
220
221         int unitsPerEm = headTable.getUnitsPerEm();
222         double factor = (double)FFT_UnitsPerEm / (double)unitsPerEm;
223
224         // Get glyph shapes, and bounds.
225
for (int i = 0; i < numGlyphs; i++) {
226             int index = indexTable[i];
227             int code = codeTable[i];
228             GlyfDescript glyf = glyfTable.getDescription(index);
229             TTFGlyph glyph = null;
230
231             if (glyf != null) {
232                 glyph = new TTFGlyph(glyf);
233                 glyph.scale(factor);
234                 mLogger.debug("index: " + index +
235                               " charcode: " + code +
236                               " char: " + (char)code +
237                               " numPoints: " + glyph.getNumPoints());
238             } else {
239                 mLogger.debug("index: " + index +
240                               " charcode: " + code +
241                               " has no glyph.");
242             }
243
244
245             Shape shape = new Shape();
246             shape.newStyleBlock();
247             convertGlyphToShape(glyph, shape);
248             shapeTable[i] = shape;
249             
250             int x, w, y, h;
251
252             if (glyf != null) {
253                 x = (int)Math.round(glyf.getXMinimum() * factor);
254                 y = (int)Math.round(glyf.getYMaximum() * -factor);
255                 w = (int)Math.round((glyf.getXMaximum() - glyf.getXMinimum()) * factor);
256                 h = (int)Math.round((glyf.getYMaximum() - glyf.getYMinimum()) * factor);
257             } else {
258                 // Heuristic that hopefully works out ok for
259
// missing glyfs. First try space. Then try index0
260
glyf = glyfTable.getDescription(spaceIndex);
261                 if (glyf == null) {
262                     glyf = glyfTable.getDescription(0);
263                 }
264                 if (glyf != null) {
265                     w = (int)Math.round((glyf.getXMaximum() - glyf.getXMinimum()) * factor);
266                 } else {
267                     w = 0;
268                 }
269                 x = y = h = 0;
270             }
271             boundsTable[i] = new Rectangle2D.Double JavaDoc(x, y, w, h);
272             shape.setBounds(boundsTable[i]);
273         }
274
275         // Create a 40K buffer for generating the FFT
276
FlashOutput buf = new FlashOutput( 40*1024 );
277
278         // write header.
279
final int TWIP = 20;
280
281         buf.writeByte( 'F' );
282         buf.writeByte( 'W' );
283         buf.writeByte( 'S' );
284         // write version
285
buf.writeByte( 5 );
286         // skip file size
287
buf.skip(4);
288         // write rect
289
buf.write( new Rectangle2D.Double JavaDoc(0, 0, 5*TWIP, 5*TWIP) );
290         // write frame rate
291
buf.writeWord( 10 << 8 );
292
293         // Frame count
294
buf.writeWord(0);
295
296         // Remember position
297
int tagPos = buf.getPos();
298
299         // Skip definefont2 tag header
300
buf.skip(6);
301
302         // Write font id
303
buf.writeWord(1);
304
305         // Skip flags
306
int flagsPos = buf.getPos();
307         buf.skip(2);
308
309         // Write font name
310
buf.writeStringL(fontName);
311
312         // Write number of glyphs
313
buf.writeWord(numGlyphs);
314
315         int [] offsetTable = new int [numGlyphs];
316
317         // Write out the converted shapes into a temporary buffer
318
// And remember their offsets
319
FlashOutput glyphBuf = new FlashOutput(20*1024);
320         for( int i=0; i < numGlyphs; i++ ) {
321
322             offsetTable[i] = glyphBuf.getPos();
323
324             mLogger.debug("Writing shape " + i);
325             // 1 bit of line and fill
326
glyphBuf.writeByte(0x11);
327
328             ShapeRecords shapeRecords = shapeTable[i].getShapeRecords();
329             shapeRecords.write(glyphBuf, 1, 1);
330             // Write end of shape records
331
glyphBuf.writeBits(0, 6);
332             glyphBuf.flushBits();
333         }
334
335         // UseWideOffset if glyph buf + offset table + codeTable offset
336
// is bigger than 16bit int
337
boolean useWideOffsets = glyphBuf.getSize() + (numGlyphs+1)*2 > 0xffff;
338
339         // Write offsets and codeTable offset
340
if (useWideOffsets) {
341             int offset = (numGlyphs+1)*4;
342             flags |= org.openlaszlo.iv.flash.api.text.Font.WIDE_OFFSETS;
343             for(int i = 0; i < numGlyphs; i++) {
344                 buf.writeDWord(offsetTable[i] + offset);
345             }
346             buf.writeDWord( glyphBuf.getSize() + offset );
347         }
348         else {
349             int offset = (numGlyphs+1)*2;
350             for(int i = 0; i < numGlyphs; i++) {
351                 buf.writeWord(offsetTable[i] + offset);
352             }
353             buf.writeWord( glyphBuf.getSize() + offset );
354         }
355
356         // Write shapes
357
buf.writeFOB(glyphBuf);
358
359         // Write out char code table. (glyph index to char code)
360
for( int i=0; i<numCodes; i++ ) {
361             if(useWideCodes) {
362                 buf.writeWord( codeTable[i] );
363             } else {
364                 buf.writeByte( codeTable[i] );
365             }
366         }
367
368         // Write ascent, descent, (external) leading
369
int ascent = (int)Math.round((ttf.getAscent() * factor));
370         int descent = (int)Math.round((ttf.getDescent() * -factor));
371         int leading = ascent + descent - FFT_UnitsPerEm;
372         mLogger.debug("Font metrics: " + ascent + " " + descent + " " +
373                 leading );
374
375         buf.writeWord( ascent );
376         buf.writeWord( descent );
377         buf.writeWord( leading );
378
379         // Write advance table
380
for( int i=0; i<numCodes; i++ ) {
381             int index = indexTable[i];
382             buf.writeWord((int)Math.round(hmtxTable.getAdvanceWidth(index) *factor));
383         }
384
385         // Write bounds table
386
for( int i=0; i<numCodes; i++ ) {
387             buf.write( boundsTable[i] );
388         }
389
390         // Write kerning tables
391
int nKern = 0;
392
393         KernTable kernTable = (KernTable)ttf.getTable(Table.kern);
394         // TODO: [2003-11-05 bloch] this should be passed in as an argument and taken
395
// from the font definition in the LZX file
396
boolean doKern = LPS.getProperty("lps.font.kerning", "false").equals("true");
397
398         if (kernTable != null) {
399             if (doKern) {
400                 KernSubtable kst = kernTable.getSubtable(0);
401                 nKern = kst.getKerningPairCount();
402                 mLogger.debug(nKern + " kern pairs");
403                 // We optimize out all 0s
404
int goodKern = nKern;
405                 for (int i = 0; i < nKern; i++) {
406                     if (kst.getKerningPair(i).getValue() == 0) {
407                         goodKern--;
408                     }
409                 }
410                 buf.writeWord(goodKern);
411                 mLogger.debug(goodKern + " non-zero kern pairs");
412                 for (int i = 0; i < nKern; i++) {
413                     KerningPair pair = kst.getKerningPair(i);
414                     if (pair.getValue() != 0) {
415                         if (useWideCodes) {
416                             buf.writeWord( codeTable[pair.getLeft()] );
417                             buf.writeWord( codeTable[pair.getRight()] );
418                         } else {
419                             buf.writeByte( codeTable[pair.getLeft()] );
420                             buf.writeByte( codeTable[pair.getRight()] ) ;
421                         }
422                         buf.writeWord( (int)Math.round(pair.getValue()*factor) );
423                     }
424                 }
425             } else {
426                 mLogger.warn("skipping non-empty kerning table in " + path);
427             }
428         } else {
429             buf.writeWord( 0 );
430         }
431
432         // Write the DEFINEFONT2 tag
433
int x = buf.getPos() - tagPos - 6;
434         buf.writeLongTagAt(Tag.DEFINEFONT2, x, tagPos);
435         // Write the flags
436
buf.writeWordAt(flags, flagsPos);
437
438         // Write the END tag
439
Tag.END_TAG.write( buf );
440
441         // Write the file size back at the beginning.
442
int filesize = buf.getSize();
443         buf.writeDWordAt( filesize, 4 );
444
445         return buf.getInputStream();
446     }
447
448     /**
449      * Convert TTF Glyph to Flash Shape
450      * @param glyph
451      * @param shape
452      */

453     private static void convertGlyphToShape(TTFGlyph glyph, Shape shape) {
454
455         if (glyph == null) {
456             return;
457         }
458         int firstIndex = 0;
459         int count = 0;
460
461         // Add each contour to the shape.
462
for (int i = 0; i < glyph.getNumPoints(); i++) {
463             count++;
464             if (glyph.getPoint(i).endOfContour) {
465                 addContourToShape(shape, glyph, firstIndex, count);
466                 firstIndex = i + 1;
467                 count = 0;
468             }
469         }
470     }
471
472     /**
473      * Add glyphs contour starting from index point and going
474      * count number of points to shape.
475      * @param shape
476      * @param glyph
477      * @param startIndex
478      * @param count
479      */

480     private static void addContourToShape(Shape shape,
481             TTFGlyph glyph, int startIndex, int count) {
482
483         // If this is a single point on it's own, we can't do anything with it
484
if (glyph.getPoint(startIndex).endOfContour) {
485             return;
486         }
487
488         int offset = 0;
489
490         while (offset < count) {
491             Point p0 = glyph.getPoint(startIndex + offset%count);
492             Point p1 = glyph.getPoint(startIndex + (offset+1)%count);
493
494             if (offset == 0) {
495                 shape.movePenTo(p0.x, p0.y);
496                 if (startIndex == 0) {
497                     StyleChangeRecord scr = new StyleChangeRecord();
498                     scr.setFlags(StyleChangeRecord.FILLSTYLE1 |
499                                  StyleChangeRecord.LINESTYLE);
500                     scr.setFillStyle1(1);
501                     scr.setLineStyle(0);
502                     shape.getShapeRecords().addStyleChangeRecord(scr);
503                 }
504             }
505
506             if (p0.onCurve) {
507                 if (p1.onCurve) {
508                     shape.drawLineTo(p1.x, p1.y);
509                     offset++;
510                 } else {
511                     Point p2;
512                     p2 = glyph.getPoint(startIndex + (offset+2)%count);
513
514                     if (p2.onCurve) {
515                         shape.drawCurveTo(p1.x, p1.y, p2.x, p2.y);
516                     } else {
517                         shape.drawCurveTo(p1.x, p1.y,
518                                   midValue(p1.x, p2.x),
519                                   midValue(p1.y, p2.y));
520                     }
521                     offset+=2;
522                 }
523             } else {
524                 if (!p1.onCurve) {
525                     shape.drawCurveTo(p0.x, p0.y,
526                                       midValue(p0.x, p1.x),
527                                       midValue(p0.y, p1.y));
528                 } else {
529                     shape.drawCurveTo(p0.x, p0.y, p1.x, p1.y);
530                 }
531                 offset++;
532             }
533         }
534     }
535
536     /**
537      * @return midpoint of (a,b)
538      * @param a
539      * @param b
540      */

541     private static int midValue(int a, int b) {
542         return (a + b) / 2;
543     }
544 }
545
Popular Tags