/* * DefineFont2.java * Transform * * Copyright (c) 2001-2010 Flagstone Software Ltd. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Flagstone Software Ltd. nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.flagstone.transform.font; import java.io.IOException; import java.util.ArrayList; import java.util.List; import com.flagstone.transform.Constants; import com.flagstone.transform.DefineTag; import com.flagstone.transform.MovieTypes; import com.flagstone.transform.coder.Coder; import com.flagstone.transform.coder.Context; import com.flagstone.transform.coder.SWFDecoder; import com.flagstone.transform.coder.SWFEncoder; import com.flagstone.transform.datatype.Bounds; import com.flagstone.transform.exception.IllegalArgumentRangeException; import com.flagstone.transform.shape.Shape; import com.flagstone.transform.shape.ShapeData; import com.flagstone.transform.text.Language; /** * DefineFont3 provides the same functionality as DefineFont2 but the * coordinates are expressed at 20 times the resolution, i.e. 1/20th of a twip * to increase the quality of the displayed text. * * DefineFont3 is used in conjunction with FontAlignment zones to support * snapping glyphs to the nearest pixel again to improve display quality. */ @SuppressWarnings({"PMD.TooManyFields", "PMD.TooManyMethods", "PMD.CyclomaticComplexity" }) public final class DefineFont3 implements DefineTag { /** Format string used in toString() method. */ private static final String FORMAT = "DefineFont3: { identifier=%d;" + " encoding=%d; small=%b; italic=%b; bold=%b; language=%s;" + " name=%s; shapes=%s; codes=%s; ascent=%d; descent=%d;" + " leading=%d; advances=%s; bounds=%s; kernings=%s}"; /** The unique identifier for this object. */ private int identifier; /** Code for the character encoding used. */ private int encoding; /** Is the font small. */ private boolean small; /** Is the font italicized. */ private boolean italic; /** Is the font bold. */ private boolean bold; /** Code representing the spoken language - used for line breaking. */ private int language; /** The font name. */ private String name; /** The list of font glyphs. */ private List<Shape> shapes; /** The list of character codes that map to each glyph - ascending order. */ private List<Integer> codes; /** Height of the font above the baseline. */ private int ascent; /** Height of the font below the baseline. */ private int descent; /** Spacing between successive lines. */ private int leading; /** Advances for each glyph. */ private List<Integer> advances; /** Bounding boxes for each glyph. */ private List<Bounds> bounds; /** Kernings for selected pairs of glyphs. */ private List<Kerning> kernings; /** The length of the object, minus the header, when it is encoded. */ private transient int length; /** Table of offsets to each glyph when encoded. */ private transient int[] table; /** Whether offsets are 16-bit (false) or 32-bit (true). */ private transient boolean wideOffsets; /** Whether character codes are 8-bit (false) or 16-bit (true). */ private transient boolean wideCodes; /** * Creates and initialises a DefineFont3 object using values encoded * in the Flash binary format. * * @param coder * an SWFDecoder object that contains the encoded Flash data. * * @param context * a Context object used to manage the decoders for different * type of object and to pass information on how objects are * decoded. * * @throws IOException * if an error occurs while decoding the data. */ public DefineFont3(final SWFDecoder coder, final Context context) throws IOException { length = coder.readUnsignedShort() & Coder.LENGTH_FIELD; if (length == Coder.IS_EXTENDED) { length = coder.readInt(); } coder.mark(); identifier = coder.readUnsignedShort(); shapes = new ArrayList<Shape>(); codes = new ArrayList<Integer>(); advances = new ArrayList<Integer>(); bounds = new ArrayList<Bounds>(); kernings = new ArrayList<Kerning>(); final int bits = coder.readByte(); final boolean containsLayout = (bits & Coder.BIT7) != 0; final int format = (bits >> Coder.TO_LOWER_NIB) & Coder.LOWEST3; encoding = 0; if (format == 1) { encoding = 1; } else if (format == 2) { small = true; // CHECKSTYLE IGNORE MagicNumberCheck FOR NEXT 1 LINES } else if (format == 4) { encoding = 2; } wideOffsets = (bits & Coder.BIT3) != 0; wideCodes = (bits & Coder.BIT2) != 0; italic = (bits & Coder.BIT1) != 0; bold = (bits & Coder.BIT0) != 0; if (wideCodes) { context.put(Context.WIDE_CODES, 1); } language = coder.readByte(); final int nameLength = coder.readByte(); name = coder.readString(nameLength); if (name.length() > 0) { while (name.charAt(name.length() - 1) == 0) { name = name.substring(0, name.length() - 1); } } final int glyphCount = coder.readUnsignedShort(); final int[] offset = new int[glyphCount + 1]; if (wideOffsets) { for (int i = 0; i < glyphCount; i++) { offset[i] = coder.readInt(); } } else { for (int i = 0; i < glyphCount; i++) { offset[i] = coder.readUnsignedShort(); } } // A device font may omit the offset to the start of the glyphs // when no layout information is included. if (coder.bytesRead() < length) { if (wideOffsets) { offset[glyphCount] = coder.readInt(); } else { offset[glyphCount] = coder.readUnsignedShort(); } } Shape shape; for (int i = 0; i < glyphCount; i++) { shape = new Shape(); shape.add(new ShapeData(offset[i + 1] - offset[i], coder)); shapes.add(shape); } if (wideCodes) { for (int i = 0; i < glyphCount; i++) { codes.add(coder.readUnsignedShort()); } } else { for (int i = 0; i < glyphCount; i++) { codes.add(coder.readByte()); } } if (containsLayout || coder.bytesRead() < length) { ascent = coder.readSignedShort(); descent = coder.readSignedShort(); leading = coder.readSignedShort(); for (int i = 0; i < glyphCount; i++) { advances.add(coder.readSignedShort()); } for (int i = 0; i < glyphCount; i++) { bounds.add(new Bounds(coder)); } final int kerningCount = coder.readUnsignedShort(); for (int i = 0; i < kerningCount; i++) { kernings.add(new Kerning(coder, context)); } } context.remove(Context.WIDE_CODES); coder.check(length); coder.unmark(); } /** * Creates a DefineFont2 object specifying only the name of the font. * * If none of the remaining attributes are set the Flash Player will load * the font from the system on which it is running or substitute a suitable * font if the specified font cannot be found. This is particularly useful * when defining fonts that will be used to display text in DefineTextField * objects. * * The font will be defined to use Unicode encoding. The flags which define * the font's face will be set to false. The lists of glyphs which define * the shapes and the code which map the character codes to a particular * glyph will remain empty since the font is loaded from the system on which * it is displayed. * * @param uid * the unique identifier for this font object. * @param fontName * the name of the font. */ public DefineFont3(final int uid, final String fontName) { setIdentifier(uid); setName(fontName); encoding = 0; shapes = new ArrayList<Shape>(); codes = new ArrayList<Integer>(); advances = new ArrayList<Integer>(); bounds = new ArrayList<Bounds>(); kernings = new ArrayList<Kerning>(); } /** * Creates and initialises a DefineFont3 object using the values copied * from another DefineFont3 object. * * @param object * a DefineFont3 object from which the values will be * copied. */ public DefineFont3(final DefineFont3 object) { identifier = object.identifier; encoding = object.encoding; small = object.small; italic = object.italic; bold = object.bold; language = object.language; name = object.name; ascent = object.ascent; descent = object.descent; leading = object.leading; shapes = new ArrayList<Shape>(object.shapes.size()); for (final Shape shape : object.shapes) { shapes.add(shape.copy()); } codes = new ArrayList<Integer>(object.codes); advances = new ArrayList<Integer>(object.advances); bounds = new ArrayList<Bounds>(object.bounds); kernings = new ArrayList<Kerning>(object.kernings); } /** {@inheritDoc} */ @Override public int getIdentifier() { return identifier; } /** {@inheritDoc} */ @Override public void setIdentifier(final int uid) { if ((uid < 1) || (uid > Coder.USHORT_MAX)) { throw new IllegalArgumentRangeException( 1, Coder.USHORT_MAX, uid); } identifier = uid; } /** * Add a character code and the corresponding glyph that will be displayed. * Character codes should be added to the font in ascending order. * * @param code * the character code. Must be in the range 0..65535. * @param obj * the shape that represents the glyph displayed for the * character code. * @return this object. */ public DefineFont3 addGlyph(final int code, final Shape obj) { if ((code < 0) || (code > Coder.USHORT_MAX)) { throw new IllegalArgumentRangeException(0, Coder.USHORT_MAX, code); } codes.add(code); if (obj == null) { throw new IllegalArgumentException(); } shapes.add(obj); return this; } /** * Add an advance to the list of advances. The index position of the entry * in the advance list is also used to identify the corresponding glyph and * vice-versa. * * @param anAdvance * an advance for a glyph. Must be in the range -32768..32767. * @return this object. */ public DefineFont3 addAdvance(final int anAdvance) { if ((anAdvance < Coder.SHORT_MIN) || (anAdvance > Coder.SHORT_MAX)) { throw new IllegalArgumentRangeException( Coder.SHORT_MIN, Coder.SHORT_MAX, anAdvance); } advances.add(anAdvance); return this; } /** * Add a bounds object to the list of bounds for each glyph. The index * position of the entry in the bounds list is also used to identify the * corresponding glyph and vice-versa. * * @param rect * an Bounds. Must not be null. * @return this object. */ public DefineFont3 add(final Bounds rect) { if (rect == null) { throw new IllegalArgumentException(); } bounds.add(rect); return this; } /** * Add a kerning object to the list of kernings for pairs of glyphs. * * @param anObject * an Kerning. Must not be null. * @return this object. */ public DefineFont3 add(final Kerning anObject) { if (anObject == null) { throw new IllegalArgumentException(); } kernings.add(anObject); return this; } /** * Returns the encoding scheme used for characters rendered in the font, * either ASCII, SJIS or UCS2. * * @return the encoding used for character codes. */ public CharacterFormat getEncoding() { CharacterFormat value; switch(encoding) { case 0: value = CharacterFormat.UCS2; break; case 1: value = CharacterFormat.ANSI; break; case 2: value = CharacterFormat.SJIS; break; default: throw new IllegalStateException(); } return value; } /** * Does the font have a small point size. This is used only with a Unicode * font encoding. * * @return a boolean indicating whether the font will be aligned on pixel * boundaries. */ public boolean isSmall() { return small; } /** * Sets the font is small. Used only with Unicode fonts. * * @param aBool * a boolean flag indicating the font will be aligned on pixel * boundaries. */ public void setSmall(final boolean aBool) { small = aBool; } // End Flash 7 /** * Is the font italicised. * * @return a boolean indicating whether the font is rendered in italics. */ public boolean isItalic() { return italic; } /** * Is the font bold. * * @return a boolean indicating whether the font is rendered in a bold face. */ public boolean isBold() { return bold; } // Flash 6 /** * Returns the language code identifying the type of spoken language for the * font. * * @return the Language used to determine how line-breaks are inserted * into text rendered using the font. Returns NONE if the object was * decoded from a movie contains Flash 5 or less. */ public Language getLanguage() { return Language.fromInt(language); } /** * Sets the language code used to determine the position of line-breaks in * text rendered using the font. * * NOTE: The language attribute is ignored if the object is encoded in a * Flash 5 movie. * * @param lang the Language identifying the spoken language for the text * rendered using the font. */ public void setLanguage(final Language lang) { language = lang.getValue(); } // End Flash 6 /** * Returns the name of the font family. * * @return the name of the font. */ public String getName() { return name; } /** * Returns the list of shapes used to define the outlines of each font * glyph. * * @return a list of Shape objects */ public List<Shape> getShapes() { return shapes; } /** * Returns the list of codes used to identify each glyph in the font. The * ordinal position of each Integer representing a code identifies a * particular glyph in the shapes list. * * @return a list of Integer objects that contain the character codes for * each glyph in the font. */ public List<Integer> getCodes() { return codes; } /** * Returns the ascent for the font in twips. * * @return the ascent for the font. */ public int getAscent() { return ascent; } /** * Returns the descent for the font in twips. * * @return the descent for the font. */ public int getDescent() { return descent; } /** * Returns the leading for the font in twips. * * @return the leading for the font. */ public int getLeading() { return leading; } /** * Returns the list of advances defined for each glyph in the font. * * @return a list of Integer objects that contain the advance for each * glyph in the font. */ public List<Integer> getAdvances() { return advances; } /** * Returns the list of bounding rectangles defined for each glyph in the * font. * * @return a list of Bounds objects. */ public List<Bounds> getBounds() { return bounds; } /** * Returns the list of kerning records that define the spacing between * glyph pairs. * * @return a list of Kerning objects that define the spacing adjustment * between pairs of glyphs. */ public List<Kerning> getKernings() { return kernings; } /** * Sets the font character encoding. * * @param anEncoding * the encoding used to identify characters, either ASCII, SJIS * or UNICODE. */ public void setEncoding(final CharacterFormat anEncoding) { switch(anEncoding) { case UCS2: encoding = 0; break; case ANSI: encoding = 1; break; case SJIS: encoding = 2; break; default: throw new IllegalArgumentException(); } } /** * Set the font is italicised. * * @param aBool * a boolean flag indicating whether the font will be rendered in * italics */ public void setItalic(final boolean aBool) { italic = aBool; } /** * Set the font is bold. * * @param aBool * a boolean flag indicating whether the font will be rendered in * bold face. */ public void setBold(final boolean aBool) { bold = aBool; } /** * Set the name of the font. * * @param aString * the name assigned to the font, identifying the font family. * Must not be null. */ public void setName(final String aString) { if (aString == null) { throw new IllegalArgumentException(); } name = aString; } /** * Set the list of shape records that define the outlines of the characters * used from the font. * * @param list * a list of Shape objects that define the glyphs for the font. * Must not be null. */ public void setShapes(final List<Shape> list) { if (list == null) { throw new IllegalArgumentException(); } shapes = list; } /** * Sets the codes used to identify each glyph in the font. * * @param list * sets the code table that maps a particular glyph to a * character code. Must not be null. */ public void setCodes(final List<Integer> list) { if (list == null) { throw new IllegalArgumentException(); } codes = list; } /** * Sets the ascent for the font in twips. * * @param aNumber * the ascent for the font in the range -32768..32767. */ public void setAscent(final int aNumber) { if ((aNumber < Coder.SHORT_MIN) || (aNumber > Coder.SHORT_MAX)) { throw new IllegalArgumentRangeException( Coder.SHORT_MIN, Coder.SHORT_MAX, aNumber); } ascent = aNumber; } /** * Sets the descent for the font in twips. * * @param aNumber * the descent for the font in the range -32768..32767. */ public void setDescent(final int aNumber) { if ((aNumber < Coder.SHORT_MIN) || (aNumber > Coder.SHORT_MAX)) { throw new IllegalArgumentRangeException( Coder.SHORT_MIN, Coder.SHORT_MAX, aNumber); } descent = aNumber; } /** * Sets the leading for the font in twips. * * @param aNumber * the descent for the font in the range -32768..32767. */ public void setLeading(final int aNumber) { if ((aNumber < Coder.SHORT_MIN) || (aNumber > Coder.SHORT_MAX)) { throw new IllegalArgumentRangeException( Coder.SHORT_MIN, Coder.SHORT_MAX, aNumber); } leading = aNumber; } /** * Sets the list of advances for each glyph in the font. * * @param list * of Integer objects that define the spacing between glyphs. * Must not be null. */ public void setAdvances(final List<Integer> list) { if (list == null) { throw new IllegalArgumentException(); } advances = list; } /** * Sets the list of bounding rectangles for each glyph in the font. * * @param list * a list of Bounds objects that define the bounding rectangles * that enclose each glyph in the font. Must not be null. */ public void setBounds(final List<Bounds> list) { if (list == null) { throw new IllegalArgumentException(); } bounds = list; } /** * Sets the list of kerning records for pairs of glyphs in the font. * * @param list * a list of Kerning objects that define an adjustment applied * to the spacing between pairs of glyphs. Must not be null. */ public void setKernings(final List<Kerning> list) { if (list == null) { throw new IllegalArgumentException(); } kernings = list; } /** {@inheritDoc} */ @Override public DefineFont3 copy() { return new DefineFont3(this); } @Override public String toString() { return String.format(FORMAT, identifier, encoding, small, italic, bold, language, name, shapes, codes, ascent, descent, leading, advances, bounds, kernings); } /** {@inheritDoc} */ @Override @SuppressWarnings("PMD.NPathComplexity") public int prepareToEncode(final Context context) { // CHECKSTYLE:OFF wideCodes = (context.get(Context.VERSION) > 5) || encoding != 1; context.put(Context.FILL_SIZE, 1); context.put(Context.LINE_SIZE, context.contains(Context.POSTSCRIPT) ? 1 : 0); if (wideCodes) { context.put(Context.WIDE_CODES, 1); } final int count = shapes.size(); int index = 0; int tableEntry; int shapeLength; if (wideOffsets) { tableEntry = (count << 2) + 4; } else { tableEntry = (count << 1) + 2; } table = new int[count + 1]; int glyphLength = 0; for (final Shape shape : shapes) { table[index++] = tableEntry; shapeLength = shape.prepareToEncode(context); glyphLength += shapeLength; tableEntry += shapeLength; } table[index++] = tableEntry; wideOffsets = (shapes.size() * 2 + glyphLength) > Coder.USHORT_MAX; length = 5; length += context.strlen(name); length += 2; length += shapes.size() * (wideOffsets ? 4 : 2); length += wideOffsets ? 4 : 2; length += glyphLength; length += shapes.size() * (wideCodes ? 2 : 1); if (containsLayoutInfo()) { length += 6; length += advances.size() * 2; for (final Bounds bound : bounds) { length += bound.prepareToEncode(context); } length += 2; length += kernings.size() * (wideCodes ? 6 : 4); } context.put(Context.FILL_SIZE, 0); context.put(Context.LINE_SIZE, 0); context.remove(Context.WIDE_CODES); return (length > Coder.HEADER_LIMIT ? Coder.LONG_HEADER : Coder.SHORT_HEADER) + length; // CHECKSTYLE:ON } /** {@inheritDoc} */ @Override @SuppressWarnings("PMD.NPathComplexity") public void encode(final SWFEncoder coder, final Context context) throws IOException { int format; if (encoding == 1) { format = 1; } else if (small) { format = 2; } else if (encoding == 2) { // CHECKSTYLE IGNORE MagicNumberCheck FOR NEXT 1 LINES format = 4; } else { format = 0; } if (length > Coder.HEADER_LIMIT) { coder.writeShort((MovieTypes.DEFINE_FONT_3 << Coder.LENGTH_FIELD_SIZE) | Coder.IS_EXTENDED); coder.writeInt(length); } else { coder.writeShort((MovieTypes.DEFINE_FONT_3 << Coder.LENGTH_FIELD_SIZE) | length); } if (Constants.DEBUG) { coder.mark(); } coder.writeShort(identifier); context.put(Context.FILL_SIZE, 1); context.put(Context.LINE_SIZE, context.contains(Context.POSTSCRIPT) ? 1 : 0); if (wideCodes) { context.put(Context.WIDE_CODES, 1); } int bits = 0; bits |= containsLayoutInfo() ? Coder.BIT7 : 0; bits |= format << Coder.TO_UPPER_NIB; bits |= wideOffsets ? Coder.BIT3 : 0; bits |= wideCodes ? Coder.BIT2 : 0; bits |= italic ? Coder.BIT1 : 0; bits |= bold ? Coder.BIT0 : 0; coder.writeByte(bits); coder.writeByte(language); coder.writeByte(context.strlen(name)); coder.writeString(name); coder.writeShort(shapes.size()); if (wideOffsets) { for (int i = 0; i < table.length; i++) { coder.writeInt(table[i]); } } else { for (int i = 0; i < table.length; i++) { coder.writeShort(table[i]); } } for (final Shape shape : shapes) { shape.encode(coder, context); } if (wideCodes) { for (final Integer code : codes) { coder.writeShort(code.intValue()); } } else { for (final Integer code : codes) { coder.writeByte(code.intValue()); } } if (containsLayoutInfo()) { coder.writeShort(ascent); coder.writeShort(descent); coder.writeShort(leading); for (final Integer advance : advances) { coder.writeShort(advance.intValue()); } for (final Bounds bound : bounds) { bound.encode(coder, context); } coder.writeShort(kernings.size()); for (final Kerning kerning : kernings) { kerning.encode(coder, context); } } context.put(Context.FILL_SIZE, 0); context.put(Context.LINE_SIZE, 0); context.remove(Context.WIDE_CODES); if (Constants.DEBUG) { coder.check(length); coder.unmark(); } } /** * Does the font contain layout information for the glyphs. * @return true if the font contains layout information, false otherwise. */ private boolean containsLayoutInfo() { final boolean layout = (ascent != 0) || (descent != 0) || (leading != 0) || !advances.isEmpty() || !bounds.isEmpty() || !kernings.isEmpty(); return layout; } }