1 4 5 9 10 package org.openlaszlo.media; 11 12 import java.io.InputStream ; 13 import java.io.FileInputStream ; 14 import java.io.IOException ; 15 import java.io.FileNotFoundException ; 16 import java.io.File ; 17 import java.awt.geom.Rectangle2D ; 18 19 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 import org.apache.log4j.*; 31 32 import org.apache.batik.svggen.font.*; 34 import org.apache.batik.svggen.font.table.*; 35 36 41 public class TTF2FFT { 42 43 46 private final static int LFC_TMARK = 160; 47 48 49 private static Logger mLogger = Logger.getLogger(TTF2FFT.class); 50 51 52 private static final int FFT_UnitsPerEm = 1024; 53 54 58 public static InputStream convert(File input) 59 throws TranscoderException, FileNotFoundException { 60 61 String path = input.getPath(); 62 63 if (!input.exists()) { 64 throw new FileNotFoundException (path); 65 } 66 67 if (!input.canRead()) { 70 throw new FileNotFoundException ("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 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 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 int flags = 0; 103 104 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 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 codeTable[0] = 0; 129 indexTable[0] = 0; 130 numCodes = 1; 131 132 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 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 = numCodes; 218 Shape [] shapeTable = new Shape[numGlyphs]; 219 Rectangle2D [] boundsTable = new Rectangle2D [numGlyphs]; 220 221 int unitsPerEm = headTable.getUnitsPerEm(); 222 double factor = (double)FFT_UnitsPerEm / (double)unitsPerEm; 223 224 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 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 (x, y, w, h); 272 shape.setBounds(boundsTable[i]); 273 } 274 275 FlashOutput buf = new FlashOutput( 40*1024 ); 277 278 final int TWIP = 20; 280 281 buf.writeByte( 'F' ); 282 buf.writeByte( 'W' ); 283 buf.writeByte( 'S' ); 284 buf.writeByte( 5 ); 286 buf.skip(4); 288 buf.write( new Rectangle2D.Double (0, 0, 5*TWIP, 5*TWIP) ); 290 buf.writeWord( 10 << 8 ); 292 293 buf.writeWord(0); 295 296 int tagPos = buf.getPos(); 298 299 buf.skip(6); 301 302 buf.writeWord(1); 304 305 int flagsPos = buf.getPos(); 307 buf.skip(2); 308 309 buf.writeStringL(fontName); 311 312 buf.writeWord(numGlyphs); 314 315 int [] offsetTable = new int [numGlyphs]; 316 317 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 glyphBuf.writeByte(0x11); 327 328 ShapeRecords shapeRecords = shapeTable[i].getShapeRecords(); 329 shapeRecords.write(glyphBuf, 1, 1); 330 glyphBuf.writeBits(0, 6); 332 glyphBuf.flushBits(); 333 } 334 335 boolean useWideOffsets = glyphBuf.getSize() + (numGlyphs+1)*2 > 0xffff; 338 339 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 buf.writeFOB(glyphBuf); 358 359 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 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 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 for( int i=0; i<numCodes; i++ ) { 387 buf.write( boundsTable[i] ); 388 } 389 390 int nKern = 0; 392 393 KernTable kernTable = (KernTable)ttf.getTable(Table.kern); 394 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 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 int x = buf.getPos() - tagPos - 6; 434 buf.writeLongTagAt(Tag.DEFINEFONT2, x, tagPos); 435 buf.writeWordAt(flags, flagsPos); 437 438 Tag.END_TAG.write( buf ); 440 441 int filesize = buf.getSize(); 443 buf.writeDWordAt( filesize, 4 ); 444 445 return buf.getInputStream(); 446 } 447 448 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 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 480 private static void addContourToShape(Shape shape, 481 TTFGlyph glyph, int startIndex, int count) { 482 483 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 541 private static int midValue(int a, int b) { 542 return (a + b) / 2; 543 } 544 } 545 | Popular Tags |