/* * JSwiff is an open source Java API for Macromedia Flash file generation * and manipulation * * Copyright (C) 2004-2006 Ralf Terdic (contact@jswiff.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.jswiff.swfrecords.tags; import com.jswiff.io.InputBitStream; import com.jswiff.io.OutputBitStream; import com.jswiff.swfrecords.KerningRecord; import com.jswiff.swfrecords.LangCode; import com.jswiff.swfrecords.Rect; import com.jswiff.swfrecords.Shape; import java.io.IOException; /** * This tag is used to supply font information. Unlike with * <code>DefineFont</code>, fonts defined with this tag can be used for * dynamic text. Font metrics for improved layout can be supplied. Mapping to * device fonts is also possible. * * @since SWF 3. */ public class DefineFont2 extends DefinitionTag { private boolean shiftJIS; private boolean smallText; private boolean ansi; private boolean italic; private boolean bold; private LangCode languageCode; private String fontName; private Shape[] glyphShapeTable; private char[] codeTable; private short ascent; private short descent; private short leading; private short[] advanceTable; private Rect[] boundsTable; private KerningRecord[] kerningTable; protected int numGlyphs; private boolean hasLayout; /** * <p> * Creates a new DefineFont2 tag. Requires the font's character ID and name, * a glyph shape table and a code table. * </p> * * <p> * The shape table contains one shape for each glyph. When using dynamic * device text, the shape table can be empty (set to <code>null</code>). In * this case, the code table is ignored. * </p> * * <p> * The code table is an array of characters equal in size to the shape table. * It assigns a character to each glyph. * </p> * * @param characterId character ID of the font * @param fontName font name, either direct, e.g. 'Times New Roman', or * indirect, like '_serif' * @param glyphShapeTable array of shapes (for each glyph one) * @param codeTable array of chars (for each glyph one) * * @throws IllegalArgumentException if code table is different from glyph * count */ public DefineFont2( int characterId, String fontName, Shape[] glyphShapeTable, char[] codeTable) { code = TagConstants.DEFINE_FONT_2; this.characterId = characterId; this.fontName = fontName; if (glyphShapeTable != null) { this.glyphShapeTable = glyphShapeTable; numGlyphs = glyphShapeTable.length; if (codeTable.length != numGlyphs) { throw new IllegalArgumentException( "Size of codeTable must be equal to glyph count!"); } this.codeTable = codeTable; } } DefineFont2() { // empty } /** * Sets the value of the ANSI flag. If ANSI is set, the shiftJIS flag is * cleared. If neither ANSI nor shiftJIS are set, UCS-2 is used. * * @param ansi <code>true</code> if flag set, else <code>false</code> */ public void setANSI(boolean ansi) { this.ansi = ansi; if (ansi) { shiftJIS = false; } } /** * Checks if the ANSI flag is set. If neither ANSI nor shiftJIS are set, * UCS-2 is used. * * @return <code>true</code> if flag set, else <code>false</code> */ public boolean isANSI() { return ansi; } /** * Returns an array containing the advance value for each glyph of the font. * * @return advance table */ public short[] getAdvanceTable() { return advanceTable; } /** * Returns the font's ascent. The ascent is the distance from the baseline to * the ascender line (i.e. to the highest ascender of the font). * * @return font ascent (in EM square coords) */ public short getAscent() { return ascent; } /** * Sets/clears bold style. * * @param bold <code>true</code> for bold, otherwise <code>false</code> */ public void setBold(boolean bold) { this.bold = bold; } /** * Checks if the text is bold. * * @return <code>true</code> if text is bold, otherwise <code>false</code> */ public boolean isBold() { return bold; } /** * Returns the font's bounds table. * * @return bounds table. */ public Rect[] getBoundsTable() { return boundsTable; } /** * Sets the font's code table containing a character for each glyph. * * @param codeTable font's code table */ public void setCodeTable(char[] codeTable) { this.codeTable = codeTable; } /** * Returns the font's code table containing a character for each glyph. * * @return code table of font */ public char[] getCodeTable() { return codeTable; } /** * Returns the font's descent. The descent is the space below the baseline * required by the lowest descender (distance between base line and * descender line). * * @return font descent (in EM square coords) */ public short getDescent() { return descent; } /** * Sets the name of the font. This can be either a direct (e.g. 'Times New * Roman') or an indirect font name (e.g. '_serif'). * * @param fontName string containing the font's name */ public void setFontName(String fontName) { this.fontName = fontName; } /** * Returns the name of the font. This can be either a direct (e.g. 'Times New * Roman') or an indirect font name (e.g. '_serif'). * * @return font name as string */ public String getFontName() { return fontName; } /** * Sets the glyph shape table, containing a shape for each defined glyph. * * @param glyphShapeTable glyph shapes array */ public void setGlyphShapeTable(Shape[] glyphShapeTable) { this.glyphShapeTable = glyphShapeTable; } /** * Returns the glyph shape table, containing a shape for each defined glyph. * * @return glyph shapes array */ public Shape[] getGlyphShapeTable() { return glyphShapeTable; } /** * Sets/clears italic style. * * @param italic <code>true</code> for italic, otherwise <code>false</code> */ public void setItalic(boolean italic) { this.italic = italic; } /** * Checks if the text is italic. * * @return <code>true</code> if text is italic, otherwise <code>false</code> */ public boolean isItalic() { return italic; } /** * Returns the font's kerning table. * * @return kerning table */ public KerningRecord[] getKerningTable() { return kerningTable; } /** * Sets the language code of this font. * * @param languageCode a language code */ public void setLanguageCode(LangCode languageCode) { this.languageCode = languageCode; } /** * Returns the font's language code. * * @return a language code */ public LangCode getLanguageCode() { return languageCode; } /** * <p> * Sets layout information for dynamic glyph text. * </p> * * <p> * The <code>ascent</code> is the distance from the baseline to the ascender * line (i.e. to the highest ascender of the font). * </p> * * <p> * The <code>descent</code> is the space below the baseline required by the * lowest descender. * </p> * * <p> * The <code>leading</code> is the spacing between descender and ascender * line of two successive lines (vertical line spacing). * </p> * * <p> * This method allows the specification of a bounds table and a kerning * table. Supply arrays equal in size to the glyph count (which is * determined by the size of the glyph shape table), or use * <code>null</code> to simplify matters. * </p> * * @param ascent font ascender height (in EM square coords) * @param descent font descender height (in EM square coords) * @param leading vertical spacing between lines (from descender to next * line's ascender, in EM square coords) * @param advanceTable advance values (for each glyph one, in EM square * coords) * @param boundsTable glyph bounds table * @param kerningTable kerning table * * @throws IllegalArgumentException if size of boundsTable or kerningTable is * not equal to glyph count */ public void setLayout( short ascent, short descent, short leading, short[] advanceTable, Rect[] boundsTable, KerningRecord[] kerningTable) { hasLayout = true; this.ascent = ascent; this.descent = descent; this.leading = leading; this.advanceTable = advanceTable; if ((advanceTable == null) || (advanceTable.length != numGlyphs)) { throw new IllegalArgumentException( "Size of advanceTable must be equal to glyph count!"); } if ((boundsTable != null) && (boundsTable.length != numGlyphs)) { throw new IllegalArgumentException( "Size of boundsTable must be equal to glyph count!"); } this.boundsTable = boundsTable; this.kerningTable = kerningTable; } /** * Returns the font leading, i.e. the vertical spacing between the bottom of * the descender of one line and the top of the next line's ascender. * * @return font leading (in EM square coords) */ public short getLeading() { return leading; } /** * Sets the value of the shiftJIS flag. If shiftJIS is set, then ANSI is * cleared. If neither ANSI nor shiftJIS are set, UCS-2 is used. * * @param shiftJIS <code>true</code> if flag set, else <code>false</code> */ public void setShiftJIS(boolean shiftJIS) { this.shiftJIS = shiftJIS; if (shiftJIS) { ansi = false; } } /** * Checks if the shiftJIS flag is set. If neither ANSI nor shiftJIS are set, * UCS-2 is used. * * @return <code>true</code> if flag set, else <code>false</code> */ public boolean isShiftJIS() { return shiftJIS; } /** * Sets the value of the smallFont flag. When this flag is set, the font is * optimized for small text (anti-aliasing is disabled). * * @param smallText <code>true</code> if flag set, else <code>false</code> */ public void setSmallText(boolean smallText) { this.smallText = smallText; } /** * Checks the smallFont flag. When this flag is set, the font is optimized * for small text (anti-aliasing is disabled). * * @return <code>true</code> if set, else <code>false</code> */ public boolean isSmallText() { return smallText; } /** * Checks if layout information is supplied (ascent, descent, leading, * advance). * * @return <code>true</code> if layout info supplied, else <code>false</code> */ public boolean hasLayout() { return hasLayout; } protected void writeData(OutputBitStream outStream) throws IOException { outStream.writeUI16(characterId); outStream.writeBooleanBit(hasLayout); outStream.writeBooleanBit(shiftJIS); outStream.writeBooleanBit(smallText); outStream.writeBooleanBit(ansi); // compute offsetTable (offsets start at 0 long[] offsetTable = new long[numGlyphs]; // implicit: offsetTable[0] = 0; // later on, we apply correction to the offsets: // we add the offsetTable size and the size of codeTableOffset (2 or 4) to each offset // // use a byte array bit stream for writing shapeTable to compute the offsets OutputBitStream shapeTableStream = new OutputBitStream(); if (numGlyphs > 0) { // the first offset is known (0); write first shapeTable entry glyphShapeTable[0].write(shapeTableStream); } for (int i = 1; i < numGlyphs; i++) { // store offset of shape to offset table offsetTable[i] = shapeTableStream.getOffset(); // write shape glyphShapeTable[i].write(shapeTableStream); } long codeTableOffset = shapeTableStream.getOffset(); // last offset is the biggest - do we need 32 bits, or just 16? // apply correction (offsetTable and codeTableOffset) and compare to 2^16-1 boolean wideOffsets = false; // we don't need wide offsets if tables empty if (numGlyphs > 0) { wideOffsets = ((offsetTable[numGlyphs - 1] + (2 * (numGlyphs + 1))) > 65535); } // add offsetTable size and codeTableOffset size to each offset long offsetCorrection = (numGlyphs + 1) * (wideOffsets ? 4 : 2); for (int i = 0; i < numGlyphs; i++) { offsetTable[i] += offsetCorrection; } codeTableOffset += offsetCorrection; outStream.writeBooleanBit(wideOffsets); // in SWF6 and later, unicode is used, so use wideCodes=true as default // only if ansi or shiftJIS is set, set wideCodes=false boolean wideCodes = (!(ansi || shiftJIS)); outStream.writeBooleanBit(wideCodes); outStream.writeBooleanBit(italic); outStream.writeBooleanBit(bold); if (languageCode != null) { outStream.writeUI8(languageCode.getLanguageCode()); } else { outStream.writeUI8((short) 0); } byte[] fontNameBuffer = (fontName != null) ? fontName.getBytes("UTF-8") : new byte[0]; // null-terminated or not... Flash 2004 terminates with 0, so we do this too outStream.writeUI8((short) (fontNameBuffer.length + 1)); outStream.writeBytes(fontNameBuffer); outStream.writeUI8((short) 0); outStream.writeUI16(numGlyphs); // write offsetTable and codeTableOffset if (wideOffsets) { for (int i = 0; i < numGlyphs; i++) { outStream.writeUI32((offsetTable[i])); } outStream.writeUI32(codeTableOffset); } else { for (int i = 0; i < numGlyphs; i++) { outStream.writeUI16((int) offsetTable[i]); } outStream.writeUI16((int) codeTableOffset); } // write glyphShapeTable (from bit stream data) byte[] shapeTableBuffer = shapeTableStream.getData(); if (shapeTableBuffer.length > 0) { outStream.writeBytes(shapeTableBuffer); } // write codeTable, depending on wideCodes UI16 or UI8 if (wideCodes) { for (int i = 0; i < numGlyphs; i++) { outStream.writeUI16(codeTable[i]); } } else { for (int i = 0; i < numGlyphs; i++) { outStream.writeUI8((short) codeTable[i]); } } if (hasLayout) { outStream.writeSI16(ascent); outStream.writeSI16(descent); outStream.writeSI16(leading); for (int i = 0; i < numGlyphs; i++) { outStream.writeSI16(advanceTable[i]); } // bounds table if (boundsTable == null) { for (int i = 0; i < numGlyphs; i++) { (new Rect(0, 0, 0, 0)).write(outStream); } } else { for (int i = 0; i < numGlyphs; i++) { boundsTable[i].write(outStream); } } // kerning if (kerningTable == null) { outStream.writeUI16(0); // kerningCount = 0 } else { outStream.writeUI16(kerningTable.length); for (int i = 0; i < kerningTable.length; i++) { kerningTable[i].write(outStream, wideCodes); } } } } void setData(byte[] data) throws IOException { InputBitStream inStream = new InputBitStream(data); characterId = inStream.readUI16(); hasLayout = inStream.readBooleanBit(); shiftJIS = inStream.readBooleanBit(); smallText = inStream.readBooleanBit(); ansi = inStream.readBooleanBit(); boolean wideOffsets = inStream.readBooleanBit(); boolean wideCodes = inStream.readBooleanBit(); italic = inStream.readBooleanBit(); bold = inStream.readBooleanBit(); languageCode = new LangCode((byte) inStream.readUI8()); short fontNameLen = inStream.readUI8(); byte[] fontNameBuffer = inStream.readBytes(fontNameLen); if ((fontNameLen > 0) && (fontNameBuffer[fontNameLen - 1] == 0)) { fontNameLen--; } fontName = new String(fontNameBuffer, 0, fontNameLen, "UTF-8"); numGlyphs = inStream.readUI16(); // skip offsets, we don't need them readOnHandlingWideOffsets(inStream, wideOffsets); if (numGlyphs > 0) { glyphShapeTable = new Shape[numGlyphs]; for (int i = 0; i < numGlyphs; i++) { glyphShapeTable[i] = new Shape(inStream); } codeTable = new char[numGlyphs]; if (wideCodes) { for (int i = 0; i < numGlyphs; i++) { codeTable[i] = (char) inStream.readUI16(); } } else { for (int i = 0; i < numGlyphs; i++) { codeTable[i] = (char) inStream.readUI8(); } } } if (hasLayout) { ascent = inStream.readSI16(); descent = inStream.readSI16(); leading = inStream.readSI16(); if (numGlyphs > 0) { advanceTable = new short[numGlyphs]; for (int i = 0; i < numGlyphs; i++) { advanceTable[i] = inStream.readSI16(); } boundsTable = new Rect[numGlyphs]; for (int i = 0; i < numGlyphs; i++) { boundsTable[i] = new Rect(inStream); } } int kerningCount = inStream.readUI16(); if (kerningCount > 0) { kerningTable = new KerningRecord[kerningCount]; for (int i = 0; i < kerningCount; i++) { kerningTable[i] = new KerningRecord(inStream, wideCodes); } } } } protected void readOnHandlingWideOffsets(InputBitStream inStream, boolean wideOffsets) throws IOException { if (wideOffsets) { // skip offsetTable, UI32 inStream.readBytes(numGlyphs * 4); // skip codeTableOffset, UI32 inStream.readBytes(4); } else { // skip offsetTable, UI16 inStream.readBytes(numGlyphs * 2); // skip CodeTableOffset, UI16 inStream.readBytes(2); } } }